多线程的闭锁和栅栏
JAVA并发包中有三个类用于同步一批线程的行为,分别是闭锁(Latch),信号灯(Semaphore)和栅栏(CyclicBarrier)。这里我们主要来介绍一下:
闭锁(Latch)
闭锁即是一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。
通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
其中一个具体的例子就是我们的计数器闭锁(CountDownLatch),它是JDK5+中闭锁的一个实现,允许一个或者多个线程等待某一个事件的发生。
CountDownLatch 有个正数的计数器,countDown(): 对计数器做减法操作;await(): 等待计数器 = 0。
所有await的线程都会阻塞,直到计数器为0或者等待线程中断或者超时。
我们使用代码来举一个例子:
//MyRunnable类
public class MyRunnable implements Runnable{
private final CountDownLatch await;
private final int num;
public MyRunnable(CountDownLatch await, int num) {
this.await = await;
this.num = num;
}
@Override
public void run() {
System.out.println("线程" + num + "执行完毕...");
await.countDown(); //当前事件执行完毕,计数减1
}
}
//测试类
public class TestCountDown {
public static void main(String[] args) throws InterruptedException {
CountDownLatch await = new CountDownLatch(5);
for (int i = 1; i < 6; i++) {
new Thread(new MyRunnable(await,i)).start();
}
System.out.println("等待线程运行结束...");
await.await();
System.out.println("五个线程已经运行结束...");
}
}
输出结果:
线程1执行完毕...
等待线程运行结束...
线程4执行完毕...
线程2执行完毕...
线程3执行完毕...
线程5执行完毕...
五个线程已经运行结束...
说明主线程await以后,必须等待这五个线程运行结束才会执行
我们再来举一个例子:
此时三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。
Worker工人类:
public class Worker implements Runnable{
private CountDownLatch downLatch;
private String name;
public Worker(CountDownLatch downLatch, String name) {
this.downLatch = downLatch;
this.name = name;
}
@Override
public void run() {
this.work();
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println(this.name + "活干完了...");
this.downLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void work() {
System.out.println(this.name + "正在干活...");
}
}
Boss包工头类:
public class Boss implements Runnable{
private CountDownLatch downLatch;
public Boss(CountDownLatch downLatch) {
this.downLatch = downLatch;
}
@Override
public void run() {
System.out.println("包工头等待工人干完活...");
try {
this.downLatch.await();
System.out.println("工人们已经干完活了,现在进行检查...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TestLauch测试类:
public class TestLatch {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(5);
ExecutorService executorService = Executors.newCachedThreadPool();
Worker worker1 = new Worker(latch, "张三");
Worker worker2 = new Worker(latch, "李四");
Worker worker3 = new Worker(latch, "王五");
Worker worker4 = new Worker(latch, "赵六");
Worker worker5 = new Worker(latch, "冯七");
Boss boss = new Boss(latch);
executorService.execute(worker1);
executorService.execute(worker2);
executorService.execute(worker3);
executorService.execute(worker4);
executorService.execute(worker5);
executorService.execute(boss);
executorService.shutdown();
}
}
输出结果:
张三正在干活...
赵六正在干活...
王五正在干活...
李四正在干活...
包工头等待工人干完活...
冯七正在干活...
李四活干完了...
张三活干完了...
王五活干完了...
赵六活干完了...
冯七活干完了...
工人们已经干完活了,现在进行检查...
栅栏(CyclicBarrier)
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。 栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
场景: 接着上面的例子,还是这三个工人,不过这一次,这三个工人自由了,老板不用检查他们任务了,他们三个合作建桥,有三个桩,每人打一个,同时打完之后才能一起搭桥(搭桥需要三人一起合作)。也就是说三个人都打完桩之后才能继续工作。
Worker类:
public class Worker implements Runnable{
private CyclicBarrier cyclicBarrier;
private String name;
public Worker(CyclicBarrier cyclicBarrier,String name) {
this.cyclicBarrier = cyclicBarrier;
this.name = name;
}
@Override
public void run() {
try {
this.work();
Thread.sleep(new Random().nextInt(1000));
this.waitOther();
cyclicBarrier.await();
this.continueWork();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
private void work() {
System.out.println(this.name + "正在干活...");
}
private void waitOther() {
System.out.println(this.name + "当前这部分工作干完了,等等他们吧...");
}
private void continueWork() {
System.out.println("大家都干完这部分活了," + this.name + "又得忙活了...");
}
}
TestCycleBarrier测试类:
public class TestCycleBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
ExecutorService es = Executors.newCachedThreadPool();
es.execute(new Worker(cyclicBarrier,"张三"));
es.execute(new Worker(cyclicBarrier,"李四"));
es.execute(new Worker(cyclicBarrier,"王五"));
es.execute(new Worker(cyclicBarrier,"赵六"));
es.execute(new Worker(cyclicBarrier,"冯七"));
es.shutdown();
}
}
输出结果:
张三正在干活...
冯七正在干活...
赵六正在干活...
李四正在干活...
王五正在干活...
李四当前这部分工作干完了,等等他们吧...
赵六当前这部分工作干完了,等等他们吧...
张三当前这部分工作干完了,等等他们吧...
王五当前这部分工作干完了,等等他们吧...
冯七当前这部分工作干完了,等等他们吧...
大家都干完这部分活了,冯七又得忙活了...
大家都干完这部分活了,李四又得忙活了...
大家都干完这部分活了,王五又得忙活了...
大家都干完这部分活了,张三又得忙活了...
大家都干完这部分活了,赵六又得忙活了..
闭锁和栅栏的区别:
- 闭锁用于所有线程等待一个外部事件的发生;栅栏则是所有线程相互等待,直到所有线程都到达某一点时才打开栅栏,然后线程可以继续执行。