PrintAB ?

在我们面试Java并发的时候,面试官有一个经典问题叫做多线程循环打印"A"和"B",这题之所以经典,就是考察了Java的线程通信模型.学完JUC之后,咱们也能用多种方法解决这个问题,
show me the code!!

互斥锁+共享变量

互斥锁是Java并发模型最容易想到的解决方案,通过一个锁来保障只有一个线程成功打印,通过一个共享变量来决定哪个线程可以获得锁;

public class PrintAB{private final volatile boolean isPrintA = true;private final int loopTimes = 10;class PrintA implements Runnable{@Overridepublic void run(){for(int i=0;i<loopTimes; i++){synchronized(this){while(!isprintA){this.wait();}catch(InterruptedException e){e.printStackTrace();}System.out.print("A");isPrintA = false;this.notifyAll();}}}}class PrintB implements Runnable{@Overridepublic void run(){for(int i=0;i<loopTimes; i++){synchronized(this){while(isprintA){this.wait();}catch(InterruptedException e){e.printStackTrace();}System.out.print("B");isPrintA = true;this.notifyAll();}}}}public void printAB(){Thread a= new Thread(new PrintA()).start();Thread b = new Thread(new PrintB()).start();a.start();b.start();}
}

代码中使用了一个共享的对象作为锁,通过isPrintA变量来控制线程A和线程B的交替打印。在PrintA线程中,当isPrintA为false时,线程进入等待状态;当isPrintA为true时,线程打印"A"并将isPrintA设为false,然后唤醒其他等待的线程。在PrintB线程中,当isPrintA为true时,线程进入等待状态;当isPrintA为false时,线程打印"B"并将isPrintA设为true,然后唤醒其他等待的线程。通过循环10次,线程A和线程B交替打印"A"和"B"。

信号量Semaphore

对比共享变量和互斥锁的视线,信号量是一种性能更高的写法:

/*** 使用信号量来控制打印AB <p>* 信号量有两种方法 acquire和 release分别使 semaphore -1 和 +1<p>* 当 semaphore >0 可以继续执行acquire之后的代码*/
public class PrintAB {public static void main(String[] args) {final int loopTimes = 10;Semaphore s1 = new Semaphore(1);Semaphore s2 = new Semaphore(0);new Thread(() -> {for (int i = 0; i < loopTimes; i++) {try {s1.acquire();System.out.print("A");s2.release();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"threadA").start();new Thread(() -> {for (int i = 0; i < loopTimes; i++) {try {s2.acquire();System.out.print("B");s1.release();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"threadB").start();}
}

在这个版本的代码中,我们使用了两个信号量,semaphoreA和semaphoreB。初始时,semaphoreA的许可数为1,semaphoreB的许可数为0。在PrintA线程中,先通过semaphoreA.acquire()获取semaphoreA的许可,然后打印"A",最后通过semaphoreB.release()释放semaphoreB的许可,使得PrintB线程可以获取semaphoreB的许可。在PrintB线程中,先通过semaphoreB.acquire()获取semaphoreB的许可,然后打印"B",最后通过semaphoreA.release()释放semaphoreA的许可,使得PrintA线程可以获取semaphoreA的许可。通过循环10次,线程A和线程B交替打印"A"和"B"。使用信号量可以更高效地实现线程的交替执行,避免了不必要的等待和唤醒操作,但是会有线程阻塞问题;

无锁的CAS

CAS(Compare And Swap) 通过不断的判断当前共享变量是否满足要求来实现,Java提供了一些Atomic的原子安全变量来完成安全的共享:

public class PrintAB {private static volatile AtomicInteger turn = new AtomicInteger(0);public static void main(String[] args) {Thread threadA = new Thread(new PrintA());Thread threadB = new Thread(new PrintB());threadA.start();threadB.start();}static class PrintA implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {while (turn.get() == 1) {Thread.yield();}System.out.print("A");turn.getAndIncrement();}}}static class PrintB implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {while (turn.get() == 0) {Thread.yield();}System.out.print("B");turn.decrementAndGet();}}}
}

在这个版本的代码中,我们使用了AtomicInteger来保证对turn变量的原子性操作。turn变量表示当前轮到哪个线程执行打印操作,初始值为0。在PrintA线程中,通过循环判断turn的值是否为0,如果不是0,则调用Thread.yield()让出CPU时间片,让其他线程执行。当turn的值为0时,打印"A",然后通过turn.getAndIncrement()原子地将turn的值加1。在PrintB线程中,通过循环判断turn的值是否为1,如果不是1,则调用Thread.yield()让出CPU时间片,让其他线程执行。当turn的值为奇数时,打印"B",然后通过turn.decrementAndGet()原子地将turn的值-1。通过循环10次,线程A和线程B交替打印"A"和"B"。使用CAS操作可以实现对共享变量的原子性读取和修改,从而实现多线程的交替执行;

CAS 和 Semaphore哪个性能更好?

在性能方面,CAS(Compare and Swap)操作通常比信号量(Semaphore)更高效。这是因为CAS是一种轻量级的原子操作,它不需要进入内核态或阻塞线程,而是在用户态进行原子性的读取和修改。相比之下,信号量需要进行线程的阻塞和唤醒,涉及到内核态和用户态之间的切换,因此开销相对较大。

CAS操作适用于对单个变量进行原子性操作的场景,特别是在并发读取和修改的情况下。它可以避免线程竞争和锁的开销,因此在高并发的情况下通常具有较好的性能。

信号量适用于需要对资源进行控制和同步的场景,它可以限制并发访问的线程数量。信号量可以确保在达到一定条件之前,线程将被阻塞,从而协调线程的执行顺序。尽管信号量提供了更高级的同步机制,但由于涉及线程的阻塞和唤醒,因此相对于CAS而言,它的性能开销更大。

综上所述,CAS在性能方面通常更好,特别适用于对单个变量进行原子操作的场景。而信号量更适合于需要控制和同步多个线程访问共享资源的场景。然而,性能的优劣也取决于具体的使用情况和上下文环境,因此在选择使用CAS还是信号量时,需要综合考虑实际需求和性能特点。

LockSupport的park和unpark

public class PrintAB {private static Thread threadA;private static Thread threadB;public static void main(String[] args) {threadA = new Thread(new PrintA());threadB = new Thread(new PrintB());threadA.start();threadB.start();}static class PrintA implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.print("A");LockSupport.unpark(threadB);LockSupport.park();}}}static class PrintB implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {LockSupport.park();System.out.print("B");LockSupport.unpark(threadA);}}}
}

