CountDownLatch、CyclicBarrier、Semaphore、Condition以及AbstractQueuedSynchronizer

本文详细介绍了Java并发编程中常用的三个工具类:CountDownLatch、CyclicBarrier和Semaphore,以及它们在实际场景中的应用。通过实例展示了如何使用它们来协调多线程的执行。此外,还深入探讨了AQS(AbstractQueuedSynchronizer)在这些工具类中的核心作用,包括state变量、阻塞队列和获取/释放方法的实现。通过对AQS的理解,读者能够更好地掌握Java并发编程的底层机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CountDownLatch倒数门栓

CountDownLatch倒数门栓,它允许一个或多个线程等待,直到在其他线程执行的一组操作完成为止。

 CountDownLatch(int count): CountDownLatch类只有一个构造函数,count为倒数的数值。
 await(): 调用await()方法的线程会被挂起,此线程会等待直到count值为0才继续执行。
 countDown(): 将count值减一,直到为0时,等待的线程才会被唤起。

/**
 * CountDownLatch用法一:一个线程等待多个线程都执行完毕,再继续自己的工作
 * 例如嫦娥五号发射前要进行各项检查,检查完毕后发射
 */
class CountDownLatchDemo01{
    public static void main(String[] args) throws InterruptedException {
        /**
         * 倒数计数器设置为3
         */
        CountDownLatch latch = new CountDownLatch(3);

        System.out.println("嫦娥五号进入发生塔,请各单位开始发射前检查");

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"检查完毕");

                /**
                 * 执行countDown()方法,latch的计数器减一
                 */
                latch.countDown();

            }, "单位"+i).start();
        }

        /**
         * 等待各单位检查完毕,即latch计数器为0
         */
        latch.await();

        System.out.println("发射,飞向月球");
    }
}

CyclicBarrier循环栅栏

CyclicBarrier可以构造一个栅栏,当一组线程执行到栅栏处,会被栅栏阻挡,栅栏打开,这组线程统一执行。打个比方,运动员们走上起跑线后等待,裁判鸣枪,运动员起跑。裁判鸣枪就有点类似栅栏。CyclicBarrier与CountDownLatch的不同之处是,CountDownLatch的计数器倒数至0后,就不能再次使用了,但CyclicBarrier没有倒数计数器,是可以重复使用的。

/**
 * 运动员起跑的例子
 * 为了演示CyclicBarrier可以重复使用的特性,这例子有点特殊之处。
 * 有10个运动员走上跑道,只要有5个运动员准备完毕,裁判就会扣响发令枪,这5个运动员起跑。
 * 再有5个运动员准备完毕,裁判还能扣响发令枪,5个运动员起跑。
 */
class CyclicBarrierDemo{

    public static void main(String[] args) {
        /**
         * 构造一个循环栅栏,类似裁判。
         * 有5个运动员准备完毕,裁判便会鸣枪
         */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            System.out.println("########裁判扣响发令枪");
        });

        /**
         * 构造10个运动员
         */
        for (int i = 0; i < 10; i++) {
            new Thread(new Task(cyclicBarrier), "运动员"+i).start();
        }
    }

    static class Task implements Runnable{
        private CyclicBarrier cyclicBarrier;

        public Task(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                TimeUnit.MILLISECONDS.sleep(new Random().nextInt(5000));
                System.out.println(Thread.currentThread().getName()+"准备完毕");
                /**
                 * 等待裁判鸣枪
                 */
                cyclicBarrier.await();

                System.out.println(Thread.currentThread().getName()+"起跑");
            }catch (BrokenBarrierException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Semaphore计数信号量

Semaphore的作用是维护一个“许可证”的计数,线程可以获取许可证,许可证数量减一;线程也可以释放许可证,许可证数量加一,当Semaphore的许可证数量减为0,下一个想要获取许可证的线程就需要等待,直到有另外的线程释放了许可证。Semaphore并不要求获取许可证的线程释放许可证,A线程获取一个许可证,B线程释放许可证也是可以的。

Semaphore重要方法介绍

    Semaphore(int permits),permits是许可证数量,即可同时运行的线程数。
    new Semaphore(int permits, boolean fair),fair是否使用公平策略。
    tryAcquire(),尝试获取一个许可,获取成功返回true,获取失败返回false。
    tryAcquire(timeout),和tryAcquire()一样,但是多了一个超时时间,指定时间内未获取到许可证就返回false
    acquire(),获取许可证,获取不到就阻塞,可以被中断
    acquireUninterruptibly(),和acquire()一样,但是不能响应中断
    release(),归还许可证
    acquire(int n),一次可以拿多个许可证
    release(int n),一次释放多个许可证。如无特殊需求,获取和释放的数量要一致

/**
 * 使用Semaphore模拟银行窗口办理业务
 */
class SemaphoreDemo{
    /**
     * 创建有3个许可证的Semaphore。类似银行有3个窗口,只能同时办理3个人的业务
     */
    static Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    /**
                     * acquire()获取许可证
                     */
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName()+"去窗口办理业务");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5)+1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    /**
                     * release()释放许可证
                     */
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+"业务办理完毕,离开");
                }
            }, "排队的人"+i).start();
        }
    }
}

