文章目录
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) |
方法摘要 | |
---|---|
void | await() 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 |
boolean | await(long timeout, TimeUnit unit) 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 |
void | countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 |
long | getCount() 返回当前计数。 |
String | toString() 返回标识此锁存器及其状态的字符串。 |
7.1.3 使用
举例:6个学生走完后班长关门
- 不使用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() + "班长锁门");
}
}
- 使用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() + "班长锁门");
}
}
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) |
方法摘要 | |
---|---|
int | await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。 |
int | await(long timeout, TimeUnit unit) 在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。 |
int | getNumberWaiting() 返回当前在屏障处等待的参与者数目。 |
int | getParties() 返回要求启动此 barrier 的参与者数目。 |
boolean | isBroken() 查询此屏障是否处于损坏状态。 |
void | reset() 将屏障重置为其初始状态。 |
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();
}
}
}
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 方法不使用公平设置,而是使用任意可用的许可。
通常,应该将用于控制资源访问的信号量初始化为公平的,以确保所有线程都可访问资源。为其他的种类的同步控制使用信号量时,非公平排序的吞吐量优势通常要比公平考虑更为重要。
此类还提供便捷的方法来同时 acquire 和release()多个许可。小心,在未将公平设置为 true 时使用这些方法会增加不确定延期的风险。
内存一致性效果:线程中调用“释放”方法(比如 release())之前的操作 happen-before 另一线程中紧跟在成功的“获取”方法(比如 acquire())之后的操作。
个人理解:就是操作系统种pv操作嘛。
7.3.2 构造函数及方法
构造方法摘要 | |
---|---|
创建具有给定的许可数和非公平的公平设置的 Semaphore。 | Semaphore(int permits) |
创建具有给定的许可数和给定的公平设置的 Semaphore。 | Semaphore(int permits, boolean fair) |
方法摘要 | |
---|---|
void | acquire() 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。 |
void | acquire(int permits) 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。 |
void | acquireUninterruptibly() 从此信号量中获取许可,在有可用的许可前将其阻塞。 |
void | acquireUninterruptibly(int permits) 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。 |
int | availablePermits() 返回此信号量中当前可用的许可数。 |
int | drainPermits() 获取并返回立即可用的所有许可。 |
protected Collection<Thread> | getQueuedThreads() 返回一个 collection,包含可能等待获取的线程。 |
int | getQueueLength() 返回正在等待获取的线程的估计数目。 |
boolean | hasQueuedThreads() 查询是否有线程正在等待获取。 |
boolean | isFair() 如果此信号量的公平设置为 true,则返回 true。 |
protected void | reducePermits(int reduction) 根据指定的缩减量减小可用许可的数目。 |
void | release() 释放一个许可,将其返回给信号量。 |
void | release(int permits) 释放给定数目的许可,将其返回到信号量。 |
String | toString() 返回标识此信号量的字符串,以及信号量的状态。 |
boolean | tryAcquire() 仅在调用时此信号量存在一个可用许可,才从信号量获取许可。 |
boolean | tryAcquire(int permits) 仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。 |
boolean | tryAcquire(int permits, long timeout, TimeUnit unit) 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。 |
boolean | tryAcquire(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();
}
}
}