1、等待多线程完成的 CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作。
它的构造函数接受一个int类型的参数作为计数器,如果想等待N个点完成,这里传入N即可。
调用countDown方法时,N就会减1,await方法会阻塞当前线程直到N为0时。N个点可以是N个线程,也可以是同一线程中的N个点。
比如如下代码实现等待N个线程执行并发并等待N个线程结束。
package cn.zhjw.eurekaclientnode2.common;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* @Desc:
* @Author: zhaojiwei
* @Date: 2018/10/29 22:56
*/
public class ThreadSyn {
CountDownLatch latchOne = new CountDownLatch(1);
CountDownLatch latchTen = new CountDownLatch(10);
public static void main(String[] args) throws IOException {
ThreadSyn threadSyn = new ThreadSyn();
threadSyn.testCountDownLatch();
System.in.read();
System.out.println("##############");
}
public void testCountDownLatch() {
int m = 10;
for (int i = 0; i < m; i++) {
Thread thread = new Thread(new MyRunnable(latchOne, latchTen));
thread.start();
}
System.out.println("开始并发执行。。。");
latchOne.countDown();
try {
latchTen.await();
System.out.println("############### All Thread Done!!#################");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class MyRunnable implements Runnable {
private CountDownLatch countDownLathchOne;
private CountDownLatch countDownLatchTen;
public MyRunnable(CountDownLatch latchOne, CountDownLatch latchTen) {
this.countDownLatchTen = latchTen;
this.countDownLathchOne = latchOne;
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
try {
System.out.println("到达等待点 。。。" + Thread.currentThread().getName());
countDownLathchOne.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("running 。。。" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行后减去信号量
countDownLatchTen.countDown();
}
}
}
2、同步屏障 CyclicBarrier
顾名思义:可循环使用的屏障。它所要做的事情是让一组线程到达一个屏障(同步点)时阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续运行。
CyclicBarrier 与 CountDownLatch的区别:
- CountDownLatch 计数器只能使用一次
- CyclicBarrier 的计数器可通过方法reset() 重置,适合处理更复杂的业务场景;比如计算错误可以重置计数器重新计算一次;
- CyclicBarrier 还有其他可用方法,比如getNumberWating() 可以忽的阻塞的线程数,isBorken()用来了解阻塞的线程是否被中断。
比如多线程汇总数据后,计算总数据。
package cn.zhjw.eurekaclientnode2.common;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
/**
* @Desc:循环屏障
* @Author: zhaojiwei
* @Date: 2018/11/1 15:52
*/
public class ThreadSynCyclicBarrier {
static final int COUNT = 10;
/**
* 假设需要执行4个任务,开启4个线程去执行
*/
private Executor executor = Executors.newFixedThreadPool(COUNT);
private ConcurrentHashMap<String, Integer> bankWaterCount = new ConcurrentHashMap<>();
/**
* 循环屏障,都到达同步点时,优先执行回调操作barrierAction
*/
private CyclicBarrier cyclicBarrier = new CyclicBarrier(COUNT, new BankWaterService(bankWaterCount));
public static void main(String[] args) {
ThreadSynCyclicBarrier cb = new ThreadSynCyclicBarrier();
cb.count();
System.out.println("$#################");
}
/**
* 模拟多线程计算
*/
public void count() {
for (int i = 0; i < COUNT; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
//计算当前sheet中银流数据,计算代码省略
bankWaterCount.put(Thread.currentThread().getName(), Integer.valueOf(1));
//银流计算完后,插入一个屏障
try {
System.out.println(Thread.currentThread().getName()+" => await...");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
class BankWaterService implements Runnable {
private ConcurrentHashMap<String, Integer> bankWaterCount;
public BankWaterService(ConcurrentHashMap<String, Integer> bankWaterCount) {
this.bankWaterCount = bankWaterCount;
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
int result = 0;
Set<Map.Entry<String, Integer>> entries = bankWaterCount.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
result += entry.getValue();
}
//将结果输出
bankWaterCount.put("result", result);
System.out.println(result);
}
}
}
3、控制并发量的信号量 Semaphore
Semaphore 信号量用于控制多线程最大并发度(并发数量)。它协调多线程合理利用公共资源。
Semaphore可用于流量的控制,特别是公共资源有限的应用场景,比如数据库连接。
package cn.zhjw.eurekaclientnode2.common;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @Desc: 信号量并发流量的限制
* @Author: zhaojiwei
* @Date: 2018/11/1 16:56
*/
public class ThreadSynSemaphore {
private final static int THREAD_COUNT = 30;
private final static int SEMAPHORE_COUNT = 10;
/**
* 30个线程
*/
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
/**
* 10个信号量 控制并发度
*
* @param args
*/
private static Semaphore semaphore = new Semaphore(SEMAPHORE_COUNT);
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!!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
});
}
threadPool.shutdown();
}
}
Semaphore 构造函数接受一个int类型的参数,表示可用的许可证,通过调研 acquire()获取一个许可证,用完后通过反复release() 归还许可证即可。还可以通过tryAcquire()方法尝试获取许可证。
4、线程间交换数据的 交换者 Exchanger
Exchanger 是用于线程间交换数据的工具类。它提供一个同步点,在这个同步点,两个线程可以交换彼此的 数据。
两个线程同步Exchanger交换数据,第一个线程如果先执行exchange()方法,它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产的数据传递给对方。
应用场景:
- 遗传算法
- 校对工作
package cn.zhjw.eurekaclientnode2.common;
import java.util.concurrent.*;
/**
* @Desc: 线程间交换数据
* @Author: zhaojiwei
* @Date: 2018/11/1 17:15
*/
public class ThreadSynExchanger {
/**
* 交换字符串数据
*/
private static final Exchanger<String> exchanger = new Exchanger<>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(new Runnable() {
@Override
public void run() {
/**
* A 录入银行流水数据
*/
String A = "银行流水A";
try {
String exchange = exchanger.exchange(A, 1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
/**
* B 员工录入的银行流水数据
*/
String B = "银行流水B";
try {
String A = exchanger.exchange(B, 1, TimeUnit.MINUTES);
System.out.println("A和B的录入数据是否一致:" + A.equals(B) + ",A录入的是:" + A + ",B 录入的是:" + B);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
});
}
}
参考:《JAVA 并发编程的艺术》