java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

时间:2023-02-04 18:12:29

-闭锁(Latch)

闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。

应用场景:

  • 确保某个计算在其需要的所有资源都被初始化之后才继续执行。二元闭锁(包括两个状态)可以用来表示“资源R已经被初始化”,而所有需要R的操作都必须先在这个闭锁上等待。
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
  • 等待直到某个操作的所有参与者都就绪在继续执行。(例如:多人游戏中需要所有玩家准备才能开始)

CountDownLatch是JDK 5+里面闭锁的一个实现,允许一个或者多个线程等待某个事件的发生。CountDownLatch有一个正数计数器,countDown方法对计数器做减操作,await方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时。


-栅栏(CyclicBarrier)

栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。 栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

场景: 应用一些协议,比如几个家庭成员决定在某个地方集合,所有人在6:00在某地集合,到了以后要等待其他人,之后才能讨论去哪里吃饭。 并行迭代,将一个问题分成很多子问题,当一系列的子问题都解决之后(所有子问题线程都已经await()),此时将栅栏打开,所有子问题线程被释放,而栅栏位置可以留着下次使用。


-例子:两个分别关于CountDownlatch和CyclicBarrier的例子

1、CountDownLatch

有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。所以在这里用Java代码设计两个类,Worker代表工人,Boss代表老板,具体的代码实现如下:

工人:

package LatchAndCyclicBarrier;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Work implements Runnable{


private CountDownLatch downLatch;
private String name;

public Work(CountDownLatch downLatch, String name){
this.downLatch = downLatch;
this.name = name;
}

public void run() {
this.doWork();
try{
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
}catch(InterruptedException ie){
}
System.out.println(this.name + "活干完了!");
this.downLatch.countDown();

}

private void doWork(){
System.out.println(this.name + "正在干活!");
}

}

老板:


package LatchAndCyclicBarrier;

import java.util.concurrent.CountDownLatch;

public class Boss implements Runnable{

private CountDownLatch downLatch;

public Boss(CountDownLatch downLatch){
this.downLatch = downLatch;
}

public void run() {
System.out.println("老板正在等所有的工人干完活......");
try {
this.downLatch.await();
} catch (InterruptedException e) {
}
System.out.println("工人活都干完了,老板开始检查了!");
}

}



测试代码:

package LatchAndCyclicBarrier;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestLatch {

public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();

CountDownLatch latch = new CountDownLatch(3);

Work w1 = new Work(latch,"张三");
Work w2 = new Work(latch,"李四");
Work w3 = new Work(latch,"王二");

Boss boss = new Boss(latch);

executor.execute(w3);
executor.execute(w2);
executor.execute(w1);
executor.execute(boss);

executor.shutdown();
}

}




执行结果:

李四正在干活!
老板正在等所有的工人干完活......
王二正在干活!
张三正在干活!
李四活干完了!
王二活干完了!
张三活干完了!
工人活都干完了,老板开始检查了!

2、CyclicBarrier

接着上面的例子,还是这三个工人,不过这一次,这三个工人*了,老板不用检查他们任务了,他们三个合作建桥,有三个桩,每人打一个,同时打完之后才能一起搭桥(搭桥需要三人一起合作)。也就是说三个人都打完桩之后才能继续工作。


package LatchAndCyclicBarrier;

import java.util.concurrent.CyclicBarrier;

public class CycWork implements Runnable {


private CyclicBarrier cyclicBarrier ;
private String name ;

public CycWork(CyclicBarrier cyclicBarrier,String name)
{
this .name =name;
this .cyclicBarrier =cyclicBarrier;
}

@Override
public void run() {
// TODO Auto-generated method stub

System. out .println(name +"正在打桩,毕竟不轻松。。。。。" );

try {
Thread. sleep(5000);
System. out .println(name +"不容易,终于把桩打完了。。。。" );
cyclicBarrier .await();

} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}

System. out .println(name +":其他逗b把桩都打完了,又得忙活了。。。" );


}

}

测试程序

package LatchAndCyclicBarrier;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CycTest {

public static void main(String[] args)
{
ExecutorService executorpool=Executors. newFixedThreadPool(3);
CyclicBarrier cyclicBarrier= new CyclicBarrier(3);

CycWork work1= new CycWork(cyclicBarrier, "张三" );
CycWork work2= new CycWork(cyclicBarrier, "李四" );
CycWork work3= new CycWork(cyclicBarrier, "王五" );

executorpool.execute(work1);
executorpool.execute(work2);
executorpool.execute(work3);

executorpool.shutdown();

}

}

运行结果:

李四正在打桩,毕竟不轻松。。。。。
张三正在打桩,毕竟不轻松。。。。。
王五正在打桩,毕竟不轻松。。。。。
李四不容易,终于把桩打完了。。。。
张三不容易,终于把桩打完了。。。。
王五不容易,终于把桩打完了。。。。
王五:其他逗b把桩都打完了,又得忙活了。。。
李四:其他逗b把桩都打完了,又得忙活了。。。
张三:其他逗b把桩都打完了,又得忙活了。。。

CountDownlatch和CyclicBarrierde 源码部分

1、CountDownLatch中的两个关键方法

  public void countDown() {    //对计数器减一 表示有一个事件已经发生了
sync.releaseShared(1);
}

 public void await() throws InterruptedException { //等到计数器为0
sync.acquireSharedInterruptibly(1);
}

await方法调用了AbstractQueuedSynchronizer中的acquireSharedInterruptibly

 public final void acquireSharedInterruptibly (int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
 public final boolean releaseShared (int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true ;
}
return false ;
}
protected boolean tryReleaseShared (int arg) {
throw new UnsupportedOperationException();
}

2、CyclicBarrier中的await()方法

  public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen;
}
}
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();
}

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();
}
}

// loop until tripped, broken, interrupted, or timed out
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();
}
}

上面dowait方法中有一个index,index=--count而count的值在源码中来自

count = parties;

提到 parties就不得不看看构造函数了

 public CyclicBarrier(int parties) {
this(parties, null);
}

如上例子,我们构造了CyclicBarrier(3)那么此时的 count值为3,接着dowait源码,当index==0时,后面执行的

final Runnable command = barrierCommand;

其实是可以设置的,这个Runnable可以传进来,当我们希望所有线程都达到某一时刻之后,用什么线程执行接下来的工作,当没有传Runnable进来时,就继续执行(唤醒其他线程),否则就runnable.run()(唤醒其他线程之前执行)