扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,即可关注微信公众号,阅读更多Spring源码分析
和Java并发编程
文章。
方法名 | 方法作用 |
---|---|
void await() | 让调用该方法的线程阻塞,当CountDownLatch的计数器减为0时,才会让线程解阻塞 |
boolean await(long timeout, TimeUnit unit) | 让调用该方法的线程超时阻塞,如果超过了指定的时间,CountDownLatch的计数器还没有减为0,那么线程就会直接返回 |
void countDown() | 让CountDownLatch的计数器减1,当计数器的值减为0时,会让阻塞在CountDownLatch的线程解阻塞 |
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(6);
List<String> teachers = Arrays.asList("语文老师","数学老师","英语老师","物理老师","化学老师","生物老师");
Random random = new Random();
// 创建6个线程,模拟6个科目的老师同时开始阅卷
List<Thread> threads = new ArrayList<>(6);
for (int i = 0; i < 6; i++) {
threads.add(new Thread(()->{
try {
int workTime = random.nextInt(6) + 1;
// 让线程睡眠一段时间,模拟老师的阅卷时间
Thread.sleep(workTime * 1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "阅卷完成");
// 每位老师阅卷完成后,就让计数器减1
countDownLatch.countDown();
},teachers.get(i)));
}
for (Thread thread : threads) {
thread.start();
}
// 让主线程等待所有老师阅卷完成后,再开始计算总分,进行排名
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有科目的老师均已阅卷完成");
System.out.println("开始计算总分,然后排名");
}
}
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
}
public final boolean releaseShared(int arg) {
// 尝试释放共享锁
if (tryReleaseShared(arg)) {
/**
* 当释放锁完成后,同步状态state=0,此时说明后面的线程可以获取锁了
* 如果此时同步队列中有人的等待,就唤醒后面的线程
* 如果无人等待,就将首节点的waitStatus设置为-3,表示同步状态可以无条件的传播下去,即后面的线程都可以直接获取锁了
*/
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
// 将同步状态进行减一,减一之后,同步状态变为0,就返回true,表示可以唤醒同步队列中正在等待的线程了
for (;;) {
int c = getState();
// 在对state进行减一操作之前,会先判断一下state的值是否为0,如果state已经为0了,此时还有线程来对state进行减1,这个时候是不正常的操作,因此会返回false
if (c == 0)
return false;
int nextc = c-1;
// 利用CAS操作来设置state的值
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 响应中断
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared()方法是尝试获取锁
// 对于CountDownLatch而言,当state=0时,会返回1,这表示锁被所有的线程都释放了,当state不等于0时,会返回-1,表示还有线程持有锁
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
await(long timeout, TimeUnit unit)
来阻塞线程,因为如果处理任务的子线程一直不执行完,就会一直不调用countDown()方法,这样计数器就不会减为0,导致主线程一直阻塞等待,什么也干不了,所以推荐使用await(long timeout, TimeUnit unit)。如果在具体业务中,必须要求所有线程执行完后再执行主线程的话,那就使用await()方法,不过在子线程处理任务的代码中,最好使用try…catch…finally语句,然后在finally语句块中进行countDown()调用,否则很容易给自己挖下深坑。吞掉
子线程的堆栈异常信息,最终导致什么错误日志也不打印。由于这段代码是在服务启动的时候就会执行,所以当时的现象就是,服务始终起不来,错误日志不也打印。当时碰到这个问题的时候,这段代码在线上已经运行了好久,只有在测试环境才出现,所以压根就没往这个地方去想。再加上自身对多线程相关的知识掌握度几乎为0,查了好久都没找到原因,服务器重启了n次,眼看重启大法不也好使了,只能向同事请教,最后终于找到了这个错误。最后的解决办法就是在子线程中使用try…catch…finally,然后在finally语句块中调用countDown()。这个问题其实用jstack命令,在服务器上看看线程的堆栈就能查到是哪儿出问题了,但还是因为自身很菜,对多线程没有足够的了解,才花费了很长时间去解决。也正是因为这次的教训,让我决定开始去学习并发相关的源码。