在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其实和锁有点类似,它一般用于控制对某组资源的访问权限。