在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并分析了CountDownLatch工具类的使用方式和源码实现,接下来我们继续学习CyclicBarrier,Semaphore,Exchanger的源码实现。

1. CyclicBarrier(栅栏)

  1.1 CyclicBarrier的使用方式

   在实际的并发中,我们可能需要等待所有线程到达一个数量点之后,才会去继续执行线程,这个点就是栅栏,比如开会的场景,我们应该要做的是应该等到所有与会人员都到齐之后,我们才开始开会,使用CyclicBarrier就可以很容易的实现这么一个场景,示例源码:

  1. package com.company.thread.t4;
  2.  
  3. import java.util.Random;
  4. import java.util.concurrent.BrokenBarrierException;
  5. import java.util.concurrent.CyclicBarrier;
  6.  
  7. public class CyclicBarrierDemo {
  8.  
  9. public void meeting(CyclicBarrier cyclicBarrier) {
  10. Random random = new Random();
  11.  
  12. try {
  13. Thread.sleep(random.nextInt(4000));
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. // if (Thread.currentThread().getName().equals("Thread-1")) {
  18. // throw new RuntimeException("异常了");
  19. // }
  20. System.out.println(Thread.currentThread().getName() + "进入会议室");
  21. try {
  22. cyclicBarrier.await();
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. } catch (BrokenBarrierException e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println(Thread.currentThread().getName() + "开始汇报....");
  29. }
  30.  
  31. public static void main(String[] args) {
  32.  
  33. final CyclicBarrierDemo cyclicBarrierDemo = new CyclicBarrierDemo();
  34. final CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
  35. @Override
  36. public void run() {
  37. System.out.println(Thread.currentThread().getName() + "开始开会...");
  38. }
  39. });
  40.  
  41. for (int i = 0; i < 10; i++) {
  42. new Thread(new Runnable() {
  43. @Override
  44. public void run() {
  45. cyclicBarrierDemo.meeting(cyclicBarrier);
  46. }
  47. }).start();
  48. }
  49. }
  50. }

  meeting()方法传入CyclicBarrier对象,调用await()方法等待,直到达到初始化CyclicBarrier对象是传入的值时才会唤醒所有线程并发执行剩下的任务。

  main方法中构建等待的线程数为10个,并传入一个任务,该任务由最后进入的线程来执行,将cyclicBarrier传入到meetting中,此时执行结果为:

  1. Thread-9进入会议室
  2. Thread-6进入会议室
  3. Thread-5进入会议室
  4. Thread-4进入会议室
  5. Thread-3进入会议室
  6. Thread-1进入会议室
  7. Thread-8进入会议室
  8. Thread-7进入会议室
  9. Thread-2进入会议室
  10. Thread-0进入会议室
  11. Thread-0开始开会...
  12. Thread-0开始汇报....
  13. Thread-9开始汇报....
  14. Thread-6开始汇报....
  15. Thread-4开始汇报....
  16. Thread-5开始汇报....
  17. Thread-3开始汇报....
  18. Thread-1开始汇报....
  19. Thread-7开始汇报....
  20. Thread-2开始汇报....
  21. Thread-8开始汇报....

   执行结果显示,只有当是个线程都进入等待时,才会执行我们在构造CyclicBarrier对象时传入的任务,此任务就相当于是一个栅栏点,达到线程等待数量后,唤醒所有线程去继续执行,CyclicBarrier的使用方式就如上代码所以,需要注意的是,如果执行任务期间有一个线程出现异常,导致无法达到栅栏的点,那么所有线程都加会一直阻塞。

  1.2 CyclicBarrier源码实现

  首先看CyclicBarrier的构造方法:

  1. public CyclicBarrier(int parties, Runnable barrierAction) {
  2. if (parties <= 0) throw new IllegalArgumentException();
  3. this.parties = parties;
  4. this.count = parties;
  5. this.barrierCommand = barrierAction;
  6. }

  构造函数就是执行了一个赋值的操作,将我们需要等待的线程个数进行初始化,然后看核心的方法await(),

  await():

  1. public int await() throws InterruptedException, BrokenBarrierException {
  2. try {
  3. return dowait(false, 0L);
  4. } catch (TimeoutException toe) {
  5. throw new Error(toe); // cannot happen;
  6. }
  7. }

  直接放回了dowait()方法,继续进入dowait()方法,

 dowait():

  1. private int dowait(boolean timed, long nanos)
  2. throws InterruptedException, BrokenBarrierException,
  3. TimeoutException {
  4. final ReentrantLock lock = this.lock;
  5. lock.lock();
  6. try {
  7. final Generation g = generation;
  8.  
  9. if (g.broken)
  10. throw new BrokenBarrierException();
  11.  
  12. if (Thread.interrupted()) {
  13. breakBarrier();
  14. throw new InterruptedException();
  15. }
  16.  
  17. int index = --count;
  18. if (index == 0) { // tripped
  19. boolean ranAction = false;
  20. try {
  21. final Runnable command = barrierCommand;
  22. if (command != null)
  23. command.run();
  24. ranAction = true;
  25. nextGeneration();
  26. return 0;
  27. } finally {
  28. if (!ranAction)
  29. breakBarrier();
  30. }
  31. }
  32.  
  33. // loop until tripped, broken, interrupted, or timed out
  34. for (;;) {
  35. try {
  36. if (!timed)
  37. trip.await();
  38. else if (nanos > 0L)
  39. nanos = trip.awaitNanos(nanos);
  40. } catch (InterruptedException ie) {
  41. if (g == generation && ! g.broken) {
  42. breakBarrier();
  43. throw ie;
  44. } else {
  45. // We're about to finish waiting even if we had not
  46. // been interrupted, so this interrupt is deemed to
  47. // "belong" to subsequent execution.
  48. Thread.currentThread().interrupt();
  49. }
  50. }
  51.  
  52. if (g.broken)
  53. throw new BrokenBarrierException();
  54.  
  55. if (g != generation)
  56. return index;
  57.  
  58. if (timed && nanos <= 0L) {
  59. breakBarrier();
  60. throw new TimeoutException();
  61. }
  62. }
  63. } finally {
  64. lock.unlock();
  65. }
  66. }

  它是CyclicBarrier的核心方法,我们来一步步分析:

  第一步:首先获取可重入锁,判断线程是否被中断,如果被中断,调用beakBarrier()方法,generation.broken = true,还原count为初始值,并唤醒所有等待的线程。并抛出中断异常

  beakBarrier():

  1. private void breakBarrier() {
  2. generation.broken = true;
  3. count = parties;
  4. trip.signalAll();
  5. }

  否则将count减减,count减到最后一个线程进入时。使用ranAction判断是否执行,判断初始化时是否传入任务,传入则执行该任务的run方法,这里就是我们前面说得栅栏任务由最后一个进入的线程执行的实现,将ranAction置为true,nextGeneration()调用唤醒所有线程,并将所有参数重置为初始化状态。表示这一组任务已经等待完成并开始执行,

  nextGeneration()方法:

  1. private void nextGeneration() {
  2. // signal completion of last generation
  3. trip.signalAll();
  4. // set up next generation
  5. count = parties;
  6. generation = new Generation();
  7. }

  当index不等于0,表示不是最后一个线程进入时,自旋使线程等待,当g.broken = true时,表示线程出现过错误,执行breakBarrier();方法,否则就中断线程,接着就是校验线程是否正确执行和是否超时等的一些校验,dowait()方法源码说完了。

  主要的就是初始化一个状态,每次调用自减,跟直到状态为0时,唤醒所有的线程,这点于CountDownLatch()类似,但是CyclicBarrier是可重用的,当到达栅栏的所有线程被唤醒之后,它会将CyclicBarrier实例还原成初始状态,下一次可以继续使用。

