CountDownLatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行。
- CountDownLatch 在 JDK 1.5 被引入。
- 存在于 java.util.concurrent 包下。
- 应用程序的主线程希望在负责启动框架服务的线程已经启动所有框架服务之后执行
最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。
之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,
或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。
用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,
而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
此例很形象的描述了countDown的执行过程
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
// 开始的倒数锁
final CountDownLatch begin = new CountDownLatch(1);
// 结束的倒数锁
final CountDownLatch end = new CountDownLatch(10);
// 十名选手
final ExecutorService exec = Executors.newFixedThreadPool(10);
for (int index = 0; index < 10; index++) {
final int NO = index + 1;
Runnable run = new Runnable() {
public void run() {
try {
begin.await();//一直阻塞
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
} finally {
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start");
begin.countDown();
end.await();
System.out.println("Game Over");
exec.shutdown();
}
}
//执行结果为:
Game Start
No.10 arrived
No.2 arrived
No.7 arrived
No.4 arrived
No.9 arrived
No.3 arrived
No.5 arrived
No.6 arrived
No.8 arrived
No.1 arrived
Game Over
import java.util.concurrent.CountDownLatch;
/**
* 需求 当同学走光了 班长再关门
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 离开教室");
countDownLatch.countDown();
}, i + "").start();
}
countDownLatch.await(); // 阻塞住后面的线程 直到计数器为0
System.out.println(Thread.currentThread().getName() + "\t 班长关门");
//0 离开教室
//1 离开教室
//2 离开教室
//3 离开教室
//4 离开教室
//5 离开教室
//main 班长关门
}
private static void closeDoor() {
for (int i = 0; i <= 6; i++) {
new Thread(() -> System.out.println(Thread.currentThread().getName() + "\t 离开教室"), i + "").start();
}
System.out.println(Thread.currentThread().getName() + "\t 班长关门");
}
}
应用一,zookeeper 创建
public class ConnectingExample {
private static final Logger LOG = LoggerFactory.getLogger(ConnectingExample.class);
private static final int SESSION_TIMEOUT = 5000;
public ZooKeeper connect(String hosts) throws IOException, InterruptedException {
final CountDownLatch signal = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper(hosts, SESSION_TIMEOUT, event -> {
if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
signal.countDown();
}
});
signal.await();
return zk;
}
public static void main(String[] args) throws IOException, InterruptedException {
ConnectingExample example = new ConnectingExample();
ZooKeeper zk = example.connect("192.168.25.130:2181");//安装服务IP,端口号
LOG.info("ZK state: {}", zk.getState());
zk.close();
}
}
对于集群模式,另一种写法
public class ZookeeperSession {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private ZooKeeper zooKeeper;
private ZookeeperSession(){
// 去连接zookeeper server, 创建会话的时候,是异步进行的
// 所有要给一个监听器,说告诉我们什么时候是真正的完成跟zookeeper server 连接
try {
this.zooKeeper = new ZooKeeper("192.168.43.10:2181,192.168.43.11:2181,192.168.43.12:2181",
5000,
new ZookeeperWather());
log.info("### zookeeper server 连接成功, state={} ###", zooKeeper.getState());
connectedSemaphore.await();
} catch (Exception e) {
e.printStackTrace();
}
}
private class ZookeeperWather implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
if (Event.KeeperState.SyncConnected == watchedEvent.getState()) {
connectedSemaphore.countDown();
}
}
}
private static class SingleTon {
private static ZookeeperSession instance = new ZookeeperSession();
public static ZookeeperSession getInstance() {
return SingleTon.instance;
}
}
public static ZookeeperSession getInstance() {
return SingleTon.getInstance();
}
public static void init() {
getInstance();
}
}
CyclicBarrier
CyclicBarrier
的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
直到最后一个线程到达屏障时,屏障才会开门,所有
被屏障拦截的线程才会继续干活。
线程进入屏障通过CyclicBarrier的await()方法。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
System.out.println("人到齐了 开始开会");
});
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 进入会议室");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, i + "").start();
}
}
}
Semaphore
在信号量上我们定义两种操作:
acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
要么一直等下去,直到有线程释放信号量,或超时。
release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 模拟资源类有三个
for (int i = 0; i < 10 ; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 资源类减一
System.out.println(Thread.currentThread().getName() + "\t 抢到内存");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t 释放内存");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release(); // 资源类加一
}
}, i + "").start();
}
}
}