Condition

如果说Lock用来替代synchronized,那么Condition就是用来替代Object.wait()/notify()。Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify();

/**
 * Condition实现生产者消费者模式
 */
class ConditionDemo {

    private int queueSize = 100;
    private PriorityQueue<Integer> queue = new PriorityQueue<>();
    private Lock lock = new ReentrantLock();
    /**
     * ReentrantLock可以创建多个Condition条件
     */
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public static void main(String[] args) {
        ConditionDemo condition02 = new ConditionDemo();
        Consumer consumer = condition02.new Consumer();
        Producer producer = condition02.new Producer();

        producer.start();
        consumer.start();
    }

    class Consumer extends Thread{
        @Override
        public void run() {
            consume();
        }

        private void consume(){
            while (true){
                lock.lock();
                try {
                    if (queue.size() ==0){
                        System.out.println("队列空,等待生产数据");
                        notEmpty.await();
                    }
                    Integer e = queue.poll();
                    System.out.println("消费数据 "+e+" ,唤醒生产者");
                    notFull.signalAll();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }finally{
                    lock.unlock();
                }
            }
        }
    }

    class Producer extends Thread{
        @Override
        public void run() {
            produce();
        }

        private void produce(){
            while (true){
                lock.lock();
                try {
                    if (queue.size() == queueSize){
                        System.out.println("队列满了,等待消费数据");
                        notFull.await();
                    }
                    int i = new Random().nextInt(100);
                    queue.add(i);
                    System.out.println("生产数据 "+i+" ,唤醒消费者");
                    notEmpty.signalAll();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }finally{
                    lock.unlock();
                }
            }
        }
    }
}

AQS

AQS全称AbstractQueuedSynchronizer,是一个用于构建锁、同步器、协作类的工具类(框架)。有了AQS,构建线程协作类就容易多了。

Semaphore、CountDownLatch、ReentrantLock都使用到了AbstractQueuedSynchronizer,这些类中都有一个内部类Sync,Sync继承了AbstractQueuedSynchronizer。CyclicBarrier使用ReentrantLock实现,也间接地用到了AbstractQueuedSynchronizer。

AQS三大核心:

1、private volatile int state;

state的具体含义,会根据具体实现类的不同而不同。
比如在Semaphore中,state表示剩余的许可证数量。
而在CountDownLatch中,state表示倒数计数器。
在ReentrantLock中表示锁的重入次数。
state为0表示锁不被任何线程占有。
state会被并发地修改,所有修改state的方法都要保证线程安全,比如getState()、setState(int newState)、compareAndSetState(int expect, int update)。

2、阻塞队列

这个队列用来存放“等待的线程”,当线程拿不到锁而阻塞时,将这些线程放到FIFO阻塞队列中。队列使用Node类构建,部分源码如下:

static final class Node {
    /**
     * 前一个节点
     */
    volatile Node prev;

    /**
     * 下一个节点
     */
    volatile Node next;

    /**
     * 等待的线程
     */
    volatile Thread thread;

    /**
     * 队列头结点
     */
    private transient volatile Node head;

    /**
     * 队列的尾部节点
     */
    private transient volatile Node tail;
}

3、获取、释放方法。

这类方法由线程协作工具类自己实现,并且含义各不相同。

在Semaphore中,获取是acquire方法,获取一个许可证,释放是release方法,释放一个许可证。

在CountDownLatch中,获取是await方法,等待倒数结束,释放是countDown方法,计数器减一。

在ReentrantLock中,获取是lock方法,获取锁,释放是unlock方法,释放锁。

使用AQS构建自己的线程协作类一般有三步:

1、线程协作类内部写一个Sync类继承AbstractQueuedSynchronizer。

2、独占锁重写tryAcquire(int arg)、tryRelease(int arg);共享锁重写tryAcquireShared(int acquires)、tryReleaseShared(int release)

3、线程协作类中写获取、释放方法,并调用重写的方法。

