在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier、Semaphore和Exchange。
一、CountDownLatch
CountDownLatch是一个同步计数器,初始化的时候 传入需要计数的线程等待数,可以是需要等待执行完成的线程数。
作用:用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。是一组线程等待其他的线程完成工作后再执行,相当于加强版join,其中:
await():阻塞当前线程,等待其他线程执行完成,直到计数器计数值减到0。
countDown():负责计数器的减一。
各个线程的执行示意图如下:
CountDownLatch 唯一的构造方法
// 初始化计数器
public CountDownLatch(int count) {};
count 为统计值。
CountDownLatch 的重要方法
public void await() throws InterruptedException { };
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
public void countDown() { };
- await() - 调用 await() 方法的线程会被挂起,它会等待直到 count 值为 0 才继续执行。
- await(long timeout, TimeUnit unit) - 和 await() 类似,只不过等待一定的时间后 count 值还没变为 0 的话就会继续执行
- countDown() - 将统计值 count 减 1
例如:主线程等待其他六个线程执行完成,再执行主线程。
/**
* CountDownLatch计数器演示,由外部线程控制一组线程的放行
*/
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(6);
private static class InitThread extends Thread {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始初始化....");
countDownLatch.countDown();
}
}
private static class BusiThread extends Thread {
@Override
public void run() {
try {
System.out.println("线程" + Thread.currentThread().getName() + "准备运行....");
//只有等countDownLatch的计数器为0,也就是其他计数器的线程都执行完了,才能执行
countDownLatch.await();
SleepTools.second(1);
System.out.println("线程" + Thread.currentThread().getName() + "运行完成....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始初始化....");
countDownLatch.countDown();
}
}, "main-2").start();
new BusiThread().start();
for (int i = 0; i < 5; i++) {
new InitThread().start();
}
try {
countDownLatch.await();
System.out.println("主线程运行结束.......");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、CyclicBarrier
CyclicBarrier字面意思是栅栏,是多线程中一个重要的类,主要用于线程组内部之间的线程的相互等待问题,初始化的时候传入需要等待的线程数。
作用:让一组线程达到某个屏障被阻塞,一直到组内最后一个线程达到屏障时,屏障开放,所有被阻塞的线程才会继续运行。其中:
CyclicBarrier(int parties):初始化定义需要等待的线程数parties。
CyclicBarrier(int parties, Runnable barrierAction):当屏障开放的时候,线程barrierAction的任务会执行。
CyclicBarrier 维护一个计数器 count。每次执行 await 方法之后,count 加 1,直到计数器的值和设置的值相等,等待的所有线程才会继续执行。
CyclicBarrier 的重要方法
public int await() throws InterruptedException, BrokenBarrierException {}
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {}
// 将屏障重置为初始状态
public void reset() {}
- await() - 等待调用 await() 的线程数达到屏障数。如果当前线程是最后一个到达的线程,并且在构造函数中提供了非空屏障操作,则当前线程在允许其他线程继续之前运行该操作。如果在屏障动作期间发生异常,那么该异常将在当前线程中传播并且屏障被置于断开状态。
- await(long timeout, TimeUnit unit) - 相比于 await() 方法,这个方法让这些线程等待至一定的时间,如果还有线程没有到达栅栏状态就直接让到达栅栏状态的线程执行后续任务。
- reset() - 将屏障重置为初始状态。
CountDownLatch和CyclicBarrier的区别
- countdownlatch放行条件>=线程数,CyclicBarrier放行条件=线程数
- CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
- CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方 法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
例如:主线程等待5个子线程执行完成
/**
* CyclicBarrier栅栏演示,由一组线程本身控制放行
*/
public class CyclicBarrierDemo {
//定义一个栅栏,在栅栏放行的时候,同时可以运行CollectThread这个线程
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new CollectThread());
private static ConcurrentHashMap<Long, String> concurrentHashMap = new ConcurrentHashMap();
private static class CollectThread extends Thread {
@Override
public void run() {
System.out.println(concurrentHashMap.size());
}
}
private static class SubThread extends Thread {
@Override
public void run() {
try {
Random random = new Random();
long id = Thread.currentThread().getId();
String name = Thread.currentThread().getName();
concurrentHashMap.put(id, name);
if (random.nextBoolean()) {
System.out.println("线程" + name + "正在处理中....");
SleepTools.second(2);
}
System.out.println("线程" + name + "处理结束,等待其他线程处理结束....");
//每个子线程都会在这里阻塞,等待所有的子线程都执行到这里,才会一起放行
cyclicBarrier.await();
System.out.println("线程" + name + "处理完毕....");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new SubThread().start();
}
//CyclicBarrier,不会阻塞主线程
System.out.println("主线程.....");
}
}
三、Semaphore
Semaphore又名信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。
作用:Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量,主要控制同时访问某个特定资源的线程数量,多用在流量控制。
注意:其他Semaphore的底层实现就是基于AQS的共享锁实现的。
如果一个线程要访问共享资源,必须先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。
Semaphore 提供了 2 个构造方法
public Semaphore(int permits) {}
public Semaphore(int permits, boolean fair) {}
- permits - 初始化固定数量的 permit,并且默认为非公平模式。
- fair - 设置是否为公平模式。所谓公平,是指等待久的优先获取 permit。
Semaphore的重要方法
// 获取 1 个许可
public void acquire() throws InterruptedException {}
//获取 permits 个许可
public void acquire(int permits) throws InterruptedException {}
// 释放 1 个许可
public void release() {}
//释放 permits 个许可
public void release(int permits) {}
例如:使用Semaphore模拟数据库连接池,Semaphore信号量是可以控制多个线程访问同一资源。
public class SemaphoreDemo {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore semaphore = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("save data");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}
四、Exchange
Exchange类似于一个交换器,可以在对中对元素进行配对和交换的线程的同步点,用于两个线程间的数据交换。
具体来说,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据结构,因此第一个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中。
public class ExchangerDemo {
private static Exchanger<Set<String>> exchanger = new Exchanger<>();
private static class ExchangerClassO extends Thread {
private Set<String> set;
public ExchangerClassO(Set<String> set) {
this.set = set;
}
@Override
public void run() {
try {
exchange(set);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class ExchangerClassT extends Thread {
private Set<String> set;
public ExchangerClassT(Set<String> set) {
this.set = set;
}
@Override
public void run() {
try {
exchange(set);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void exchange(Set<String> set) throws InterruptedException {
//交换数据,阻塞
System.out.println("线程:" + Thread.currentThread().getName() + "交换前得值....");
for (String s : set) {
System.out.println(s);
}
exchanger.exchange(set);
System.out.println("线程:" + Thread.currentThread().getName() + "交换后得值....");
for (String s : set) {
System.out.println(s);
}
}
public static void main(String[] args) {
Set<String> setA = new HashSet<>();
Set<String> setB = new HashSet<>();
setA.add("a1");
setA.add("b1");
setA.add("c1");
setB.add("a2");
setB.add("b2");
setB.add("c2");
new ExchangerClassO(setA).start();
new ExchangerClassT(setB).start();
}
五、参考
https://blog.csdn.net/qq_28822933/article/details/83340642
https://dunwu.github.io/javacore/concurrent/java-concurrent-tools.html