CountDownLatch
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
CountDownLatch
用给定的计数初始化。 await
方法阻塞,直到由于countDown()
方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await
调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier
。
CountDownLatch
是一种通用的同步工具,可用于多种用途。 一个CountDownLatch
为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await
在门口等待,直到被调用countDown()
的线程打开。 一个CountDownLatch
初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。
CountDownLatch
一个有用的属性是,它不要求调用countDown
线程等待计数到达零之前继续,它只是阻止任何线程通过await
,直到所有线程可以通过。
构造方法
CountDownLatch(int count)
构造一个以给定计数 CountDownLatch
CountDownLatch
普通方法
public void await()
throws InterruptedException
导致当前线程等到锁存器计数到零,除非线程是interrupted 。
如果当前计数为零,则此方法立即返回。
如果当前计数大于零,则当前线程将被禁用以进行线程调度,并处于休眠状态,直至发生两件事情之一:
- 由于
countDown()
方法的调用,计数达到零; - 或者一些其他线程interrupts当前线程。
如果当前线程:
- 在进入该方法时设置了中断状态;
- 或者是interrupted等待,
然后InterruptedException
会抛出,并且当前线程的中断状态被清除。
public void countDown()
减少锁存器的计数,如果计数达到零,释放所有等待的线程。
如果当前计数大于零,则它将递减。 如果新计数为零,则所有等待的线程都将被重新启用以进行线程调度。
如果当前计数等于零,那么没有任何反应。
Semaphore
Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lock与 unlock过程。相对来说他也有两个主要的方法:
用于获取权限的acquire(),其底层实现与CountDownLatch.countdown()类似;
用于释放权限的release(),其底层实现与acquire()是一个互逆的过程。
一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()
都会阻塞,直到许可证可用,然后才能使用它。 每个release()
添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象;Semaphore
只保留可用数量的计数,并相应地执行。
信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。
在获得项目之前,每个线程必须从信号量获取许可证,以确保某个项目可用。 当线程完成该项目后,它将返回到池中,并将许可证返回到信号量,允许另一个线程获取该项目。 请注意,当调用acquire()
时,不会保持同步锁定,因为这将阻止某个项目返回到池中。 信号量封装了限制对池的访问所需的同步,与保持池本身一致性所需的任何同步分开。
信号量被初始化为一个,并且被使用,使得它只有至多一个允许可用,可以用作互斥锁。 这通常被称为二进制信号量 ,因为它只有两个状态:一个许可证可用,或零个许可证可用。 当以这种方式使用时,二进制信号量具有属性(与许多Lock
实现不同),“锁”可以由除所有者之外的线程释放(因为信号量没有所有权概念)。 这在某些专门的上下文中是有用的,例如死锁恢复。
此类的构造函数可选择接受公平参数。 当设置为false时,此类不会保证线程获取许可的顺序。 特别是, 闯入是允许的,也就是说,一个线程调用acquire()
可以提前已经等待线程分配的许可证-在等待线程队列的头部逻辑新的线程将自己。 当公平设置为真时,信号量保证调用acquire
方法的线程被选择以按照它们调用这些方法的顺序获得许可(先进先出; FIFO)。 请注意,FIFO排序必须适用于这些方法中的特定内部执行点。 因此,一个线程可以在另一个线程之前调用acquire
,但是在另一个线程之后到达排序点,并且类似地从方法返回。 另请注意, 未定义的tryAcquire
方法不符合公平性设置,但将采取任何可用的许可证。
通常,用于控制资源访问的信号量应该被公平地初始化,以确保线程没有被访问资源。 当使用信号量进行其他类型的同步控制时,非正常排序的吞吐量优势往往超过公平性。
构造方法
Semaphore(int permits)
创建一个 Semaphore
与给定数量的许可证和非公平公平设置。
Semaphore(int permits, boolean fair)
创建一个 Semaphore
与给定数量的许可证和给定的公平设置。
普通方法
public void acquire()
throws InterruptedException
从此信号量获取许可证,阻止直到可用,否则线程为interrupted 。
获得许可证,如果有可用并立即返回,则将可用许可证数量减少一个。
如果没有可用的许可证,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,直至发生两件事情之一:
- 一些其他线程调用此信号量的
release()
方法,当前线程旁边将分配一个许可证; 要么 - 一些其他线程interrupts当前线程。
如果当前线程:
- 在进入该方法时设置了中断状态; 要么
- 是interrupted等候许可证,
然后InterruptedException
被关上,当前线程的中断状态被清除。
public void release()
释放一个许可证,将其返回到信号量。
发放许可证,将可用许可证的数量增加一个。 如果任何线程尝试获取许可证,那么选择一个被授予刚被释放的许可证。 为了线程调度的目的,该线程被(重新)启用。
没有要求释放许可证的线程必须通过调用acquire()
获取该许可证。 信号量的正确使用通过应用程序中的编程惯例来确定。
例子
模拟并发,线程不安全的。
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
@NotThreadSafe //自定义注解:标识线程不安全
public class ConcurrencyTest {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count++;
}
}
结果:
count:4969
count:4978
count:4983
。。。。