简言之,闭锁可以等待其他线程执行完毕再执行其他操作,如果没有闭锁则需要发送一个通知或者估计一个执行时间来保证其他线程的操作执行完成,这样效率会很低。(不过闭锁的原理也相当于发送一个通知,也就是计数器的值为0,代表操作已经完成)。
Java中的闭锁是CountDownLatch
下面是一个树上的例子:
同步多个线程,并同时释放释放这些线程。
其中:
CountDownLatch startGate = new CountDownLatch(1);
初始化startGate的计数为1,即有一个任务需要完成。
final CountDownLatch endGate = new CountDownLatch(nThreads);
初始化endGate的计数为线程个数nThreads,代表需要有nThread个线程同步。
在for循环中,每次循环新开一个线程并start,并在线程内部调用await()方法,使得当前线程等待,不在继续执行,一直到循环结束,会创建nThread个线程。在这些线程中,因为startGate的计数一直为1,所以一直处于阻塞状态。我们传入的final Runnable task并没有执行。
然后调用startGate.countDown();
,此时startGate变为0,即任务以及完成,此时,释放所有线程,等待执行完毕。在每个线程执行的时候,会检测endGate的计数是否为0,若不为0,则表示所有子线程任务没有完成,则主线程阻塞等待,直到endGate的计数为0,则主线程继续执行输出“执行完毕”。
在这个例子中,startGate就是一道门,当它不为0时,门是关的,阻止了nThread个线程的运行,当它值为0时,就同时释放所有线程。它的主要作用就是让所有子线程同时直线,便于测开始时间。
endGate可以看作一个计数器,当计数不为0时,代表子线程还有没运行完的,需要继续等待,一旦计数为0,则子线程完成。它阻塞的主线程,而startGate阻塞的是子线程。
public class Test5 {
public static void main(String[] args) throws InterruptedException {
System.out.println(new Test5().timeTasks(10,new Test5A()));
}
public long timeTasks(int nThreads, final Runnable task)
throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread("线程" + i) {
@Override
public void run() {
try {
//阻塞线程t的执行,当startGate的count减为0时,再释放所有线程t
//本线程的阻塞不会影响其他线程,在这里不会影响循环
startGate.await();
Thread.sleep((100));
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException ignored) {
}
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
System.out.println("等待执行");
endGate.await();
System.out.println("执行完毕");
long end = System.nanoTime();
return (end - start)/1000;
}
}
class Test5A implements Runnable{
@Override
public void run() {
System.out.println("【"+Thread.currentThread().getName()+"执行】");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("【"+Thread.currentThread().getName()+"执行完了】");
}
}
}