CountDownLatch
CountDownLatch能够实现让某一个线程等待其他线程执行完成后再执行,比如多个线程负责计算各自的数据,数据之间没有联系,一个线程等待所有线程都计算完后进行数据的汇总。
传统的串行执行的方式,假设线程A/B/C分别进行计算,再由D线程汇总数据,总共耗时7秒。
但是如果A/B/C计算之间并没有任何依赖关系,那么就可以让他们同时进行计算,线程D等待所有线程都执行完之后再进行汇总,这样总共耗时就只需4秒。
当然除了CountDownLatch,还有其他的一些方法也能实现类似的效果,本文就只讨论CountDownLatch提供的方式。
CountDownLatch是通过计数器的方式实现的,构建CountDownLatch时先定义一个数量,比如3,然后线程D开始等待,每当线程A/B/C有一个完成任务时就减1,当计数器被减到0时,表示A/B/C任务都已执行完毕,则原先等待的线程D继续执行后续的任务。
public class CountDownLatchDemo {
//定义计数器为3
static CountDownLatch latch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("线程A执行耗时1秒。。。");
try {
Thread.sleep(1000);
//计数器减1
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
System.out.println("线程B执行耗时2秒。。。");
try {
Thread.sleep(2000);
//计数器减1
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
System.out.println("线程C执行耗时3秒。。。");
try {
Thread.sleep(3000);
//计数器减1
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
long s = System.currentTimeMillis();
//阻塞当前线程,直到计数器为0时释放
latch.await();
long e = System.currentTimeMillis();
System.out.println("总共耗时:" + (e - s) + "毫秒");
}
}
线程A执行耗时1秒。。。
线程B执行耗时2秒。。。
线程C执行耗时3秒。。。
总共耗时:3000毫秒
CountDownLatch除了实现上述类似的场景之外,还可以实现模拟并发的功能。
public class CountDownLatchConcurrentDemo {
//控制并发
static CountDownLatch lock = new CountDownLatch(1);
//控制10个线程全部准备好
static CountDownLatch countDownLatch = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
//创建完一个线程计数器扣减1,直到10个线程全部创建完成
countDownLatch.countDown();
//等待控制并发的计数器为0
lock.await();
System.out.println("thread name: " + Thread.currentThread().getName() + " 当前时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//阻塞等待10个线程准备好
countDownLatch.await();
//释放锁,10个线程同时执行后续业务
lock.countDown();
}
}
thread name: Thread-0 当前时间:1593094595528
thread name: Thread-7 当前时间:1593094595528
thread name: Thread-1 当前时间:1593094595528
thread name: Thread-3 当前时间:1593094595528
thread name: Thread-9 当前时间:1593094595528
thread name: Thread-8 当前时间:1593094595528
thread name: Thread-4 当前时间:1593094595528
thread name: Thread-6 当前时间:1593094595528
thread name: Thread-2 当前时间:1593094595528
thread name: Thread-5 当前时间:1593094595528
CyclicBarrier
CyclicBarrier意为可循环屏障,多个线程到达屏障点时阻塞,直到最后一个线程到达则开启屏障,然后释放所有阻塞的线程一起执行。
public class CyclicBarrierDemo {
private static CyclicBarrier barrier = new CyclicBarrier(3);
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程到达,开始等待!");
try {
barrier.await();
System.out.println(Thread.currentThread().getName() + "线程进入!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Thread-0线程到达,开始等待!
Thread-2线程到达,开始等待!
Thread-1线程到达,开始等待!
Thread-1线程进入!
Thread-0线程进入!
Thread-2线程进入!
CyclicBarrier还有一个带Runnable参数的构造方法,表示所有线程到达屏障后,优先执行Runnable。
public class CyclicBarrierDemo {
private static CyclicBarrier barrier = new CyclicBarrier(3, () -> {
try {
//休眠了1秒,证明runnable是优先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("构造方法中的线程。。。");
});
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程到达,开始等待!");
try {
barrier.await();
System.out.println(Thread.currentThread().getName() + "线程进入!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Thread-0线程到达,开始等待!
Thread-2线程到达,开始等待!
Thread-1线程到达,开始等待!
构造方法中的线程。。。
Thread-1线程进入!
Thread-0线程进入!
Thread-2线程进入!
CyclicBarrier也是可应用与多个线程计算,然后由一个线程等待并最终合并结果处理的场景。
CountDownLatch和CyclicBarrier区别
1、他们都是利用计数器来实现对线程的控制,CountDownLatch只能使用一次,CyclicBarrier可循环使用。
2、CountDownLatch需要由线程调用countDown方法扣减计数,CyclicBarrier直接调用await就会阻塞并且扣减计数。
3、要实现汇总结算的方式,CountDownLatch主线程等待await即可,
CyclicBarrier则通过使用带Runnable的构造方法实现。
4、要实现模拟并发,需要构建2个CountDownLatch,CyclicBarrier则更方便,每个线程各自调用await,最后一个线程调用完,则所有线程就会同时执行之后的任务。
Semaphore
Semaphore信号量,用来控制访问流量,经常应用在限流的场景中。
Semaphore的构造方法Semaphore(int permits)传入一个int型数字,表示许可证的数量。
public class SemaphoreDemo {
public static void main(String[] args) {
//5个许可证
Semaphore semaphore = new Semaphore(5);
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
//acquire表示领取一个许可证,10个线程需要10个许可证。
//一次最多只能领取到5个,必须等有许可证归还才能继续领取。
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("领取一个许可证,当前可用的许可证数量:" + semaphore.availablePermits());
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//release表示归还一个许可证
semaphore.release();
System.out.println("******归还一个许可证,当前可用的许可证数量:" + semaphore.availablePermits());
}
}).start();
}
}
领取一个许可证,当前可用的许可证数量:4
领取一个许可证,当前可用的许可证数量:3
领取一个许可证,当前可用的许可证数量:2
领取一个许可证,当前可用的许可证数量:1
领取一个许可证,当前可用的许可证数量:0
******归还一个许可证,当前可用的许可证数量:1
领取一个许可证,当前可用的许可证数量:0
******归还一个许可证,当前可用的许可证数量:1
领取一个许可证,当前可用的许可证数量:0
******归还一个许可证,当前可用的许可证数量:1
领取一个许可证,当前可用的许可证数量:0
******归还一个许可证,当前可用的许可证数量:1
领取一个许可证,当前可用的许可证数量:0
******归还一个许可证,当前可用的许可证数量:1
领取一个许可证,当前可用的许可证数量:0