AQS在CountDownLatch中的应用

CountDownLatch构造函数源码解析

    /**
     * CountDownLatch构造函数
     * 内部类Sync继承了AbstractQueuedSynchronizer
     * 创建Sync对象,赋值给CountDownLatch的sync属性
     * 倒数计数器值count设置给AbstractQueuedSynchronizer的state属性
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

await()方法源码解析

调用了CountDownLatch重写的tryAcquireShared(int acquires)

    protected int tryAcquireShared(int acquires) {
        // AQS的state为0返回1,不为0返回-1
        return (getState() == 0) ? 1 : -1;
    }

state != 0时,tryAcquireShared(int acquires)返回结果小于0,则会调用AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(),此方法做了两件事:

1、用当前线程创建一个Node对象形成队列,当前的线程便进入了队列中。

2、调用AbstractQueuedSynchronizer.parkAndCheckInterrupt()阻塞当前线程。

await()方法调用栈如下:

parkAndCheckInterrupt:837, AbstractQueuedSynchronizer (java.util.concurrent.locks)
doAcquireSharedInterruptibly:997, AbstractQueuedSynchronizer (java.util.concurrent.locks)
acquireSharedInterruptibly:1304, AbstractQueuedSynchronizer (java.util.concurrent.locks)
await:231, CountDownLatch (java.util.concurrent)

countDown()方法源码解析

调用了CountDownLatch重写的tryReleaseShared(int releases)

    protected boolean tryReleaseShared(int releases) {
        // 自旋
        for (;;) {
            int c = getState();
            // 如果state == 0,直接返回
            if (c == 0)
                return false;
            
            // 使用CAS将state值减一,如果state-1==0,最后一次倒数结束,则返回true
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

tryReleaseShared(int releases)返回true,则会调用AbstractQueuedSynchronizer.doReleaseShared()在此方法中唤醒队列中等待的线程。

AQS在Semaphore中的应用

Semaphore的构造函数有两个,Semaphore(int permits)、Semaphore(int permits, boolean fair)可设置公平性与非公平性。Semaphore内部同样有Sync内部类,并且还有FairSync、NonfairSync这两个类用于实现获取许可证的公平与非公平性。

acquire()方法源码解析

此方法调用tryAcquireShared(int acquires)方法,FairSync、NonfairSync的tryAcquireShared(int acquires)方法的差异是FairSync的tryAcquireShared(int acquires)方法会判断阻塞队列是否有线程,若有,则返回-1。

    protected int tryAcquireShared(int acquires) {
        for (;;) {
            /**
             * FairSync的tryAcquireShared(int acquires)多了以下的判断
             * 如果队列中有排队线程,则返回-1
             * 有关公平性与非公平性可查看我这篇博客 https://blog.youkuaiyun.com/u010606397/article/details/109247667
             */
            if (hasQueuedPredecessors())
                return -1;

            
            // 在Semaphore中,state表示可用许可证的数量
            int available = getState();
            // 剩余数量 = 可用许可证数量 - 线程需要的许可证数量
            int remaining = available - acquires;
            // 如果剩余数量 < 0 意味着可用许可证数量不够,不去修改state的值,直接返回remaining
            // 如果剩余数量 >= 0 意味许可证数量够用,使用“CAS+自旋”修改state值
            if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                return remaining;
        }
    }

tryAcquireShared(int acquires)返回结果小于0,意味着当前线程没获取到许可证。执行AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(int arg),在此方法中将线程加入阻塞队列中,并将当前线程阻塞。这部分的逻辑跟CountDownLatch的await()方法很类似。

release()方法源码解析

调动Semaphore.Sync.releaseShared(int arg)

    public final boolean releaseShared(int arg) {
        // 1、tryReleaseShared(arg)释放操作,即归还许可证
        if (tryReleaseShared(arg)) {
            // 2、唤醒阻塞队列中等待的线程
            doReleaseShared();
            return true;
        }
        return false;
    }

AQS在ReentrantLock中的应用

ReentrantLock构造函数

    public ReentrantLock() {
        // 创建非公平的同步控制类
        sync = new ReentrantLock.NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        // 创建公平或非公平的同步控制类
        sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
    }

lock()方法源码解析

这是获取方法,大概的逻辑是判断state是否为0,state == 0 即锁没被线程持有,state值增加,设置当前线程拥有锁。state != 0 则锁被其他线程持有,当前线程进入队列中排队。

unlock()方法源码解析

这是释放方法,大概的逻辑是,state值减少,当state==0 唤醒队列中等待的线程。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值