文章目录

  • 《Java并发编程的艺术》读后笔记-Java中的并发工具类(第八章)
    • 1.等待多线程完成的CountDownLatch
    • 2.同步屏障CyclicBarrier
      • 2.1 CyclicBarrier简介
      • 2.2 CyclicBarrier应用场景
      • 2.3 CyclicBarrier和CountDownLatch的区别
    • 3.控制并发线程数的Semaphore
      • 3.1 Semaphore简介
      • 3.2 Semaphore应用场景
    • 4.线程间交换数据的Exchanger
      • 4.1 Exchanger简介
      • 4.2 Exchanger应用场景

《Java并发编程的艺术》读后笔记-Java中的并发工具类(第八章)

1.等待多线程完成的CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。

我们先看下面这个例子理解一下:

假如有这样一个需求:我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用 join()方法。

使用join()方法的代码如下:

/*** @author xppll* @date 2022/1/13 14:18*/
public class JoinCountDownLatchTest {public static void main(String[] args) throws InterruptedException {Thread parse1 = new Thread(() -> {});Thread parse2 = new Thread(() -> {System.out.println("parse2 finish");});parse1.start();parse2.start();parse2.join();parse2.join();System.out.println("all parser finish");}
}

join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。其中,wait(0)表示永远等待下去,代码片段如 下。

while (isAlive()) {wait(0);
}

直到join线程中止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的,所以在JDK里看不到,大家可以查看JVM源码。

在JDK 1.5之后的并发包中提供的CountDownLatch也可以实现join的功能,并且比join的功能更多

使用CountDownLatch的代码如下:

/*** @author xppll* @date 2022/1/13 14:23*/
public class CountDownLatchTest {static CountDownLatch c = new CountDownLatch(2);public static void main(String[] args) throws InterruptedException {new Thread(() -> {System.out.println(1);c.countDown();System.out.println(2);c.countDown();}).start();//阻塞主线程,只有计数器为0了,才不会阻塞c.await();System.out.println("3");}
}

CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。

当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。

由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。

如果有某个解析sheet的线程处理得比较慢,我们不可能让主线程一直等待,所以可以使用另外一个带指定时间的await方法——await(long time,TimeUnit unit),这个方法等待特定时间后,就会不再阻塞当前线程。join也有类似的方法。

注意:

