前言
本节笔者将详细讲下CountDownLatch, CyclicBarrier, Semaphore,Exchanger 这四个并发工具类的使用。
这4个工具类在高并发的场景下,也是使用广泛。
1.1 CountDownLatch简介
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
1.2 CountDownLatch使用场景
1.实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
2.开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
3.死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
1.3CountDownLatch 例子
/**
* CountDownLatch
* @author niyuelin
*
*/
public class CountDownLatchDemo {
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);// 两个工人的协作
Worker worker1 = new Worker("zhang san", 5000, latch);
Worker worker2 = new Worker("li si", 1000, latch);
worker1.start();//
worker2.start();//
latch.await();// 等待所有工人完成工作
System.out.println("all work done at " + sdf.format(new Date()));
}
static class Worker extends Thread {
String workerName;
int workTime;
CountDownLatch latch;
public Worker(String workerName, int workTime, CountDownLatch latch) {
this.workerName = workerName;
this.workTime = workTime;
this.latch = latch;
}
public void run() {
System.out.println("Worker " + workerName + " do work begin at " + sdf.format(new Date()));
doWork();// 工作了
System.out.println("Worker " + workerName + " do work complete at " + sdf.format(new Date()));
latch.countDown();// 工人完成工作,计数器减一
}
private void doWork() {
try {
Thread.sleep(workTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.4 输出结果
Worker li si do work begin at 2017-12-14 18:52:46
Worker zhang san do work begin at 2017-12-14 18:52:46
Worker li si do work complete at 2017-12-14 18:52:47
Worker zhang san do work complete at 2017-12-14 18:52:51
all work done at 2017-12-14 18:52:51
2.1 CyclicBarrier简介
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
2.2 CyclicBarrier使用场景
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景
2.3 CyclicBarrier例子
/**
* CyclicBarrier
* @author niyuelin
*
*/
public class CyclicBarrierDemo2 {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N, new Runnable() {
@Override
public void run() {
System.out.println("当前线程" + Thread.currentThread().getName());
}
});
for (int i = 0; i < N; i++)
new Writer(barrier).start();
}
static class Writer extends Thread {
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在写入数据...");
try {
Thread.sleep(5000); // 以睡眠来模拟写入数据操作
System.out.println("线程" + Thread.currentThread().getName() + "写入数据完毕,等待其他线程写入完毕");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("所有线程写入完毕,继续处理其他任务...");
}
}
}
2.4 输出结果
线程Thread-0正在写入数据...
线程Thread-1正在写入数据...
线程Thread-3正在写入数据...
线程Thread-2正在写入数据...
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
当前线程Thread-1
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
3.1 Semaphore简介
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
3.2 Semaphore使用场景
1.流量控制,特别公用资源有限的应用场景,比如数据库连接
2.对某组资源的访问权限
3.3 Semaphore例子
/**
* Semaphore
* @author niyuelin
*
*/
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService executorService = Executors.newFixedThreadPool(30);
private static Semaphore s = new Semaphore(10);
private static int in = 0;
public static synchronized void add(){
in++;
}
public static void main(String[] args) throws InterruptedException {
for(int i =0; i<THREAD_COUNT; i++){
executorService.execute(new Runnable() {
public void run() {
try {
s.acquire();
Thread.sleep(1000);
add();
// System.out.println("save data");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
Thread.sleep(1500);
System.out.println(in);
Thread.sleep(1000);
System.out.println(in);
executorService.shutdown();
}
}
3.4 输出结果
10
29
4.1 Exchanger简介
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
4.2 Exchanger使用场景
1.遗传算法,遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,并使用交叉规则得出2个交配结果。
2.校对工作 我们需要将纸制银流通过人工的方式录入成电子银行流水,为了避免错误,需要两个线程相互校对。
4.3 Exchanger例子
/**
* Exchanger
* @author niyuelin
*
*/
public class ExChangerTest {
private static final Exchanger<String> exgr = new Exchanger<String>();
private static ExecutorService executorService = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
String A = "银行流水A";
String c = exgr.exchange(A);
System.out.println("c: "+ c);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.execute(new Runnable() {
public void run() {
try {
String B = "银行流水B";
String A = exgr.exchange(B);
System.out.println("A:"+A+" ,B:"+B);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.shutdown();
}
}
4.4 输出结果
c:银行流水B
A:银行流水A ,B:银行流水B