线程同步器
在执行线程任务时,时常会遇到需要多个线程执行完成之后,才进行下一步操作。线程中有三个工具可以实现此功能,下面进行一一介绍:
(一) CountDownLatch
CountDownLatch依赖于抽象同步队列实现,当new 一个countDownLatch对象时传入计数器参数,执行await方法时进入等待。计数器在每个线程执行完之后通过执行countDown方法减去1 ,直到所有线程执行完后计数器为0,此时await方法退出并进入下一步操作。
由于计数器是在线程中干完工作后减少的,所以在创建线程任务时需要将countDownLatch对象传入。
package concurrent.executorService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MyCountDownLatch { private ExecutorService executorService; private CountDownLatch countDown; public MyCountDownLatch(int poolSize, int countDownNum){ this.executorService = Executors.newFixedThreadPool(poolSize); this.countDown = new CountDownLatch(countDownNum); } public void useCountDownLatch() throws InterruptedException { long taskNum = this.countDown.getCount(); for(int i = 0; i < taskNum; i++){ executorService.submit(new MyTask(countDown, i)); } countDown.await(); } public class MyTask implements Runnable{ private CountDownLatch countDownLatch; private int taskSequence; public MyTask(CountDownLatch countDown, int i){ this.countDownLatch = countDown; this.taskSequence = i; } @Override public void run() { doTask(taskSequence); this.countDownLatch.countDown(); } } public void doTask(int taskSequence) { try{ Thread.sleep(1000); }catch (Exception e){ System.out.println(e.getMessage()); } System.out.println("execute task job :" + taskSequence); } }
测试代码如下:
public class MyCountDownLatchTest extends TestCase { public void testUseCountDownLatch() throws InterruptedException { MyCountDownLatch myCountDownLatch = new MyCountDownLatch(5,3); myCountDownLatch.useCountDownLatch(); System.out.println("所有任务已完成"); } }
测试结果:
execute task job :1
execute task job :2
execute task job :0
所有任务已完成
(二)CyclicBarrier
cyclicBarrier回环屏障也被成为栅栏,在创建cyclicBarrier对象时,需要传入计数器的值(设置的栅栏数)和需要等待所有线程完成之后要做的事情(Runnable接口)。 在每个线程执行完内部的工作之后执行await方法即将计数器减去1(跨过了一个栅栏),待到计数器的值为0时,就执行Runnable接口实现的方法。此外计数器为0 时,也会重新将计数器设置为原来的栅栏数,此举就是为了复用。
代码示例如下:
package concurrent.executorService; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MyCyclicBarrier { private CyclicBarrier cyclicBarrier; private ExecutorService service; private Integer integer = new Integer(1); public MyCyclicBarrier() { cyclicBarrier = new CyclicBarrier(2, () -> { System.out.println("step " + integer + " finished"); ++integer; }); service = Executors.newFixedThreadPool(2); } public void useCyclicBarrier() { service.submit(() -> { System.out.println(Thread.currentThread().getName() + " do step one"); cyclicBarrierAwait(); System.out.println(Thread.currentThread().getName() + " do step two"); cyclicBarrierAwait(); System.out.println(Thread.currentThread().getName() + " do step three"); cyclicBarrierAwait(); }); service.submit(() -> { System.out.println(Thread.currentThread().getName() + " do step one"); cyclicBarrierAwait(); System.out.println(Thread.currentThread().getName() + " do step two"); cyclicBarrierAwait(); System.out.println(Thread.currentThread().getName() + " do step three"); cyclicBarrierAwait(); }); service.shutdown(); System.out.println("over"); } private void cyclicBarrierAwait() { try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }
测试代码如下:
public class MyCyclicBarrierTest extends TestCase { public void testUseCyclicBarrier() { MyCyclicBarrier cyclicBarrier = new MyCyclicBarrier(); cyclicBarrier.useCyclicBarrier(); } }
测试结果:
pool-1-thread-1 do step one
pool-1-thread-2 do step one
over
step 1 finished
pool-1-thread-1 do step two
pool-1-thread-2 do step two
step 2 finished
pool-1-thread-1 do step three
pool-1-thread-2 do step three
step 3 finished
从以上结果可以看出,主线程的执行顺序是不受栅栏影响的,栅栏只规定了线程中哪些步骤做完之后再去做栅栏中的Runnable接口的方法。此外,栅栏的计数器是可以复用的。
(三)Semaphore 信号量
信号量也是依赖于抽象同步队列实现的。在创建信号量对象时传入的计数器值是0,执行信号量的acquire方法时会传入一个参数,该参数表示计数器需要达到该值后才会退出等待,执行下一步操作。每个线程中通过执行信号量的release方法,将计数器加1.
package concurrent.executorService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class MySemaphore { private Semaphore semaphore = new Semaphore(0); private ExecutorService service; public MySemaphore(){ service = Executors.newFixedThreadPool(2); } public void useSemaphore() throws InterruptedException { service.submit(() -> { System.out.println(Thread.currentThread().getName() + " do step one"); semaphore.release(); }); service.submit(() -> { System.out.println(Thread.currentThread().getName() + " do step one"); semaphore.release(); }); semaphore.acquire(2); service.shutdown(); System.out.println("over"); } }
测试代码:
public class MySemaphoreTest extends TestCase { public void testUseSemaphore() throws InterruptedException { MySemaphore mySemaphore = new MySemaphore(); mySemaphore.useSemaphore(); } }
测试结果:
pool-1-thread-1 do step one
pool-1-thread-2 do step one
over
从这里可以看出Semphore和CountDownLatch的效果非常的类似。只是Semphore的计数器也是可以复用的。
(四) cyclicBarrier与CountDownLatch的区别
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } //减少锁存器的计数,如果计数达到零,则释放所有等待线程; 如果当前计数大于零,则递减。 public void countDown() { sync.releaseShared(1); } //将当前线程阻塞住,直到count值减为0才会放开执行 public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; } //指定每次需要进行协调的线程个数 public CyclicBarrier(int parties) { this(parties, null); } private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } //如果计数器为0, 唤醒所有线程,并进入下一个线程协调器。 int index = --count; if (index == 0) { // tripped boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } //计数器不为0,继续进行循环 for (;;) { try { if (!timed) trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // We're about to finish waiting even if we had not // been interrupted, so this interrupt is deemed to // "belong" to subsequent execution. Thread.currentThread().interrupt(); } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } } private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); } private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); }
区别 | CountDownLatch | CyclicBarrier |
计数方式 |
递减计数 调用countDown,N-1 |
加法计数 调用await, N+1 |
可重复利用性 | 不可重复利用 | 可重复利用 |
初始值 | 初始值为N,N>0 | N为0 |
阻塞条件 | N>0, 调用await一直阻塞 | N小于指定值 |
何时释放等待线程 | 计数为0时 | 计数达到指定的N值 |
是否阻塞主进程 | 会 | 不会,只会阻塞子进程 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)