  • 计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会阻塞当前线程。
  • CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。
  • 一个线程调用countDown方法happen-before,另外一个线程调用await方法。

2.同步屏障CyclicBarrier

2.1 CyclicBarrier简介

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

示例代码:

/*** @author xppll* @date 2022/1/13 14:36*/
public class CyclicBarrierTest {static CyclicBarrier c = new CyclicBarrier(2);public static void main(String[] args) {new Thread(() -> {try {c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}System.out.println(1);}).start();try {c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}System.out.println(2);}
}

因为主线程和子线程的调度是由CPU决定的,两个线程都有可能先执行,所以会产生两种输出。

第一种可能输出如下:

1
2

第二种可能输出如下:

2
1

如果把new CyclicBarrier(2)修改成new CyclicBarrier(3),则主线程和子线程会永远等待, 因为没有第三个线程执行await方法,即没有第三个线程到达屏障,所以之前到达屏障的两个线程都不会继续执行。

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。

代码如下:

/*** @author xppll* @date 2022/1/13 14:40*/
public class CyclicBarrierTest2 {static CyclicBarrier c = new CyclicBarrier(2, new A());public static void main(String[] args) {new Thread(() -> {try {c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}System.out.println(1);}).start();try {c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}System.out.println(2);}static class A implements Runnable {@Overridepublic void run() {System.out.println(3);}}

因为CyclicBarrier设置了拦截线程的数量是2,所以必须等代码中的第一个线程和线程A都执行完之后,才会继续执行主线程,然后输出2,所以代码执行后的输出如下。

3
1
2

2.2 CyclicBarrier应用场景

CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景

例如,用一个Excel保存了用户所有银行流水,每个Sheet保存一个账户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。

代码如下:

/*** 银行流水处理服务类*/
public class BankWaterService implements Runnable {//创建4给屏障,处理完之后执行当前类的run方法private CyclicBarrier c = new CyclicBarrier(4, this);//假设只有4给sheet,所以只启动4个线程private Executor executor = Executors.newFixedThreadPool(4);//保存每个sheet计算出的银行流水结果private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new ConcurrentHashMap<String, Integer>();private void count() {for (int i = 0; i < 4; i++) {executor.execute(() -> {//计算当前sheet的银流数据sheetBankWaterCount.put(Thread.currentThread().getName(), 1);//银流计算法完成,插入一个屏障try {//计数减一c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}});}}@Overridepublic void run() {int result = 0;//汇总每个sheet计算出的结果for (Map.Entry<String, Integer> sheet : sheetBankWaterCount.entrySet()) {result += sheet.getValue();}//将结果输出sheetBankWaterCount.put("result", result);System.out.println(result);}public static void main(String[] args) {BankWaterService bankWaterService = new BankWaterService();bankWaterService.count();}
}
//输出:
4

2.3 CyclicBarrier和CountDownLatch的区别

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置

所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。

CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken()方法用来了解阻塞的线程是否被中断。

代码如下:

/*** @author xppll* @date 2022/1/13 15:24*/
public class CyclicBarrierTest3 {static CyclicBarrier c = new CyclicBarrier(2);public static void main(String[] args) {Thread thread = new Thread(() -> {try {c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}});thread.start();thread.interrupt();try {c.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();System.out.println(c.isBroken());}}
}
//输出:
true

3.控制并发线程数的Semaphore

3.1 Semaphore简介

Semaphore(信号量)是用来控制同时访问特定资源的线程数量。它通过协调各个线程,以保证合理的使用公共资源。

打个比方:

把Semaphore比作是控制流量的红绿灯。比如××马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入××马路,但是如果前一百辆中有5辆车已经离开了××马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行

3.2 Semaphore应用场景

Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。

假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这 时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,就可以使用Semaphore来做流量控制。

代码如下:

/*** @author xppll* @date 2022/1/13 15:32*/
public class SemaphoreTest {public static final int THREAD_COUNT = 30;private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);private static Semaphore s = new Semaphore(10);public static void main(String[] args) {for (int i = 0; i < THREAD_COUNT; i++) {threadPool.execute(() -> {try {s.acquire();System.out.println("save data");s.release();} catch (InterruptedException e) {e.printStackTrace();}});}threadPool.shutdown();}
}

在代码中,虽然有30个线程在执行,但是只允许10个并发执行。

Semaphore的构造方法 Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。

Semaphore的用法也很简单,首先线程使用Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。

其他方法:

  • intavailablePermits():返回此信号量中当前可用的许可证数。
  • intgetQueueLength():返回正在等待获取许可证的线程数。
  • booleanhasQueuedThreads():是否有线程正在等待获取许可证。
  • void reducePermits(int reduction):减少reduction个许可证,是个protected方法。
  • Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected方法。

4.线程间交换数据的Exchanger

4.1 Exchanger简介

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。

它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

4.2 Exchanger应用场景