在这个版本的代码中,我们使用了LockSupport类来实现线程的阻塞和唤醒。在PrintA线程中,先打印"A",然后调用LockSupport.unpark(threadB)唤醒PrintB线程,接着调用LockSupport.park()将PrintA线程阻塞。在PrintB线程中,先调用LockSupport.park()将PrintB线程阻塞,等待被唤醒,然后打印"B",最后调用LockSupport.unpark(threadA)唤醒PrintA线程。通过循环10次,线程A和线程B交替打印"A"和"B"。使用park和unpark方法可以实现线程的阻塞和唤醒,从而实现多线程的交替执行。

除了上述方法还可以使用CyclicBarrier,但是对于这个问题来说不算最佳方案,在此就不展开了;

JUC:通过PrintAB管中窥豹相关推荐

  1. 面试高频——JUC并发工具包快速上手(超详细总结)

    目录 一.什么是JUC 二.基本知识 2.1.进程和线程 2.2.Java默认有两个进程 2.3.Java能够开启线程吗? 2.4.并发和并行 2.5.线程的状态 2.6.wait和sleep的区别 ...

  2. JUC 常用 4 大并发工具类

    欢迎关注方志朋的博客,回复"666"获面试宝典 什么是JUC? JUC就是java.util.concurrent包,这个包俗称JUC,里面都是解决并发问题的一些东西 该包的位置位 ...

  3. JUC AQS ReentrantLock源码分析

    Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还 ...

  4. java应用menchac_java之JUC

    java的JUC即java.util.concurrent包,这是一个处理线程的工具包. 一.synchronized与lock锁 两者区别? 1.synchronized是java关键字,lock是 ...

  5. juc java_深入理解JUC(java.util.concurrent)

    Concurrent下的核心类 Executor:具有runnable任务的执行者 ExecutorService:一个线程池管理者,实现类有多种,能把runnable,callable提交到线程池中 ...

  6. 【初识】-JUC·Executor框架

    前言 多线程和并发这两个东西真的是向往已久,总是有一种神秘的感觉,想去探索一波,又担心水平不够无法驾驭.想以读书笔记的方式来写,但是又觉得缺少自己的一些思考:但是在没有足够并发编程经验的情况下又没法去 ...

  7. 线程不安全 静态变量_ArrayList线程不安全,JUC是如何处理的

    当我们并发往ArrayList中添加值的时候,当并发过大的时候会出现ConcurrentModificationException(并发修改异常), List list =new ArrayList& ...

  8. 多线程总结-JUC中常用的工具类

    本文只记录JUC中较常用到的一些工具类,只是列举其常见的使用方法,至于其实现原理,此处不做说明. CountDownLatch 一个同步工具类,允许一个或多个线程一直等待,直到其他线程运行完成后再执行 ...

  9. JAVA并发编程JUC基础学习(简介)

    2019独角兽企业重金招聘Python工程师标准>>> 之前写过一篇并发编程的简单实例应用,Future快速实现并发编程,可以很快的在自己的项目中应用,但并不系统,之前说过总结一篇( ...

最新文章

  1. 实验三 Gmapping建图
  2. Alpha 冲刺11——总结
  3. c语言从集合中随机选取一个值,从集合中挑选一个随机元素
  4. html多个盒子重叠浮动,如何让浮动后的多个盒子水平居中
  5. 重新组织函数--《重构》阅读笔记
  6. PrimeTime指南——概述和基本流程
  7. 085 Maximal Rectangle 最大矩形
  8. ps专业色彩调色扩展面板 Moody Photoshop Panel 1.1.2汉化版
  9. 检验mysql安装成功win7,手把手教你win7系统成功安装 RMySQL的处理对策
  10. Enum 作为一个数据源绑定
  11. 程序设计入门经典题解(百练篇)
  12. Android---------------Handler的学习
  13. 22. yii 2 sql
  14. Twaver-HTML5基础学习(8)拓扑元素(Element)_网元(Element)、节点(Node)
  15. 互联网+是什么意思?
  16. 常见Linux应急排查命令
  17. 【DockerCE】RHEL 7.9完整安装DockerCE 20.10.5的包集合
  18. python怎么做q检验_关于eviews做时间序列模型的残差Q统计量检验我决定写一些!...
  19. 小案例:实现http://www.alloyteam.com/page/0/移动端效果,博客文章列表和文章详情页面
  20. 请问你知道分布式系统设计模式的最低水位线思想么?

热门文章

  1. TeamViewer如何解除5分钟限制和检测为商业用途
  2. jquery改变选中对象的css,使用jQuery选择和操作CSS伪元素,例如:: before和:: after
  3. ES6数组去重的常用方法
  4. c++ 构造函数 继承
  5. 多线程Future模式使用
  6. burpsuite的intruder模块简介
  7. day15 java基础(Collection类,List类,Object类)
  8. VUE定时器请求后端数据
  9. linux卸载webmin及其配置,CentOS安装Webmin
  10. python自动抓包_burp抓包+python定时签到