Java并发编程之——同步器工具类(semaphores, barriers, CountDownLatch,exchangers)
Semaphore:
类java.util.concurrent.Semaphore提供了一个计数信号量,从概念上讲,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。调用acquire()时无法保持同步锁,因为这会阻止将数据项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。将信号量初始化为1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。这通常也称为二进制信号量,因为它只能有两种状态。按此方式使用,二进制信号量具有某种属性(与很多Lock实现不同),即可以由线程释放“锁”,而不是由所有者(因为信号量没有所有权的概念)。在某些专门的上下文(如死锁恢复)中这会很有用。
Semaphore可以实现信号灯,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数
Semaphore sp = new Semaphored(个数);
sp.availablePermits()当前可获得个数
sp.acquire();取得信号灯
sp.release();释放信号灯
1 import java.util.concurrent.ExecutorService;
2 import java.util.concurrent.Executors;
3 import java.util.concurrent.Semaphore;
4
5 public class SemaphoreTest {
6
7 /**
8 * @param args
9 */
10 public static void main(String[] args) {
11 ExecutorService service = Executors.newCachedThreadPool();
12 //最多3个许可,也就最多有3个线程同时访问
13 final Semaphore sp = new Semaphore(3);
14 for(int i=0;i<10;i++){
15 Runnable runable = new Runnable() {
16 @Override
17 public void run() {
18 try{
19 //取得sq许可
20 sp.acquire();
21 }catch (Exception e) {
22 e.printStackTrace();
23 }
24 System.out.println(Thread.currentThread().getName()+"进入");
25 System.out.println("当前还有"+(3-sp.availablePermits())+"并发");
26 //释放许可
27 sp.release();
28 }
29 };
30 service.execute(runable);
31
32 }
33 service.shutdown();
34 }
35
36 }
Barrier
在实际应用中,有时候需要多个线程同时工作以完成同一件事情,而且在完成过程中,往往会等所有线程都到达某一个阶段后再统一执行。这是CyclicBarrier很有用。它允许一组线程互相等待,直到到达某个公共屏障点。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时CyclicBarrier很有用。因为该barrier在释放等待线程后可以重用,所以称它为循环的barrier.它重要的属性就是参与者个数,另外最重要的方法是await(),当所有线程都调用了await()后,就表示这些线程可以继续执行,否则就会等待。CyclicBarrier支持一个可选 的Runnable命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作有用。
CyclicBarrier:表示大家彼此等待,大家集合好后才开始出发,就是一定数组的线程在一些集合点汇合,只有所有线程都执行到后才开始下一步。
CyclicBarrier cb = new CyclicBarrier(个数3);
cb.await();
cb.await(); cb.getNumberWaiting();
cb.await();
1 public class CyclicBarrierTest {
2
3 public static void main(String[] args) throws IOException, InterruptedException {
4 CyclicBarrier barrier = new CyclicBarrier(3);
5
6 ExecutorService executor = Executors.newFixedThreadPool(3);
7 executor.submit(new Thread(new Runner(barrier, "1号选手")));
8 executor.submit(new Thread(new Runner(barrier, "2号选手")));
9 executor.submit(new Thread(new Runner(barrier, "3号选手")));
10
11 executor.shutdown();
12 }
13 }
14
15 class Runner implements Runnable {
16 // 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
17 private CyclicBarrier barrier;
18
19 private String name;
20
21 public Runner(CyclicBarrier barrier, String name) {
22 super();
23 this.barrier = barrier;
24 this.name = name;
25 }
26
27 @Override
28 public void run() {
29 try {
30 Thread.sleep(1000 * (new Random()).nextInt(8));
31 System.out.println(name + " 准备好了...");
32 // barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
33 barrier.await();
34 } catch (InterruptedException e) {
35 e.printStackTrace();
36 } catch (BrokenBarrierException e) {
37 e.printStackTrace();
38 }
39 System.out.println(name + " 起跑!");
40 }
41 }
CountDownLatch
在完成一组正在其他线程 中执行的操作之前,它允许一个或多个线程一直等待。用给定的数字作为计数器初始化CountDownLatch,一个线程调用await()方法后,在当前计数到达零之前,会一直受阻塞。其它线程调用countDown()方法,会使计数器减一,所以,计数器的值 为0后,会释放所有等待的线程。其它后续的await()调用都将立即返回。这种现象只出现一次,因为计数无法被重置。如果需要重置计数,请考虑使用CyclicBarrier.
CountDownLatch作为一个通用同步工具,有很多用途。使用1初始化的CountDownLatch用作一个简单的开、关锁存器,或入口:在通过调用countDown()的线程打开入口前,所有调用await()的线程都一直在入口处等待。用N初始化的CountDownLatch可以使一个线程在N个线程完成某项操作之前一直等待,或者使其在某项操作完成N次之前一直等待。
CountDownLatch:犹如倒计时器计数器,调用CountDownLatch对象的countDown
方法就将计算器减1,当计数到达0时,则所有等待者或单个等待者开始执行。
CountDownLatch cdOrder = new CountDownLatch(3);
Exchanger
java.util.concurrent.Exchanger提供了一个同步点,在这个同步点,一对线程可以交换数据。每个线程通过exchange()方法的入口提供数据给他的伙伴线程,并接收他的伙伴线程提供的数据,并返回。
Exchager用于实现两个人之间的数据交换,每个人在完成一定的事务后与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿到数据到来时,才能彼此交换数据


浙公网安备 33010602011771号