CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock、Condition、FutureTask
**计数器向下减的闭锁 **
同步阻塞类,完成阻塞当前线程的功能,给定了一个计数器,原子操作,计数器不能重置。
1.通过一个计数来保证线程是否需要被阻塞。实现一个或多个线程等待其他线程执行的场景。
2.程序需要等待某个条件完成后,才能进行后面的操作(如父任务等待所有子任务都完成的时候,再继续往下进行)。
我们定义一个CountDownLatch,通过给定的计数器为其初始化,该计数器是原子性操作,保证同时只有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态。其他线程调用countDown方法(每次使计数器-1),当计数器变为0的时候,所有等待的线程才会继续执行。
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum); //需要被等待的线程执行的方法
} catch (Exception e) {
log.error("exception", e);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
3.多个线程完成一个任务,但是这个任务只想给它一个指定的时间,超过这个时间(计数器还未清零)就不继续等待了,完成多少算多少。(并不是第一时间毁掉所有线程,而是先让正在执行的线程执行完)。countDownLatch.await(等待时间长度,时间单位 );
查询需要等待某个条件完成后才能继续执行后续操作(Ex:并行计算)拆分任务
监控并发数
保证同一时间的请求量(并发访问控制线程的数目),达到上限会阻塞
信号量在操作系统中是很重要的概念,Java并发库里的Semaphore就可以很轻松的完成类似操作系统信号量的控制。Semaphore可以很容易控制系统中某个资源被同时访问的线程个数。
在数据结构中我们学过链表,链表正常是可以保存无限个节点的,而Semaphore可以实现有限大小的链表。
/**
* 1、普通调用
*/
try {
semaphore.acquire(); // 获取一个许可
test();//需要并发控制的内容
semaphore.release(); // 释放一个许可
} catch (Exception e) {
log.error("exception", e);
}
/**
* 2、acquire(n),release(n)
* 同时获取多个许可,同时释放多个许可
*/
try {
semaphore.acquire(2);
test();
semaphore.release(2);
} catch (Exception e) {
log.error("exception", e);
}
//tryAcquire()
//tryAcquire(int permits)//permits尝试获取许可的次数
//tryAcquire(long timeout, TimeUnit unit);
//tryAcquire(int permits,long timeout, TimeUnit unit)
/*
* 3、tryAcquire())//尝试获取一个许可
* 尝试获取许可,获取不到不执行
*/
try {
if (semaphore.tryAcquire()) {
test(threadNum);
semaphore.release();
}
} catch (Exception e) {
log.error("exception", e);
}
/*
* 4、尝试获取许可的时候等待一段时间,获取不到不执行
* 参数1:等待时间长度 参数2:等待时间单位
*/
try {
if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) {
test(threadNum);
semaphore.release();
}
} catch (Exception e) {
log.error("exception", e);
}
同步辅助类,运行一组线程等待到一个公共的屏障点,实现多个线程相互等待,所有线程都准备就绪后才继续执行,通过计数器实现的.
当某个线程调用了await()后,就会进入awaiting等待状态,并将计数器-1,直到所有的线程调用await()使计数器为0,线程再同时继续执行。
由于计数器释放之后可以重用(reset方法),所以称之为循环屏障。
多线程计算数据,最后合并计算结果。
如Excel保存用户的银行流水,每页保存了一个用户近一年的每笔银行流水,现统计用户的日均银行流水,多线程处理每一页里的银行流水。都执行完以后得到每一页的日均银行流水。之后通过CyclicBarrier 的action,利用这些线程的计算结果,计算出整个Excel的日均流水。
//公共线程循环调用方法
private static CyclicBarrier barrier = new CyclicBarrier(5);
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
//使用方法1:每个线程都持续等待
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await();
log.info("{} continue", threadNum);
}
//使用方法2:每个线程只等待一段时间
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
try {
barrier.await(2000, TimeUnit.MILLISECONDS);
} catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
log.warn("BarrierException", e);
}
}
//使用方法3:在初始化的时候设置runnable,当线程达到屏障时优先执行runnable
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
log.info("callback is running");
});
CyclicBarrier与CountDownLatch的比较
CyclicBarrier | CountDownLatch |
---|---|
可重复用 reset() | 只能使用一次 |
多个线程相互等待(内部关系) | 一个或n个线程等待其他线程的关系 |
CyclicBarrier提供方法获取阻塞线程的个数,知道阻塞的线程是否中断