java同步并发工具类CountDownLatch、CyclicBarrier和Semaphore

闭锁CountDownLatch

  闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行,例如:

确保某个计算在其需要的所有资源都被初始化之后才继续执行。二元闭锁(包括两个状态)可以用来表示“资源R已经被初始化”,而所有需要R的操作都必须现在这个闭锁上等待。 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。每个服务都有一个相关的二元闭锁。当启动服务S时,将首先在S依赖的其他服务的闭锁上等待,在所有依赖的服务都启动后会释放闭锁S,这样其他依赖S的服务才能继续执行。 等待直到某个操作的所有参与者(例如,在多玩家游戏中的所有玩家)都就绪再继续执行。在这种情况下,当所有玩家都准备就绪时,闭锁将到达结束状态。

  CountDownLatch是一种灵活的闭锁实现,可以在上述各种情况中使用,它可以使一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示有一个事件已经发生了,而await方法等待计数器达到0,这表示所有需要等待的事件都已经发生。如果计数器的值非0,那么await会一直阻塞直到计数器为0,或者等待中的线程中断,或者等待超时。·

如下代码展示了CountDownLatch的使用。

 1 import java.util.concurrent.CountDownLatch;
 2
 3 /**
 4  * @author bridge
 5  */
 6 public class CountDownLatchTest {
 7
 8
 9     public static void main(String[] args) {
10         final CountDownLatch latch = new CountDownLatch(2);
11         new Thread() {
12             public void run() {
13                 try {
14                     System.out.println("子线程" + Thread.currentThread().getName() + "正在执行");
15                     Thread.sleep(3000);
16                     System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
17                     latch.countDown();
18                 } catch (InterruptedException e) {
19                     e.printStackTrace();
20                 }
21             }
22         }.start();
23
24         new Thread() {
25             public void run() {
26                 try {
27                     System.out.println("子线程" + Thread.currentThread().getName() + "正在执行");
28                     Thread.sleep(3000);
29                     System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
30                     latch.countDown();
31                 } catch (InterruptedException e) {
32                     e.printStackTrace();
33                 }
34             }
35         }.start();
36
37         try {
38             System.out.println("等待2个子线程执行完毕...");
39             latch.await();
40             System.out.println("2个子线程已经执行完毕");
41             System.out.println("继续执行主线程");
42         } catch (InterruptedException e) {
43             e.printStackTrace();
44         }
45     }
46
47
48 }

执行结果如下:

1 子线程Thread-0正在执行
2 等待2个子线程执行完毕...
3 子线程Thread-1正在执行
4 子线程Thread-0执行完毕
5 子线程Thread-1执行完毕
6 2个子线程已经执行完毕
7 继续执行主线程

Semaphore

计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。

Semaphore中管理着一组虚拟的许可,许可的初始数量可通过构造函数来指定,在执行操作时可以首先获得许可,并在使用以后释放许可。如果没有一个许可给信号量,那么acquire将阻塞直到有许可(或者直到被中断或者操作超时)。release方法将返回一个许可给信号量。计算信号量的一种简化形式是二值信号量,即初始化为1的Semaphore。二值信号量可以用作互斥体(mutex),并具备不可重入的加锁语义:谁拥有这个唯一的许可,谁就拥有了互斥锁。

应用场景:

1.Semaphore可以用于实现资源池,例如数据库连接池。可以构造一个固定长度的资源池。将Semaphore的计数值初始化为池的大小,并从池中获取一个资源之前首先调用acquire方法获取一个许可,在将资源返回给池之后调用release释放许可,那么acquire将一直阻塞直到资源池不为空。

