CountdownLatch 闭锁
用来控制一个线程等待多个线程。
维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
代码例子参考如下博客http://www.cnblogs.com/cz123/p/7503545.html
设想这样的场景,有一个线程要统计结果,结果是由四个线程分别的处理结果汇总起来的。这时候统计结果线程需要等四个处理线程结束才可以开始统计。实现这一点要靠CountdownLatch,在统计结果的线程中执行CountdownLatch.await()方法,让统计线程进入等待队列。等到每个处理数据线程都执行完后,CountdowLatch.countDown()方法,等计数器减到0的时候,唤醒调用await()方法而等待的线程。
CountDownLatch是通过“共享锁”实现的,在创建CountDownLatch时,会传递一个int类型参数,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count个线程同时获取,这个值只能被设置一次。主线程必须在启动其他线程后立即调用await()方法。这样主线程的操作就会在这个方法上阻塞,知道其他线程完成各自的任务。当某线程调用CountDownLatch对象的await()方法时,该线程会等待共享锁可用时,才会获取共享锁二继续运行。共享锁的可用条件,就是锁计数器的值为0。每当一个线程调用该CountDoenLatch对象的countDown()方法时,才将“锁计数器”-1。
package countDownLatch;
import java.util.concurrent.CountDownLatch;
/**
* Created by on 2018/6/8.
* http://www.cnblogs.com/cz123/p/7503545.html
*/
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(4);
new Thread() {
@Override
public void run() {
System.out.println("加工线程:" + Thread.currentThread().getName() + "开始等待数据");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据都到齐了!!!");
}
}.start();
for (int i=0; i<4; i++) {
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始统计数据");
try {
sleep(1000); // 模拟统计过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "统计完成");
latch.countDown();
}
}.start();
}
}
}
上面的代码也可以这么写,效果是一样的。来源于参考链接中的GitHub上的例子。
package countDownLatch;
import java.util.concurrent.CountDownLatch;
/**
* Created by on 2018/6/8.
* http://www.cnblogs.com/cz123/p/7503545.html
*/
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(4);
new Thread() {
@Override
public void run() {
System.out.println("加工线程:" + Thread.currentThread().getName() + "开始等待数据");
for (int i=0; i<4; i++) {
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始统计数据");
try {
sleep(1000); // 模拟统计过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "统计完成");
latch.countDown();
}
}.start();
}
try {
System.out.println("运行到await()");
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据都到齐了!!!");
}
}.start();
}
}
运行结果
加工线程:Thread-0开始等待数据
Thread-1开始统计数据
Thread-2开始统计数据
Thread-3开始统计数据
Thread-4开始统计数据
Thread-1统计完成
Thread-3统计完成
Thread-2统计完成
Thread-4统计完成
数据都到齐了!!!
package countDownLatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2018/7/1 0001.
*/
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int totalThread = 4;
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.println("run");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
2、CyclicBarrier 循环屏障
CountDownLatch和CyclicBarrier的区别
(1)CountDownLatch的作用是允许1个线程等待其他线程执行完成后,它才执行。二CyclicBarrier允许N个线程相互等待到某个公共屏障点,然后一组线程再同时执行。
(2)CountDownLatch的计数器的值无法被重置,这个初始值只能被设置一次,是不能够重用的。CyclicBarrier可以重用。
package countDownLatch;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* Created by on 2018/6/8.
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(4);
for (int i=0; i<4; i++) {
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始检查文件");
try {
sleep(2000); // 模拟检查过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "检查文件结束,等待其他线程");
try {
barrier.await(); // 等待
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "统计");
}
}.start();
}
}
}
Thread-3开始检查文件
Thread-1开始检查文件
Thread-2开始检查文件
Thread-0开始检查文件
Thread-0检查文件结束,等待其他线程
Thread-2检查文件结束,等待其他线程
Thread-3检查文件结束,等待其他线程
Thread-1检查文件结束,等待其他线程
Thread-1统计
Thread-3统计
Thread-0统计
Thread-2统计
3、Semaphore 信号量
可以控制某个资源可悲同时访问的个数,通过构造函数设定一定数量的许可,通过acquire()获取一个许可,如果没有就等待,二release()释放一个许可。
package AQS;
import java.util.concurrent.Semaphore;
/**
* Created by Administrator on 2018/7/20 0020.
*/
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore windows = new Semaphore(5); // 声明5个窗口
for (int i = 0; i < 8; i++) {
new Thread() {
@Override
public void run() {
try {
windows.acquire(); // 占用窗口
System.out.println(Thread.currentThread().getName() + ": 开始买票");
sleep(2000); // 睡2秒,模拟买票流程
System.out.println(Thread.currentThread().getName() + ": 购票成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
windows.release(); // 释放窗口
}
}
}.start();
}
}
}
Thread-2: 开始买票
Thread-0: 开始买票
Thread-1: 开始买票
Thread-3: 开始买票
Thread-4: 开始买票
Thread-2: 购票成功
Thread-3: 购票成功
Thread-0: 购票成功
Thread-1: 购票成功
Thread-5: 开始买票
Thread-6: 开始买票
Thread-7: 开始买票
Thread-4: 购票成功
Thread-6: 购票成功
Thread-5: 购票成功
Thread-7: 购票成功