1. CountDownLatch
实现类似计数器的功能,将一个任务分为多个任务进行执行。
public class MyCountDownLatch {
//要处理的任务 -倒计时门栓,任务是独立的
class MyTask implements Callable<String> {
@Setter
private String name;
@Setter
private CountDownLatch countDownLatch;
public MyTask(String name, CountDownLatch countDownLatch) {
this.name = name;
this.countDownLatch = countDownLatch;
}
@Override
public String call() {
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
this.countDownLatch.countDown();
System.out.println(this.countDownLatch.getCount());
return ("Hello " + name);
}
}
private final int count = 5;
private CountDownLatch myTaskLatch = new CountDownLatch(count);
@Test
public void test() throws InterruptedException {
//线程池
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
ExecutorService fixedThreadPool = new ThreadPoolExecutor(10, 20,
200L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
namedThreadFactory);
//单个任务
//Future<String> futureResult = fixedThreadPool.submit(new MyTask("哈哈哈哈"));
//System.out.println(futureResult.get());
//多个任务时
List<Future<String>> resultList = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
resultList.add(fixedThreadPool.submit(new MyTask(String.valueOf(i), myTaskLatch)));
}
//等待任务完成
myTaskLatch.await();
//关闭线程池
fixedThreadPool.shutdown();
resultList.forEach(r -> {
try {
System.out.println(r.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
System.out.println("任务执行结果发生中断导致失败!");
}
});
}
}
原理
CountDownLatch通过实现AbstractQueuedSynchronizer,内部实现了公平锁类Sync,通过Sync来实现计数。关于AQS的简介可以参考Java并发基础 - AbstractQueuedSynchronizer
2. CyclicBarrier
回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
public class MyCyclicBarrier {
//要处理的任务 -循环栅栏,任务相互作用
class MyTask implements Callable<String> {
@Setter
private String name;
@Setter
private CyclicBarrier cyclicBarrier;
public MyTask(String name, CyclicBarrier cyclicBarrier) {
this.name = name;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public String call() throws BrokenBarrierException, InterruptedException {
System.out.println("线程" + Thread.currentThread().getName() + "正在写入数据...");
//线程的集合点
cyclicBarrier.await();
System.out.println("栅栏后等待的数量:" + cyclicBarrier.getNumberWaiting());
return ("Hello " + name);
}
}
private final int count = 5;
//第二个参数 Runnable barrierAction,就是所有线程执行完之后,再执行的
private CyclicBarrier myBarrier = new CyclicBarrier(count, () -> System.out.println("栅栏完了,执行其他逻辑..." +
Thread.currentThread()));
@Test
public void test() {
//1、定义线程池
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
ExecutorService fixedThreadPool = new ThreadPoolExecutor(10, 20,
200L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
namedThreadFactory);
//2、定义带返回值的多个任务
List<Future<String>> resultList = new ArrayList<>(count);
for (int i = 0; i < 10; i++) {
resultList.add(fixedThreadPool.submit(new MyTask(String.valueOf(i), myBarrier)));
}
//关闭线程池
//fixedThreadPool.shutdown();
resultList.forEach(r -> {
try {
System.out.println("返回的结果:" + r.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
System.out.println("任务执行结果发生中断导致失败!");
}
});
}
}
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同;CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时 执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
原理
CyclicBarrier内部通过ReentrantLock进行同步处理。
3. Semaphore
信号量,控制某个资源同时访问的线程个数。和锁有点类似。
public class MySemaphore {
class Worker implements Runnable {
private Semaphore semaphore;
private int count;
public Worker(Semaphore semaphore, int count) {
this.semaphore = semaphore;
this.count = count;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(this + " 占用一个机器在生产... ");
System.out.println(this + " 释放机器... ");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "-" + count;
}
}
@Test
public void test() {
int N = 5; //工人数
Semaphore semaphore = new Semaphore(3, true); //机器数目,资源数
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < N; ++i) {
exec.execute(new Worker(semaphore, i));
}
exec.shutdown();
}
}
运行后可以看到输出:
Worker-2 占用一个机器在生产...
Worker-1 占用一个机器在生产...
Worker-2 释放机器...
Worker-0 占用一个机器在生产...
Worker-1 释放机器...
Worker-0 释放机器...
Worker-4 占用一个机器在生产...
Worker-3 占用一个机器在生产...
Worker-4 释放机器...
Worker-3 释放机器...
示例代码中有5个工人,但是只有3个机器,也就是说同时最多可以访问3个机器(3个资源),所以5个工人分成了2批。
原理
Semaphore的实现同样借助了AbstractQueuedSynchronizer类,具体的可以参考本人的这篇文章Java并发 - ReentrantLock