在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。CountDownLatch、CyclicBarrier均是为应对上面场景的类。此外还有Semaphore也可以起到阻塞的作用。
一、CountDownLatch
CountDownLatch(int count):构造方法,设置计数量;
void countDown():count减1;
long getCount():返回当前count;
boolean await():让当前线程等待直到count减为0。countDown()或者线程被中断count都会减1;
boolean await(long timeout, TimeUnit unit):限制等待时间
例子
@Test
public void testCountDownLatch() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread thread1 = new Thread(()-> {
System.out.println("thread1 run");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
countDownLatch.countDown();
}
});
Thread thread2 = new Thread(()-> {
System.out.println("thread2 run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
countDownLatch.countDown();
}
});
thread1.start();
thread2.start();
thread2.interrupt();
Thread.sleep(1000);
System.out.println("thread2 is interrupted,and Count is "+countDownLatch.getCount());
countDownLatch.await();
System.out.println("finally Count is "+countDownLatch.getCount());
System.out.println("end");
Thread.sleep(1000);
}
二、CyclicBarrier(循环屏障)
CountDownLatch 在解决多个线程同步方面对于调用线程的join方法已经有了不少优化。但还有一个问题是CountDownLatch 的计数器是一次性的,也就是等到计数器变为0后,再调用CountDownLatch 的await和countdown方法都会立即返回,起不到线程同步的效果。而CyclicBarrier可以使计数器重置,并且不限于CountDownLatch 的功能。
例子1
@Test
public void testCyclicBarrier() {
CyclicBarrier cyclicBarrier =new CyclicBarrier(2,()-> {
System.out.println("执行线程"+Thread.currentThread()+"时,Barrier减为0,触发了执行后任务。");
});
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(
200));
pool.execute(()->{
System.out.println(Thread.currentThread()+" 开始执行");
try {
System.out.println(Thread.currentThread()+" cyclicBarrier 开始等待");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" cyclicBarrier 不再等待");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.execute(()->{
System.out.println(Thread.currentThread()+" 开始执行");
try {
System.out.println(Thread.currentThread()+" cyclicBarrier 开始等待");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" cyclicBarrier 不再等待");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.shutdown();
}
上面例子还不足以说明CyclicBarrier可循环利用,再举一个例子
假设有任务分三个阶段处理,但是需要全部线程的某一个阶段全部完成才可以进行下一个阶段的工作。
@Test
public void testCyclicBarrier2() {
CyclicBarrier cyclicBarrier =new CyclicBarrier(2);
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(
200));
pool.execute(()->{
try {
System.out.println(Thread.currentThread()+" 阶段1执行完毕!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 阶段2执行完毕!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 阶段3执行完毕!");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.execute(()->{
try {
System.out.println(Thread.currentThread()+" 阶段1执行完毕!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 阶段2执行完毕!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 阶段3执行完毕!");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.shutdown();
}
打印结果:
三、Semaphore
信号量,不可以自动重置,主要三个方法
Semaphore(int permits):设置初始已获得的许可数
void acquire(int permits):请求许可,当后面获得相应数量的许可才会放行,否则会阻塞当前线程。
void release():发放1个许可。
例子
@Test
public void testSemaphore() throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(
200));
pool.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+" 执行");
semaphore.release();
});
pool.execute(()->{
System.out.println(Thread.currentThread()+" 执行.");
semaphore.release();
});
semaphore.acquire(2);
System.out.println("线程全部执行完毕");
Thread.sleep(100);
pool.shutdown();
}
上面的例子中,假如将“ Semaphore semaphore = new Semaphore(0);”改为“ Semaphore semaphore = new Semaphore(2);”,那么semaphore.acquire(2);将不会等待,因为初始化已经获得了相应数量的许可。