2. Semaphore(信号量)

  信号量指当前空闲有三个资源,但是有一个栏杆一样的东西在拦截线程,当前面三个线程进来获取资源时,因为有资源所以允许三个线程进入,当资源被前面三个线程占用时,其他的线程就只能等待着正在使用资源的线程释放资源,栏杆才会随机的放一个线程进入,这样就达到了控制并发线程数量的一个目的;

  2.1 使用方式

  直接看代码:

  1. package com.company.thread.t4;
  2.  
  3. import java.util.concurrent.Semaphore;
  4.  
  5. public class SemaphoreDemo {
  6.  
  7. public void method (Semaphore semaphore) {
  8.  
  9. try {
  10. semaphore.acquire();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName() + "is running ");
  15. try {
  16. Thread.sleep(2000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. semaphore.release();
  21. }
  22.  
  23. public static void main(String[] args) {
  24. final SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
  25. final Semaphore semaphore = new Semaphore(10);
  26. for (int i = 0; i < 20; i++){
  27. new Thread(new Runnable() {
  28. @Override
  29. public void run() {
  30. semaphoreDemo.method(semaphore);
  31. }
  32. }).start();
  33. }
  34. }
  35. }

  method()方法中调用 semaphore.acquire();获取资源,当能获取到资源后继续执行,执行完成后调用 semaphore.release()方法释放资源,main方法中可以创建很多个线程,但是我们通过观察输出可以看到,每次都只能有是个线程在执行,这就是在初始化的时候控制了线程并发执行的数量只能是是个,所有多余的将会等待前面执行的线程释放资源,然后拿到资源,使用都是非常简单的,看示例源码来理解完全没有问题,接下来我们看看信号量的实现原理

 2.2 源码实现

  首先还是看构造方法:

  1. public Semaphore(int permits) {
  2. sync = new NonfairSync(permits);
  3. }

  可以看到它是实例化非公平内部同步器,并将信号量个数初始化,这里就不再深入,主要的方法是acquire()/release()方法,

  acquire()方法: 

  1. public void acquire() throws InterruptedException {
  2. sync.acquireSharedInterruptibly(1);
  3. }

  这里调用的AQS的共享模式的acquireSharedInterruptibly(1)对线程获取资源,由于AQS的模式主要是看自己实现的tryAcquireShared()方法,所以我们主要看tryAcquireShared()源码:

  1. protected int tryAcquireShared(int acquires) {
  2. return nonfairTryAcquireShared(acquires);
  3. }

  返回nonfairTryAcquireShared()的执行结果,

  nonfairTryAcquireShared()方法是操作AQS状态。

  1. final int nonfairTryAcquireShared(int acquires) {
  2. for (;;) {
  3. int available = getState();
  4. int remaining = available - acquires;
  5. if (remaining < 0 ||
  6. compareAndSetState(available, remaining))
  7. return remaining;
  8. }
  9. }

  返回资源数量,我们知道在AQS共享模式下,如果返回大于0时,它就可以去等待队列中唤醒线程,这里是自旋直到返回状态减acquires后的能拿到资源的状态,即是自旋一直等待其他线程释放资源,此时当前线程就可以拿到资源执行。

  release()方法:

  1. public void release() {
  2. sync.releaseShared(1);
  3. }

  也是使用AQS的releaseShared()方法,所以我们这里依然只看自己实现的tryReleaseShared()方法:

  1. protected final boolean tryReleaseShared(int releases) {
  2. for (;;) {
  3. int current = getState();
  4. int next = current + releases;
  5. if (next < current) // overflow
  6. throw new Error("Maximum permit count exceeded");
  7. if (compareAndSetState(current, next))
  8. return true;
  9. }
  10. }

  它的实现就是,当释放资源时,不断修改AQS的stats增加releases资源,这样当释放成功后,等待中的线程就有机会获取线程去执行了。

  理解了AQS的情况下,CyclicBarrier和信号量的实现其实也就不难理解了,都是继续AQS提供的模式重写tryXX方法来实现自己想要的功能。

3. Exchanger(数据交换)

  Exchanger是可以线程之间通信的,它可以使数据在两个线程直接交换,也就是A线程可以获取到B线程中的某些数据,B线程也可能可以获取到B线程的数据。

  3.1 使用方式

  直接上代码:

  1. package com.company.thread.t5;
  2.  
  3. import java.util.concurrent.Exchanger;
  4.  
  5. public class Demo {
  6.  
  7. public void a (Exchanger<String> exchanger) {
  8. System.out.println("a 方法执行");
  9.  
  10. try {
  11. Thread.sleep(2000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. String res = "12345";
  16.  
  17. try {
  18. String value = exchanger.exchange(res);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. public void b(Exchanger<String> exchanger) {
  24. System.out.println("b 方法开始执行");
  25.  
  26. try {
  27. Thread.sleep(4000);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. String res = "123456";
  32.  
  33. try {
  34. String value = exchanger.exchange(res);
  35. System.out.println("开始进行比对");
  36. System.out.println("比对结果为:" + value.equals(res));
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. }

  Exchanger是一个泛型类,他管理的是传入的类型的变量,示例中使用一个Exchanger实例,在a方法中调用exchange()将需要交换的数据出入,b也一样,创建两个线程,分别调用a,b方法,传入同一个Exchanger实例,这样当两个线程执行到一个零界点时,就会将两个线程中的传入的数据交换的两外的线程中,这样就可以在另外的方法或线程中拿到其他线程的某些我们需要的数据。jdk文档中介绍,它最常用的就是缓冲这一块,有兴趣可以去了解一下。Exchanger的使用也是非常简单的。

  3.2 Exchanger 源码

    exchange()核心方法:

  1. public V exchange(V x) throws InterruptedException {
  2. if (!Thread.interrupted()) {
  3. Object v = doExchange((x == null) ? NULL_ITEM : x, false, 0);
  4. if (v == NULL_ITEM)
  5. return null;
  6. if (v != CANCEL)
  7. return (V)v;
  8. Thread.interrupted(); // Clear interrupt status on IE throw
  9. }
  10. throw new InterruptedException();
  11. }

  简单明了的看到,它调用了doExchange()方法去执行了具体的改变操作,所以进入doExchange():

 
  1. private Object doExchange(Object item, boolean timed, long nanos) {
  2. Node me = new Node(item); // Create in case occupying
  3. int index = hashIndex(); // Index of current slot
  4. int fails = 0; // Number of CAS failures
  5.  
  6. for (;;) {
  7. Object y; // Contents of current slot
  8. Slot slot = arena[index];
  9. if (slot == null) // Lazily initialize slots
  10. createSlot(index); // Continue loop to reread
  11. else if ((y = slot.get()) != null && // Try to fulfill
  12. slot.compareAndSet(y, null)) {
  13. Node you = (Node)y; // Transfer item
  14. if (you.compareAndSet(null, item)) {
  15. LockSupport.unpark(you.waiter);
  16. return you.item;
  17. } // Else cancelled; continue
  18. }
  19. else if (y == null && // Try to occupy
  20. slot.compareAndSet(null, me)) {
  21. if (index == 0) // Blocking wait for slot 0
  22. return timed ?
  23. awaitNanos(me, slot, nanos) :
  24. await(me, slot);
  25. Object v = spinWait(me, slot); // Spin wait for non-0
  26. if (v != CANCEL)
  27. return v;
  28. me = new Node(item); // Throw away cancelled node
  29. int m = max.get();
  30. if (m > (index >>>= 1)) // Decrease index
  31. max.compareAndSet(m, m - 1); // Maybe shrink table
  32. }
  33. else if (++fails > 1) { // Allow 2 fails on 1st slot
  34. int m = max.get();
  35. if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1))
  36. index = m + 1; // Grow on 3rd failed slot
  37. else if (--index < 0)
  38. index = m; // Circularly traverse
  39. }
  40. }
  41. }

  它的核心算法是for(;;)中的内容,当slot为空时,创建一个Slot,不为空,则获取slot的value不为空,则表示已经有线程进来了,使用cas将当前线程的value与前一个进入的value进行交换,成功后唤醒前面进入之后等待的线程。当前y为空,则表示当前线程是第一个进来的线程,设置创建一个节点,将当前需要保存的value设置到slot中,以node的形式保存,当被唤醒时,则执行await()方法返回修改后的值,因为在上面代码中第二个进来的线程已经交换了两个需要交换的数据,所以当等待线程被叫醒后返回的被后一个线程交换后的数据。

  Java的并发工具类,到这里就算是学习了,理解不是很深刻,望各位各位多多指点,共同进步,谢谢!

原文 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理

并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理的更多相关文章

  1. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

  2. JUC并发编程学习笔记

    JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...

  3. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  4. 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理

    1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...

  5. 并发编程学习笔记(15)----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

  6. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  7. 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

  8. 并发编程学习笔记(5)----AbstractQueuedSynchronizer(AQS)原理及使用

    (一)什么是AQS? 阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架, ...

  9. 并发编程学习笔记(4)----jdk5中提供的原子类及Lock使用及原理

    (1)jdk中原子类的使用: jdk5中提供了很多原子类,它会使变量的操作变成原子性的. 原子性:原子性指的是一个操作是不可中断的,即使是在多个线程一起操作的情况下,一个操作一旦开始,就不会被其他线程 ...

随机推荐

  1. Objective-C语言的 if ( self = [super init] )

    我们先假设如今自己创建了个类.我们起名叫MyObject,继承于NSObject. 继承知道吧,就是你这个子类(MyObject)假设什么都不写的话,和父类(NSObject)就是一模一样的. OC里 ...

  2. Flex 页面启动事件

    事件启动顺序 容器Preinitialize=>子组件preinitialize=>子组件initialize=>childAdd=>initialize =>子组件cr ...

  3. iOS开发——swift——swift与OC之间不得不知道的21点

    swift与OC之间不得不知道的21点   自6月的WWDC大会上由苹果的大神Chris Lattner向我们首次展示swift至今已经大半年时间了,虽然绝大部分软件公司代码里还都见不到一丁点swif ...

  4. HDU4267 树状数组 不连续区间修改(三维)

    A Simple Problem with Integers                                  Problem Description Let A1, A2, ... ...

  5. 在64位的ubuntu 14.04 上开展32位Qt 程序开发环境配置(pro文件中增加 QMAKE_CXXFLAGS += -m32 命令)

    为了能中一个系统上开发64或32位C++程序,费了些周折,现在终于能够开始干过了.在此记录此时针对Q5.4版本的32位开发环境配置过程. 1. 下载Qt 5.4 的32位版本,进行安装,安装过程中会发 ...

  6. Linux/Android——Input系统之frameworks层InputManagerService (六)【转】

    本文转载自:http://blog.csdn.net/u013491946/article/details/72638954 版权声明:免责声明: 本人在此发文(包括但不限于汉字.拼音.拉丁字母)均为 ...

  7. 7-74 JavaScript 事件

    7-74 JavaScript 事件 学习要点 掌握常用的javaScript事件 基本概念 事件是一些特定动作发生时所发出的信号,JavaScript中的事件是可以被 JavaScript 侦测到的 ...

  8. 关于mysql的索引原理与慢查询优化

    大多情况下我们都知道加索引能提高查询效率,但是应该如何加索引呢?索引的顺序如何呢? 大家看一下下面的sql语句(在没有看下面的优化的方法之前)应该如何优化加索引以及优化sql语句: 1.select  ...

  9. 谷歌浏览器(Chrome)查看http报文headers信息

    转自:https://blog.csdn.net/floatdreamed/article/details/79208719 ①打开谷歌浏览器,随意输入要搜索的内容 ②按下F12键,此时会弹出浏览器的 ...

  10. CAS和Oauth2的区别

    CAS是单点登陆(SSO) Oauth2是对某种资源进行授权访问