Java - JUC -常用的并发工具类

本文深入解析Java多线程中的Condition、CountDownLatch、Semaphore和CyclicBarrier等高级工具类,阐述它们的工作原理及应用场景,帮助开发者更好地理解和运用这些工具进行线程间的协调与控制。

Condition

Condition是JUC里面多线程协调通信的工具类,可以让一些线程一起等待条件即(Condition)。只有当满足条件时,线程才会被唤醒。

类似于 wait/notify 。在Condition中也有类似的实现。

public class ConditionAwait implements Runnable{

    private Lock lock;

    private Condition condition;

    public ConditionAwait(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        System.out.println("begin -- ConditionAwait start");
        lock.lock();
        try {
            //  阻塞挂起当前线程
            condition.await();
            System.out.println("end -- ConditionAwait");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

public class ConditionSignal implements Runnable{

    private Lock lock;

    private Condition condition;

    public ConditionSignal(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        System.out.println("begin -- condition signal");
        lock.lock();
        //  唤醒阻塞状态的线程  signal 唤醒一个  signal 唤醒所有
        condition.signal();
        System.out.println("end -- condition signal");
        lock.unlock();
    }
}

public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(new ConditionAwait(lock, condition)).start();
        new Thread(new ConditionSignal(lock, condition)).start();
    }


//  输出
begin -- ConditionAwait start
begin -- condition signal
end -- condition signal
end -- ConditionAwait

如代码示例,我们启动了两个线程。两个线程使用同一个Lock和Condition对象。通过代码的运行结果可以发现。当代码运行到condition.await()时,线程将会被挂起。等待另一个线程调用condition.signal()方法时,将会去唤醒被阻塞的线程。值得注意的时,阻塞在condition.await()方法处的线程,若没有condition.signal()方法和condition.signalAll()方法去唤醒,线程将一直处于被阻塞状态。

Condition源码分析

调用Lock.lock(),需要获得Lock锁,所以意味着会存在一个AQS同步队列。初始情况如下

线程A获得锁后,state状态由0更新到1。那么此时线程A调用了condition.await()方法后。会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。

await()方法

      public final void await() throws InterruptedException {
            //  表示调用await方法允许被中断
            if (Thread.interrupted())
                throw new InterruptedException();
            
            //  创建一个新的节点,节点状态为condition,该方法构造了一个新的 condition 队列
            //  这里的队列不是双向链表,而是单向链表
            Node node = addConditionWaiter();

            //  释放当前的锁,得到锁的状态,并从AQS队列中唤醒head节点的next节点的线程
            //  彻底释放锁
            int savedState = fullyRelease(node);

            
            int interruptMode = 0;

            //  判断当前节点是否在同步队列上,当还没有收到 signal 信号,则将当前线程阻塞
            while (!isOnSyncQueue(node)) {

                //  挂起当前线程
                LockSupport.park(this);

                //  
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

            //   当线程醒来,会尝试获取锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;

            //   清理 condition 队列上的 node
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

当addConditionWaiter()方法执行完毕,会构造一个 Condition 队列。此时线程A会被加入到condition队列中。直到被其它线程用signal唤醒,会重新加入到AQS队列中去竞争锁。

signal()方法

await()方法会阻塞ThreadA,然后ThreadB抢占到了锁的执行权限,ThreadB中调用了Condition的signal方法。将会唤醒在condition队列中的节点。

        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            
            //  拿到condition队列中的一个节点
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

doSignal()方法

       private void doSignal(Node first) {
            do {
                //   从 condition 队列中删除 first 节点
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;

              // CAS修改节点状态,将这个节点放到 AQS 队列中。然后唤醒这个节点的线程。
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

CountDownLatch

countDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其它线程执行完毕再执行。

    public void test() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"执行中");
            //  调用countDown方法,计数器减 1 等于 2
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        }, "t1").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"执行中");
            //  调用countDown方法,计数器减 1 等于 1
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        }, "t2").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"执行中");
            //  调用countDown方法,计数器减 1 等于 0
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        }, "t3").start();

        //  使用 countDownLatch。await() 方法来阻塞主线程
        // 当 CountDownLatch 计数器的值为 0 的时候 主线程才会被释放
        countDownLatch.await();

        System.out.println("所有线程执行完毕");
    }

使用CountDownLatch模拟并发场景。

public class ConcurrenceDemo extends Thread{

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    @Override
    public void run() {
        try {
            //  阻塞当前线程
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }


    public static void main(String[] args) {
        //  创建一千个线程,同时被阻塞
        for (int i = 0; i < 1000; i++) {
            new ConcurrenceDemo().start();
        }
        
        //  计数器-1   所有线程同时被释放
        countDownLatch.countDown();
    }

}

总结:countDown()方法每次调用都会将state的值减1,直到state的值为0;而await是一个阻塞方法,当state减为0的时候,await方法才会返回。所有调用了await方法的线程阻塞在AQS的阻塞队列中,等待被唤醒。

Semaphore

semaphore,可以控制同时访问的线程个数。

如下代码,我们创建Semaphore对象,设置参数为5,表示同时允许5个线程执行目标代码。

public class SemaphoreDemo {

    public static void main(String[] args) {
        //  表示同时可以有五个线程去获得执行权
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 10; i++) {
            new Car(i, semaphore).start();
        }
    }

    static class Car extends Thread {

        private int num;

        private Semaphore semaphore;

        public Car(int num, Semaphore semaphore) {
            this.num = num;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                //  获取一个许可
                semaphore.acquire();
                System.out.println("第"+num+"占用一个停车位");
                TimeUnit.SECONDS.sleep(2);
                System.out.println("第"+num+"辆车走了");
                semaphore.release();
            } catch (Exception e) {

            }
        }
    }

}

使用场景:比较适合用来做限流操作。

CyclicBarrier

CyclicBarrier的字面意思是可循环使用(cyclic)的屏障(Barrier)。该工具的作用是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,然后所有被拦截的屏障才会继续工作。CyclicBarrier的默认构造方法是CyclicBarrier(int parties),其参数表示屏障拦截线程的数量。

使用场景:当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值