2.使用Semaphore将任何一种容器变成有界阻塞容器。信号量的计数值会初始化为容器的最大值。add操作在向底层容器添加一个元素之前,首先要获取一个许可。如果add操作没有添加任何元素,那么会立刻释放许可。同样,remove操作释放一个许可,使更多的元素能够添加到容器中。

 1 import java.util.Collections;
 2 import java.util.HashSet;
 3 import java.util.Set;
 4 import java.util.concurrent.Semaphore;
 5
 6 /**
 7  * 信号量实现有界阻塞容器
 8  *
 9  * @author bridge
10  */
11 class BoundedHashSet<t> {
12     private final Set<t> set;
13     private final Semaphore sem;
14
15     public BoundedHashSet(int bound) {
16         this.set = Collections.synchronizedSet(new HashSet<t>());
17         sem = new Semaphore(bound);
18     }
19
20     public boolean add(T o) throws InterruptedException {
21         //获取许可
22         sem.acquire();
23         boolean wasAdded = false;
24
25         try {
26             wasAdded = set.add(o);
27             return wasAdded;
28         } finally {
29             //如果添加失败,释放许可
30             if (!wasAdded)
31                 sem.release();
32         }
33
34     }
35
36     public boolean remove(Object o) {
37
38         boolean wasRemoved = set.remove(o);
39         //如果移除成功则释放许可
40         if (wasRemoved)
41             sem.release();
42         return wasRemoved;
43     }
44
45 }
46
47 public class BoundedHashSetTest {
48
49     public static void main(String[] args) {
50         final BoundedHashSet<string> set = new BoundedHashSet<string>(5);
51         new Thread() {
52             @Override
53             public void run() {
54                 System.out.println("添加");
55                 try {
56                     set.add("A");
57                     set.add("B");
58                     set.add("C");
59                     set.add("D");
60                     set.add("E");
61                     set.add("F");
62                     System.out.println("添加完毕");
63                 } catch (InterruptedException e) {
64                     e.printStackTrace();
65                 }
66             }
67         }.start();
68         try {
69             Thread.sleep(2000);
70         } catch (InterruptedException e) {
71             e.printStackTrace();
72         }
73         new Thread() {
74             @Override
75             public void run() {
76                 System.out.println("移除");
77                 set.remove("A");
78 //                set.remove("B");
79 //                set.remove("F");
80                 System.out.println("移除完毕");
81             }
82         }.start();
83     }
84 }
85 

运行结果

添加

移除
移除完毕
添加完毕
 
 

CyclicBarrier

栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。栅栏用于实现一些协议,例如几个家庭决定在某个地方集合:”所有人6:00在麦当劳碰头,到了以后要等待其他人,之后再讨论下一步要做的事情。”

CyclicBarrier可以使一定数量的参与方反复地在栅栏位置汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便下次使用。如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏就被认为是打破了,所有阻塞的await调用都将终止并抛出BrokenBarrierExcepiton。

如果成功地通过栅栏,那么await将为每个线程返回一个唯一的到达索引号,我们可以利用这些索引号来“选举”产生一个领导线程,并在下一次迭代中由该领导线程执行一些特殊的工作。

CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会(在一个子任务线程中)执行它,但在阻塞线程被释放之前是不能执行的。

下面的例子展示了CyclicBarrier的使用。

 1 import java.util.concurrent.BrokenBarrierException;
 2 import java.util.concurrent.CyclicBarrier;
 3
 4 /**
 5  * @author bridge
 6  */
 7 public class CyclicBarrierTest {
 8
 9     public static void main(String[] args) {
10         int N = 4;
11         CyclicBarrier cyclicBarrier = new CyclicBarrier(N);
12         for (int i = 0; i < N; i++) {
13             new Player(cyclicBarrier).start();
14         }
15
16     }
17
18
19 }
20  

21 class Player extends Thread {
22
23     private CyclicBarrier cyclicBarrier;
24
25     public Player(CyclicBarrier cyclicBarrier) {
26         this.cyclicBarrier = cyclicBarrier;
27     }
28
29     @Override
30     public void run() {
31         try {
32             Thread.sleep((long) (5000 * Math.random()));
33             System.out.println(Thread.currentThread().getName() + "准备好了!");
34             cyclicBarrier.await();
35         } catch (InterruptedException e) {
36             e.printStackTrace();
37         } catch (BrokenBarrierException e) {
38             e.printStackTrace();
39         }
40         System.out.println(Thread.currentThread().getName() + " 起跑!");
41     }
42 }

  

