七、JUC辅助类

7.1 减少计数CountDownLatch

7.1.1 介绍

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句。

  • CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
  • 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
  • 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行

7.1.2 构造函数及方法

构造方法摘要
构造一个用给定计数初始化的 CountDownLatch。CountDownLatch(int count)
方法摘要
voidawait()
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
booleanawait(long timeout, TimeUnit unit)
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
voidcountDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
longgetCount()
返回当前计数。
StringtoString()
返回标识此锁存器及其状态的字符串。

7.1.3 使用

举例:6个学生走完后班长关门

  1. 不使用CountDownLatch
/**
 * @author LWJ
 * @date 2023/6/20
 *
 * 使用 CountDownLatch
 */
public class CountDownLatchDemo {
    //举例:6个学生走完后班长关门
    public static void main(String[] args) {

        for (int i = 1; i <= 6; i++) {
            new Thread( () -> {
                System.out.println(Thread.currentThread().getName()+"号同学离开教室");
            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName() + "班长锁门");
    }
}

image.png

  1. 使用CountDownLatch
/**
 * @author LWJ
 * @date 2023/6/20
 *
 * 使用 CountDownLatch
 */
public class CountDownLatchDemo {
    //举例:6个学生走完后班长关门
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread( () -> {
                System.out.println(Thread.currentThread().getName()+"号同学离开教室");
                //计数器-1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + "班长锁门");
    }
}

image.png

7.2 循环栅栏CyclicBarrier

7.2.1 介绍

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。
如果屏障操作在执行时不依赖于正挂起的线程,则线程组中的任何线程在获得释放时都能执行该操作。为方便此操作,每次调用 await() 都将返回能到达屏障处的线程的索引。然后,您可以选择哪个线程应该执行屏障操作,例如:

if (barrier.await() == 0) {
    // log the completion of this iteration
}

对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。
内存一致性效果:线程中调用 await() 之前的操作 happen-before 那些是屏障操作的一部份的操作,后者依次 happen-before 紧跟在从另一个线程中对应 await() 成功返回的操作。

个人理解:任务分开完成,等到全部完成后,最终汇总。

7.2.2 构造函数及方法

构造方法摘要
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。CyclicBarrier(int parties)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。CyclicBarrier(int parties, Runnable barrierAction)
方法摘要
intawait()
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
intawait(long timeout, TimeUnit unit)
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
intgetNumberWaiting()
返回当前在屏障处等待的参与者数目。
intgetParties()
返回要求启动此 barrier 的参与者数目。
booleanisBroken()
查询此屏障是否处于损坏状态。
voidreset()
将屏障重置为其初始状态。

7.2.3 使用

/**
 * @author LWJ
 * @date 2023/6/20
 * 使用CyclicBarrier
 */
public class CyclicBarrierDemo {
    //举例:集齐七颗龙珠召唤神龙

    //创建资源(七颗龙珠)
    private static final int NUMBER = 7;
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,() -> {
            System.out.println("集齐七颗龙珠召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +"颗龙珠被收集");
                //等待全部收集完
                try {
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

image.png

7.3 信号灯Semaphore

7.3.1 介绍

public class Semaphore extends Object implements Serializable

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用 acquire() 时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。这通常也称为_二进制信号量_,因为它只能有两种状态:一个可用的许可,或零个可用的许可。按此方式使用时,二进制信号量具有某种属性(与很多 Lock 实现不同),即可以由线程释放“锁”,而不是由所有者(因为信号量没有所有权的概念)。在某些专门的上下文(如死锁恢复)中这会很有用。
此类的构造方法可选地接受一个_公平_ 参数。当设置为 false 时,此类不对线程获取许可的顺序做任何保证。特别地,闯入 是允许的,也就是说可以在已经等待的线程前为调用 acquire() 的线程分配一个许可,从逻辑上说,就是新线程将自己置于等待线程队列的头部。当公平设置为 true 时,信号量保证对于任何调用获取方法的线程而言,都按照处理它们调用这些方法的顺序(即先进先出;FIFO)来选择线程、获得许可。注意,FIFO 排序必然应用到这些方法内的指定内部执行点。所以,可能某个线程先于另一个线程调用了 acquire,但是却在该线程之后到达排序点,并且从方法返回时也类似。还要注意,非同步的 tryAcquire 方法不使用公平设置,而是使用任意可用的许可。
通常,应该将用于控制资源访问的信号量初始化为公平的,以确保所有线程都可访问资源。为其他的种类的同步控制使用信号量时,非公平排序的吞吐量优势通常要比公平考虑更为重要。
此类还提供便捷的方法来同时 acquirerelease()多个许可。小心,在未将公平设置为 true 时使用这些方法会增加不确定延期的风险。
内存一致性效果:线程中调用“释放”方法(比如 release())之前的操作 happen-before 另一线程中紧跟在成功的“获取”方法(比如 acquire())之后的操作。

个人理解:就是操作系统种pv操作嘛。

7.3.2 构造函数及方法

构造方法摘要
创建具有给定的许可数和非公平的公平设置的 Semaphore。Semaphore(int permits)
创建具有给定的许可数和给定的公平设置的 Semaphore。Semaphore(int permits, boolean fair)
方法摘要
voidacquire()
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
voidacquire(int permits)
从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断
voidacquireUninterruptibly()
从此信号量中获取许可,在有可用的许可前将其阻塞。
voidacquireUninterruptibly(int permits)
从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。
intavailablePermits()
返回此信号量中当前可用的许可数。
intdrainPermits()
获取并返回立即可用的所有许可。
protected Collection<Thread>getQueuedThreads()
返回一个 collection,包含可能等待获取的线程。
intgetQueueLength()
返回正在等待获取的线程的估计数目。
booleanhasQueuedThreads()
查询是否有线程正在等待获取。
booleanisFair()
如果此信号量的公平设置为 true,则返回 true。
protected voidreducePermits(int reduction)
根据指定的缩减量减小可用许可的数目。
voidrelease()
释放一个许可,将其返回给信号量。
voidrelease(int permits)
释放给定数目的许可,将其返回到信号量。
StringtoString()
返回标识此信号量的字符串,以及信号量的状态。
booleantryAcquire()
仅在调用时此信号量存在一个可用许可,才从信号量获取许可。
booleantryAcquire(int permits)
仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。
booleantryAcquire(int permits, long timeout, TimeUnit unit)
如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
booleantryAcquire(long timeout, TimeUnit unit)
如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断 ,则从此信号量获取一个许可。

7.3.3 使用

/**
 * @author LWJ
 * @date 2023/6/20
 * Semaphore 使用
 */
public class SemaphoreDemo {

    //举例:三个停车位,7辆车停车
    public static void main(String[] args) {
        //三个停车位
        Semaphore semaphore = new Semaphore(3);

        //7辆车
        for (int i = 1; i <= 7; i++) {
            new Thread( () -> {
                try {
                    //请求车位
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位---");

                    //模拟停车时长
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    //离开车位
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + "---离开车位");
                }
            },String.valueOf(i)).start();
        }
    }
}

image.png

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值