0.总结
1.CountDownLatch是1个线程等待其他线程,CyclicBarrier是多个线程相互等待;
2.CountDownLatch是计数-1,直到减为0,CyclicBarrier是计数+1,直到达到指定值;
3.CountDownLatch是一次性的,CyclicBarrier是循环使用的;
4.CyclicBarrier可以在最后一个线程到达后,选择1执行1个优先操作;
5.Semaphore,需要拿到锁才能执行,并且可以选择公平、非公平的模式
1.CountDownLatch——基于AQS实现
它是一个同步辅助器,允许一个或多个线程一直等待,直到一组在其他线程执行的操作全部完成。
构造方法
会传入一个 count 值,用于计数。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
常用的方法有2个:await、countDown
- 1.await方法
当一个线程调用await方法时,就会阻塞当前线程。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- 2.countDown 方法
每当有线程调用一次 countDown 方法时,计数就会减 1。当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。
public void countDown() {
sync.releaseShared(1);
}
场景设计
CountDownLatch阻塞的是主线程。
- 主线程:领导
- 分支线程:多个worker
现在设想一个场景,公司项目,线上出现了一个紧急 bug,被客户投诉,领导焦急的过来,想找人迅速的解决这个 bug 。
那么,一个人解决肯定速度慢啊,于是领导(主线程)叫来张三和李四,一起分工解决。终于,当他们两个都做完了自己所需要做的任务之后,领导(主线程)才可以答复客户,客户也就消气了。
于是,我们可以设计一个 Worker 类(分支线程类)来模拟单个人修复 bug 的过程,领导(主线程)一直等待所有 Worker 任务执行结束,主线程才可以继续往下走。
- bug如果1个人修复,需要耗时5秒,现在张三干2秒,李四干3秒,且并发进行,则只需要3秒即可完工。
public class CountDownTest {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
Worker w1 = new Worker("张三", 2000, latch);
Worker w2 = new Worker("李四", 3000, latch);
w1.start();
w2.start();
long startTime = System.currentTimeMillis();
latch.await();
System.out.println("bug全部解决,领导可以给客户交差了,任务总耗时:"+ (System.currentTimeMillis() - startTime));
}
static class Worker extends Thread{
String name;
int workTime;
CountDownLatch latch;
public Worker(String name, int workTime, CountDownLatch latch) {
this.name = name;
this.workTime = workTime;
this.latch = latch;
}
@Override
public void run() {
System.out.println(name+"开始修复bug,当前时间:"+sdf.format(new Date()));
doWork();
System.out.println(name+"结束修复bug,当前时间:"+sdf.format(new Date()));
latch.countDown();
}
private void doWork() {
try {
//模拟工作耗时
Thread.sleep(workTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.CyclicBarrier—基于Reentrantlock实现
一组线程会互相等待,直到所有线程都到达一个同步点。
* 就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。
1.两种构造方法
1.第1个参数parties(int型),指的是需要几个线程一起到达,才可以使所有线程取消等待。
public CyclicBarrier(int parties) {
this(parties, null);
}
2.构造方法2多了第2个参数barrierAction(Runnable型),用于在所有线程达到屏障时,优先执行 barrierAction。
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
2.常用方法——await()
- await();
在下边的例子中,执行1次barrier.await(),计数器+1,等到计数器值达到CyclicBarrier设定值时(本例中是3),那大家一起冲破屏障。
3.场景模拟
1.跑步比赛(忽略裁判)——大家都准备好了就一起跑
- 使用构造方法1
class BarrierTest {
public static void main(String[] args) {
1.参数:4,质需要4个线程到达才可以开跑
CyclicBarrier barrier = new CyclicBarrier(4); //①
2.创建4个运动员:张三、李四、王五、赵六
Runner runner1 = new Runner(barrier, "张三");
Runner runner2 = new Runner(barrier, "李四");
Runner runner3 = new Runner(barrier, "王五");
Runner runner4 = new Runner(barrier, "赵六");
3..创建线程池
ExecutorService service = Executors.newFixedThreadPool(4);
service.execute(runner1);
service.execute(runner2);
service.execute(runner3);
service.execute(runner4);
service.shutdown();
}
}
2.运动员类
class Runner implements Runnable{
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
1.模拟准备耗时
Thread.sleep(new Random().nextInt(5000));
System.out.println(name + ":准备OK");
2.屏障等待
barrier.await();
System.out.println(name +": 开跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
}
}
2.跑步比赛–准备好后,等待裁判吹哨子
-
使用构造方法2——加入优先执行action(本例是:等待裁判吹哨子)
所有运动员都准备好之后,再,等待裁判吹哨子。 -
上边的代码修改如下:
1.创建等待裁判的动作
Judge waiteJudge = new Judge();
2.调用构造方法2
CyclicBarrier barrier = new CyclicBarrier(4,waiteJudge);
3.建立裁判Juge类
class Judge implements Runnable{
@Override
public void run() {
try {
System.out.println("等裁判吹口哨...");
这里停顿两秒更便于观察线程执行的先后顺序
Thread.sleep(2000);
System.out.println("裁判吹口哨->>>>>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.CyclicBarrier的循环使用
张三、李四、王五、赵六,4个人分两批开跑。
- 代码修改部分:CyclicBarrier 传入参数是2,线程池核心线程数也是2
.
2个人开跑后(即释放2个核心线程后),另外2个人才开始准备。
CyclicBarrier barrier = new CyclicBarrier(2,waiteJudge);
ExecutorService service = Executors.newFixedThreadPool(2);
3.Semaphore
Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。
1.构造方法(默认是非公平锁)
方法1,参数permits代表信号量个数;
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
方法2:参数boolean fair,为true,表示使用公平锁;false:表示非公平锁。
(公平锁的机制是通过AQS实现)
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
2.场景设计
- 20辆车经过1座桥,每次只能同时有5辆车在桥上,超过5辆就会导致桥梁垮塌。
- 基于Semaphore来设置5把锁,车拿到锁可以上桥,过完桥就归还锁,然后后边的车再取锁。
class SemaphoreTest {
1.总共20辆车
private static int count = 20;
public static void main(String[] args) {
2.建立核心线程数
ExecutorService executorService = Executors.newFixedThreadPool(count);
3.利用信号量:指定最多只能有4个线程同时执行
Semaphore semaphore = new Semaphore(5);
4.random 模拟每辆车的耗时时间
Random random = new Random();
5.依次提交20个线程到线程池
for (int i = 0; i < count; i++) {
final int no = i;
Car car = new Car(semaphore,no,random);
executorService.execute(car);
}
6.关闭线程池
executorService.shutdown();
}
}
1.新建汽车类
class Car implements Runnable{
Semaphore semaphore;
int no;
Random random;
public Car(Semaphore semaphore, int no, Random random) {
this.semaphore = semaphore;
this.no = no;
this.random = random;
}
@Override
public void run() {
try {
1.获得许可
semaphore.acquire();
System.out.println(no +":号车可通行");
2.模拟车辆通行耗时
Thread.sleep(random.nextInt(2000));
3.释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}