目录
一、AQS
1. AQS
介绍:
AQS
(AbstractQueuedSynchronizer
),是并发容器J.U.C(java.lang.concurrent)
下locks
包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut
先进先出)的队列。底层实现的数据结构是一个双向列表。
2. AQS组件:
AQS组件包括:CountDownLatch
、Semaphore
、CyclicBarrier
3. AQS
的实现思路
AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。
4. AQS
设计思想
- 使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架。
- 利用int类型标识状态。在AQS类中有一个叫做state的成员变量。
源码:
/**
* The synchronization state.
*/
private volatile int state;
- 基于AQS有一个同步组件,叫做ReentrantLock。在这个组件里,stste表示获取锁的线程数,假如state=0,表示还没有线程获取锁,1表示有线程获取了锁。大于1表示重入锁的数量。
- 继承:子类通过继承并通过实现它的方法管理其状态(acquire和release方法操纵状态)。
- 可以同时实现排它锁和共享锁模式(独占、共享),站在一个使用者的角度,AQS的功能主要分为两类:独占和共享。它的所有子类中,要么实现并使用了它的独占功能的api,要么使用了共享锁的功能,而不会同时使用两套api,即便是最有名的子类ReentrantReadWriteLock也是通过两个内部类读锁和写锁分别实现了两套api来实现的;
二、CountDownLatch
组件
1. CountDownLatch
介绍
CountDownLatch
是一种java.util.concurrent
包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。CountDownLatch
是基于AbstractQueuedSynchronizer(AQS)
实现的,其通过state
作为计数器。构造CountDownLatch
时初始化一个state
,以后每调用countDown()
方法一次,state
减1;当state=0
时,唤醒在await()
上被挂起的线程。
2. CountDownLatch
的使用与分析
CountDownLatch countDownLatch= new CountDownLatch(N);
//构造对象时候 需要传入参数N;**countDownLatch.await()
能够阻塞线程 直到调用N次countDownLatch.countDown()
方法才释放线程;**countDownLatch.countDown()
可以在多个线程中调用 计算调用次数是所有线程调用次数的总和;**
总结来说,await()
后面的方法,需要在countDown()
方法中的计数器减到0
后,才允许被执行
示例源码:
@Slf4j
public class CountDownLatchExample1 {
private static final int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
//此处循环将200次,因此 countDownLatch.countDown()也执行了200次
for (int i=0; i<threadCount; i++){
final int threadNum = i;
executorService.execute(()->{
try {
test(threadNum);
}catch (Exception e){
log.error("exception",e);
}finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
log.info("finish!!");
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
Thread.sleep(100);
log.info("{}",threadNum);
Thread.sleep(100);
}
}
上述源码演示的是:在线程池中启动200个线程,每个线程都是执行test()
方法,期望在200个线程执行结束之后,打印“finish!!”
字样;
执行结果如下所示:
...
19:59:45.018 [pool-1-thread-175] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 174
19:59:45.018 [pool-1-thread-176] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 175
19:59:45.018 [pool-1-thread-177] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 176
19:59:45.018 [pool-1-thread-153] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 152
19:59:45.018 [pool-1-thread-154] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 153
19:59:45.018 [pool-1-thread-155] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 154
19:59:45.018 [pool-1-thread-157] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 156
19:59:45.018 [pool-1-thread-158] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 157
19:59:45.018 [pool-1-thread-159] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 158
19:59:45.135 [main] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - finish!!
Process finished with exit code 0
尽管200个线程执行顺序是交叉的,但是最终输出结果的最后一行是finish!!
,与期望的一致;
2.1 代码证明CountDownLatch
能它允许一个或多个线程等待直到在其他线程中一组操作执行完成。
通过注释掉countDownLatch.await();
语句,那么finsh!!
日志内容,将会出现任何位置,而不会绝对仅仅出现在末尾。示例代码如下:
@Slf4j
public class CountDownLatchExample1 {
private static final int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i=0; i<threadCount; i++){
final int threadNum = i;
executorService.execute(()->{
try {
test(threadNum);
}catch (Exception e){
log.error("exception",e);
}finally {
countDownLatch.countDown();
}
});
}
//注释掉下面一行代码
// countDownLatch.await();
log.info("finish!!");
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
Thread.sleep(100);
log.info("{}",threadNum);
Thread.sleep(100);
}
}
上述代码输出结果如下:
20:24:57.930 [main] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - finish!!
20:24:58.046 [pool-1-thread-9] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 8
20:24:58.045 [pool-1-thread-5] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 4
20:24:58.045 [pool-1-thread-4] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 3
20:24:58.045 [pool-1-thread-6] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 5
这是因为,输出finish!!
内容的主线程,与线程池中的200个线程,没有任何执行顺序的约束。
2.2 代码证明CountDownLatch
中计数器的存在
CountDownLatch countDownLatch= new CountDownLatch(N)
中N的值有多大,那么countDownLatch.countDown()
方法就必须执行多少次,否则无法执行countDownLatch.await();
,
将上述代码进行稍微修改,让for
中的少执行一次``方法,代码如下:
@Slf4j
public class CountDownLatchExample1 {
private static final int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
//此处循环将199次,因此 countDownLatch.countDown()也只会执行了199次
for (int i=0; i<threadCount-1; i++){
final int threadNum = i;
executorService.execute(()->{
try {
test(threadNum);
}catch (Exception e){
log.error("exception",e);
}finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
log.info("finish!!");
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
Thread.sleep(100);
log.info("{}",threadNum);
Thread.sleep(100);
}
}
执行结果如下:
...
20:07:56.069 [pool-1-thread-72] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 71
20:07:56.069 [pool-1-thread-73] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 72
20:07:56.069 [pool-1-thread-75] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 74
20:07:56.069 [pool-1-thread-78] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 77
20:07:56.069 [pool-1-thread-79] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 78
上述代码运行之后,将会一直处于阻塞状态,且并没有输出“finish!!”
日志内容。这是因为countDownLatch
中的计数器state
,一直无法减小到0
,因此无法执行countDownLatch.await();
后面的程序,
3. CountDownLatch的特殊使用
情景:如果线程池中有200个不同的线程同时在执行,需要在所有线程执行完后输出finish!!
日志,但是如果有的线程等待时间超过10毫秒,那么就跳过该线程,直接输出finish!!
日志。
针对上面的情景,可以将上述代码修改:
@Slf4j
public class CountDownLatchExample2 {
private static final int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i=0; i<threadCount; i++){
final int threadNum = i;
executorService.execute(()->{
try {
test(threadNum);
}catch (Exception e){
log.error("exception",e);
}finally {
countDownLatch.countDown();
}
});
}
//此处通过设置countDownLatch.await()内的参数,达到要求;
countDownLatch.await(10, TimeUnit.MILLISECONDS);
log.info("finish!!");
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
Thread.sleep(100);
log.info("{}",threadNum);
}
}
可以通过设置countDownLatch.await()
内的参数,满足要求,其中第一个参数为需要等待的时间,第二个参数为时间单位。由于代码中每个线程执行时间都超过了100毫秒,所以最先输出finish!!
日志,结果如下:
20:47:17.659 [main] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - finish!!
20:47:17.723 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 0
20:47:17.727 [pool-1-thread-2] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 1
20:47:17.730 [pool-1-thread-4] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 3
20:47:17.730 [pool-1-thread-3] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 2
...
Java并发编程学习系列
如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)