作用
CountDownLatch类似于计数器,主要场景为线程需要在单个或多个线程执行完毕后再执行,例如:榨西瓜汁需要等准备好西瓜和准备好榨汁机后才能执行
使用CountDownLatch之前需要先了解CAS、AQS的基本原理,参考CAS 以及 AQS 的实现原理
Demo
CountDownLatch内部维护了继承类AQS的类Sync,采用共享获取锁的机制,重写了tryAcquireShared、tryReleaseShared方法
// 覆盖在共享模式下尝试获取锁
protected int tryAcquireShared(int acquires) {
// 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1
return (getState() == 0) ? 1 : -1;
}
// 覆盖在共享模式下尝试释放锁
protected boolean tryReleaseShared(int releases) {
// 在for循环中Decrement count直至成功;
// 当状态值即count为0的时候,返回false表示 signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
// 让当前线程阻塞直到计数count变为0,或者线程被中断
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// count减1
public void countDown() {
sync.releaseShared(1);
}
/**
* CountDownLatch使用
* <p>
* 主线程:榨西瓜汁
* 子线程1:准备西瓜
* 子线程2:准备榨汁机
* 主线程只能等线程1和线程2都执行完后才能执行
*
* @author dkangel
*/
public class CountDownLatchDemo {
private static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private static CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) {
prepare("准备西瓜");
prepare("准备榨汁机");
try {
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程,榨汁");
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
}
private static void prepare(String description) {
executorService.execute(() -> {
System.out.println("子线程" + Thread.currentThread().getName() + "正在执行: " + description);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
// count递减
latch.countDown();
Thread.currentThread().interrupt();
});
}
}
CAS
Compare And Swap,即比较并换,是解决多线程情况下使用锁造成性能损耗的一种机制,CAS操作包含3个值,内存位置、预期原始值、新值,如果内存位置的值与预期原始值相同,则处理器将新值替换内存位置的旧值,如果不相同则不处理
在 Java 中,sun.misc.Unsafe
类提供了硬件级别的原子操作来实现这个 CAS,java.util.concurrent
包下的大量类都使用了这个Unsafe
类的 CAS 操作,如AtomicInteger
CAS优点:
- 相比Synchronized的悲观锁实现,CAS是乐观锁的实现方式,效率更高
- 线程不会应该获取不到锁而一直等待
CAS缺点
ABA问题
,原始值是A,被修改为B后又改为A,CAS检查的时候发现数据没被改过,其实是改过的。改进方法:可以添加版本号,1A 2B 3A循环时间开销大
,CAS 自旋如果长时间不成功,会给 CPU 带来非常大的执行开销只能保证一个共享变量的原子操作
,当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就需要用锁,取巧:从 Java 1.5 开始 JDK 提供了AtomicReference类来保证引用对象之间的原子性,我们可以把多个变量放在一个对象里来进行 CAS 操作
AQS
AQS(AbstractQueuedSynchronizer),即抽象队列同步器,是 JDK 下提供的一套用于实现基于 FIFO 等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类