  • Exchanger可以用于遗传算法。遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,并使用交叉规则得出2个交配结果。
  • Exchanger也可以用于校对工作。比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水,为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对两个Excel数据进行校对,看看是否录入一致。

关于第二个应用场景的代码举例:

/*** @author xppll* @date 2022/1/13 16:14*/
public class ExchangerTest {public static final Exchanger<String> exgr = new Exchanger<String>();private static ExecutorService threadPool = Executors.newFixedThreadPool(2);public static void main(String[] args) {threadPool.execute(() -> {try {//A录入银行流水数据String A = "银行流水A";exgr.exchange(A);} catch (InterruptedException e) {e.printStackTrace();}});threadPool.execute(() -> {try {//B录入银行流水数据String B = "银行流水B";String A = exgr.exchange("B");System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:"+ A + ",B录入是:" + B);} catch (InterruptedException e) {e.printStackTrace();}});threadPool.shutdown();}
}
//输出:
A和B数据是否一致:false,A录入的是:银行流水A,B录入是:银行流水B

如果两个线程有一个没有执行exchange()方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用exchange(V x,longtimeout,TimeUnit unit)设置最大等待时长。

《Java并发编程的艺术》读后笔记-Java中的并发工具类(第八章)相关推荐

  1. 《Java并发编程的艺术》——Java中的并发工具类、线程池、Execute框架(笔记)

    文章目录 八.Java中的并发工具类 8.1 等待多线程完成的CountDownLatch 8.2 同步屏障CyclicBarrier 8.2.1 CyclicBarrier简介 8.2.2 Cycl ...

  2. 《Java并发编程的艺术》——Java并发的前置知识(笔记)

    文章目录 一.并发编程的挑战 1.1 上下文切换 1.1.1 多线程一定快吗 1.1.2 如何减少上下文的切换 1.2 死锁 死锁发生的条件 预防死锁 避免死锁 1.3 资源限制的挑战 1.3.1 什 ...

  3. Java并发编程的艺术(十)——Java中的锁(5)

    1. LockSupport工具 1.1 LockSupport的作用 当需要阻塞或唤醒一个线程的时候,都会使用LockSupport工具类来完成相应工作.LockSupport定义了一组公共的静态方 ...

  4. Java并发编程的艺术笔记-Java内存模型

    1.Java内存模型的基础 1.1 并发编程模型的两个关键问题 线程之间如何通信: 通信是指线程之间以何种机制来交换信息 通信机制有两种:共享内存和消息传递 线程之间如何同步: 同步:指程序中用于控制 ...

  5. java 并发 mobi_Java并发编程的艺术pdf txt mobi下载及读书笔记

    Java并发编程的艺术pdf txt mobi读书笔记 如何解决资源限制的问题:对于软件资源限制,可以考虑使用资源池将资源复用.比如使用连接池将数据库和Socket连接复用,或者在调用对方webser ...

  6. Java并发编程的艺术,解读并发编程的优缺点

    并发编程的优缺点 使用并发的原因 多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升. 在特殊的业务场景下先天的就适合于并发编程. 比如在 ...

  7. 《Java并发编程的艺术》笔记

    <Java并发编程的艺术>笔记 第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种 ...

  8. 《Java并发编程的艺术》——线程(笔记)

    文章目录 四.Java并发编程基础 4.1 线程简介 4.1.1 什么是线程 4.1.2 为什么要使用多线程 4.1.3 线程优先级 4.1.4 线程的状态 4.1.5 Daemon线程 4.2 启动 ...

  9. 《Java 并发编程的艺术》迷你书

    本文源自InfoQ发表的<Java 并发编程的艺术>电子书  作者:方腾飞  序言:张龙 免费下载此迷你书 推荐序 欣闻腾飞兄弟的<聊聊并发>系列文章将要集结成InfoQ迷你书 ...

最新文章

  1. 强化学习(十九) AlphaGo Zero强化学习原理
  2. HD 2048 数塔 DP(简单递推)
  3. 【Python】字符转换为 ASCII 码
  4. 创建MySQL数据库中useUnicode=truecharacterEncoding=UTF-8
  5. 处于计算机学科的基础地位,谈谈离散数学在计算机学科中的地位和作用(原稿)...
  6. ANDROID 高性能图形处理 之 OPENGL ES
  7. Illustrator中文版教程,如何在 Illustrator 中使用自由变换工具?
  8. 自媒体原创检测工具,了解了这个离收获大量粉丝不会远啦!
  9. 驾驭你的“职场布朗运动” (作者李云)
  10. jmeter学习问题记录
  11. linux 添加动态链接库的方法
  12. ajax里数组添加数据,小笔记(一):ajax传递数组及将ajax返回数据赋值
  13. C语言常见开发工具的安装和配置
  14. 倍速增长!裁员潮下小i机器人逆势扩招
  15. [原创]从程序员角度分析安徽电信HTTP劫持的无耻行径,以及修改Hosts文件,使用OPENDNS无效情况下的解决方案...
  16. CSS颜色:RGB颜色/HEX颜色/HSL颜色(网页颜色完全总结)
  17. Akamai 1.75
  18. CentOS 7安装配置vsftp并搭建FTP(一)
  19. 2021WSB-day1-2-Ajay Kumar介绍Less Constrained下的非接触性掌纹识别
  20. avada和divi哪个好

热门文章

  1. Beta 测试和 Alpha 测试有什么区别?
  2. 2020考研,老学长帮你规划
  3. oracle数据库ogg延迟,oracle goldengate ogg 源段传输进程lag延迟不断增加的原因?
  4. 快手小店通效果好吗?
  5. 记录搭建分布式项目环境-MySQL集群
  6. b站网页版没有html播放,网页b站能小窗口播放吗?怎么播放?最新版本bilibili小窗口播放器...
  7. netstat命令不可用,安装net-tools
  8. Handlebars js模版
  9. 获取固定到任务栏的快捷方式的图标
  10. RZ,NRZ,NRZI