在java并发中,CountDownLatch、CycliBarrier、Semaphore是很重要的同步工具类,都位于java.util.concurrent包下,下面分别介绍其用法和区别。
1.CountDownLatch
在JUC中,可以使用 Countdownlatch实现闭锁。其原理是, Countdownlatch在创建时,会指定一个计数器,表示等待线程的执行数量(例如,5就表示当5个线程执行完毕后,再闭锁,使A能够继续执行)。之后,其他每个线程在各自执行完毕时,分别调用一次 countdown方法,用来递减计数器,表示有一个线程已经执行完毕了。与此同时,线程A可以调用 await0方法,用来等待计数器的值为0。如果计数器的值大于0,那么 await方法会一直阻塞;直到计数器为0时线程A才会继续执行。如果线程A一直无法等到计数器为0,则会显示等待超时;当然也可以在线程A等待时,通过程序中断等待。举个生活中的例子就好比上晚自习,规定班长在同学全部离开后,班长最后关灯锁门。这个班长就是线程A。Demo如下:
import java.util.concurrent.CountDownLatch; public class TestCountDownLatch { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(10); MyThread myThread = new MyThread(countDownLatch); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { Thread thread = new Thread(myThread, "T" + i); thread.start(); } System.out.println("========"); countDownLatch.await(); long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); } static class MyThread implements Runnable { private CountDownLatch countDownLatch; public MyThread(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { Thread.sleep(300); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } } } }
2. CyclicBarrier
与 Countdownlatch类似, Cyclicbarrier也可以用于解决多个线程之间的相互等待问题。Cyclic Barrier的使用场景是,每个线程在执行时,都会碰到屏障,该屏障会拦截所有线程的执行(通过 await方法实现);当指定数量的线程全部就位时,所有的线程再跨过屏障同时执行。现举例说明 Countdownlatch和 Cyclicbarrier的区别。假设有A、B、C3个线程,其中C是最后一个加入的线程。举个开会的例子,只有等到最后一个人到了才能开始会议。Demo如下:
import java.io.IOException; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestCyclicBarrier { static class MyThread implements Runnable { //用于控制会议开始的屏障 private CyclicBarrier barrier; //参会人员 private String person; public MyThread(CyclicBarrier barrier, String name) { this.barrier = barrier; this.person = name; } @Override public void run() { try { Thread.sleep((int) (10000 * Math.random())); System.out.println(person + " 已经到会..."); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(person + " 开始会议..."); } } public static void main(String[] args) throws IOException, InterruptedException { //将屏障设置为3,即当有3个线程执行到await()时,再同时释放 CyclicBarrier barrier = new CyclicBarrier(3); ExecutorService executor = Executors.newFixedThreadPool(3); //三个人去开会 executor.submit(new MyThread(barrier, "zs")); executor.submit(new MyThread(barrier, "ls")); executor.submit(new MyThread(barrier, "ww")); executor.shutdown(); } }
3.Semaphor
Semaphore称为信号量,是引自操作系统中的概念。在Java中, Semaphore可以通过 permits属性控制线程的并发数。在使用了 Semaphore的编程中,默认情况下所有线程都处于阻塞状态。可以用 Semaphore的构造方法设置可执行线程的并发数(即 permits的值,如5),然后通过 acquire方法允许同一时间只能有5个线程同时执行;并且在这5个线程中,如果某个线程执行完毕,就可以调用 release释放一次执行的机会,然后从其他等待的线程中随机选取一个来执行。同一时间只允许3个线程执行,但是有10个线程尝试并发执行,Demo如下:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class TestSemaphore { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); // 同一时间,只允许3个线程并发访问 Semaphore semp = new Semaphore(3); // 创建10个线程 for (int i = 0; i < 10; i++) { final int threadNo = i; //execute()方法的参数:重写了run()方法的Runnable对象 executor.execute(() -> { try { //同一时间,只能有3个线程获取许可去执行 semp.acquire(); System.out.println("得到许可并执行的线程: " + threadNo); Thread.sleep((long) (Math.random() * 10000)); // 得到许可的线程执行完毕后,将许可转让给其他线程 semp.release(); } catch (InterruptedException e) { } } ); } executor.shutdown(); } }
总结:
Countdownlatch可以实现当A和B全部执行完毕后,C再去执行。Cyclicbarrier可以实现A、B等到C就绪后( await(0表示就绪),A、B、C三者再同时去执行。Countdownlatch是不能够重用的,而Cyclicbarrier是可以重用的。Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。