首先来看一下JUC包和类的整体框图:
其中JUC包含五个大的包:
- 线程执行器Executor
- 锁locks
- 原子变量类atomic
- 并发工具类tools
- 并发集合collections
针对上面五个包,之前已经介绍过并发集合和Executor线程执行类,本文主要介绍其他几个。
Tools
主要是下面四个类考察的比较多,共同的作用是为了协助线程的同步:
- 闭锁 CountDownLatch
- 栅栏 CyclicBarrier
- 信号量 Semaphore
- 交换器 Exchanger
1、闭锁CountDownLatch
闭锁主要是让主线程等待一组事件发生后再继续执行,事件指的就是CountDownLatch里面的countDown方法。
countDown()方法将会被一个个线程去执行,执行结束之后越过CountDownLatch之后,子线程T1、T2、T3不会受到约束将会继续执行,主线程TA需要所有子线程都已经执行完countDown()方法之后才能继续执行。
那么如何判断所有线程都执行到了呢?
CountDownLatch()里面的一个cnt计数器,计数器的初始值就是线程的条数,每当一个线程执行过countDown()方法之后,就会减1,减到0的时候主线程才会继续推进执行。
下图是一个简单的demo:
2、栅栏 CyclicBarrier
可以做到的的是阻塞当前线程,等待其他线程,其中
- 等待其他线程,阻塞自己的当前线程,所有线程必须同时到达栅栏才能继续执行;
- 所有线程到达栅栏的时候,可以触发执行另外一个预先设置的触发器流程。
和CountDownLatch一样CyclicBarrier内部也有一个计数器,T1、T2、T3线程每次调用一个await(),计数器就会减1,而当计数器为0的时候主线程才会继续执行。
3、信号量 Semaphore
Semaphore的作用是可以控制一个资源最多被多少个线程同时访问,通过acquire去获取到一个许可,如果没有就等待,如果线程的任务完成就会执行release方法,去释放出一个许可出来让其他线程再获取到acquire许可使用资源。
下图是一个简单的demo:
4、交换器 Exchanger
在Exchanger中有一个同步点,在同步点中两个线程可以互相交换彼此的数据,当一个线程到达同步点之后就会阻塞等待之后另一个线程到达同步点之后开始交换数据。Exchanger只能用于两个线程之间交换数据。
下图是一个简单的demo:当使用exchange这个api的时候,交换的同步点就已经触发了。
Collections
之前我们讲过了ConcurrentHashMap,现在来讲另一个也很重要的队列BlockingQueue:
BlockingQueue:提供可阻塞的入队和出队操作
队列满了入队操作就会被阻塞直到有空间可以使用,如果队列空了出队操作就会被阻塞直到有元素可用。看看底层代码实现:
尝试往队尾中添加元素添加成功返回true,失败抛出异常。
offer作用和add一样,不同的是添加失败返回false,此外还有另外一个重载的方法:
添加失败会等待一段时间。
put和以上两者作用相同,添加失败会不断的等待,直到可以添加。
和put对应,从队列头取出元素,队列为空就会一直等待直到有元素为止。
poll和offer的重载方法对应,也是从队列头取出元素,会等待一段时间。
获取当前队列剩余的可存储元素的数量。
下面来说一下BlockingQueue的使用场景:
主要用于生产者-消费者模型,在多线程场景中,生产者线程在队列尾部添加元素而消费者线程则在队列头部消费元素,通过这种方式能够达到将任务的生产和消费进行隔离的目的。
BlockingQueue也是一个接口,有以下七种实现方式:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列;
- LinkedBlockingQueue:一个由链表结构组成的有界/无界阻塞队列;
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列;
- DealyQueue:一个使用优先级队列实现的无界阻塞队列;
- SynchronousQueue:一个不存储元素的阻塞队列;
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列;
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。