CountDownLatch的使用

作用

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值来表示状态的同步器的基类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值