执行结果

Thread-1准备好了!
Thread-3准备好了!
Thread-2准备好了!
Thread-0准备好了!
Thread-0 起跑!
Thread-1 起跑!
Thread-2 起跑!
Thread-3 起跑!

原文地址:https://www.cnblogs.com/linghu-java/p/9023428.html

时间: 2024-11-10 14:58:28

java同步并发工具类CountDownLatch、CyclicBarrier和Semaphore的相关文章

Java并发工具类 - CountDownLatch

Java并发工具类 - CountDownLatch 1.简介 CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html 官方API. CountDownLatch能够使一个或多个线程等待其他线程完成各自的工作后再执行:CountDownLatch是JDK 5+里面

25.大白话说java并发工具类-CountDownLatch,CyclicBarrier,Semaphore,Exchanger

1. 倒计时器CountDownLatch 在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join方法,让主线程等待被join的线程执行完之后,主线程才能继续往下执行.当然,使用线程间消息通信机制也可以完成.其实,java并发工具类中为我们提供了类似"倒计时"这样的工具类,可以十分方便的完成所说的这种业务场景. 为了能够理解CountDownLatch,举一个很通俗的例子,运动员进行跑

1.3.4 并发工具类CountDownLatch/Semaphore/CyclicBarrier/FutureTask

CountDownLatch的2个用途: 1. 所有线程都到达相同的起跑线后,再一起开始跑(并非同时开始,而是队列中一个唤醒另一个)[此情况需到达起跑线后再调用await()等待其他线程] 2. 所有线程都到达终点(执行完)后,再一起庆祝 (并非同时开始,而是队列中一个唤醒另一个)[此情况需到达起终点后再调用await()等待其他线程] package com.study.concurrent_utils; import java.util.concurrent.CountDownLatch;

【死磕Java并发】-----J.U.C之并发工具类:CyclicBarrier

此篇博客所有源码均来自JDK 1.8 CyclicBarrier,一个同步辅助类,在API中是这么介绍的: 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用.因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier. 通俗点讲就是:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线

j.u.c系列(09)---之并发工具类:CyclicBarrier

写在前面 CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier. 注意比较CountDownLatch和CyclicBarrier: (01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行:而CyclicBarrier则是允许N个线程相互等待. (02) CountDownLatch的计数器无法被重置:

同步工具类 CountDownLatch 和 CyclicBarrier

在开发中,一些异步操作会明显加快执行速度带来更好的体验,但同时也增加了开发的复杂度,想了用好多线程,就必须从这些方面去了解 线程的 wait() notify() notifyall() 方法 线程异步返回 Future ThreadLocal 类 线程池 ThreadPoolExecutor 同步工具类 CountDownLatch,CyclicBarrier,Semaphore,Phaser,Exchanger 估计上面每一个对于 2~3 年的 java 同学来说都是恶梦,比较难以理解,本文

Java并发编程-线程的并发工具类

Fork-Join 什么是分而治之?规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解动态规范工作密取workStealing Fork/Join使用的标准范式 常用的并发工具类CountDownLatch作用:是一组线程等待其他的线程完成工作以后在执行,加强版joinawait用来等待,countDown负责计数器的减一CyclicBarrier让一组线程达到某个屏障,被阻塞,一直到组内最后一个线程达到屏

并发编程(2)--线程的并发工具类

1.线程的并发工具类 Fork-Join 什么是分而治之? 规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解 动态规范 工作密取 workStealing Fork/Join使用的标准范式 下面演示第一种用法:由于上下文切换的原因,所以性能上有可能不如单线程效果好. package com.xiangxue.ch2.forkjoin.sum; import java.util.Random; /** *

并发工具类:CountDownLatch、CyclicBarrier、Semaphore

在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch.CyclicBarrier.Semaphore. 一.CountDownLatch 1 import java.util.concurrent.CountDownLatch; 2 3 4 public class CountDownLatchTest 5 { //设置N为2 6 static CountDownLatch c = new CountDownLatch(2); 7 p