lancao

上次简单了解了多线程中锁的类型,今天要简单了解下多线程并发控制的一些工具类了。

1. 概念说明:

CountDownLatch:相当于一个待执行线程计数器,当计数减为零时表示所有待执行线程都已执行完毕,等待被计数线程结果的(即使用await()的)线程可以继续执行(await()后面的代码)了。

CyclicBarrier:可循环屏障,相当于一个准备执行线程计数器,当计数减为零时表示所有待执行线程(这些线程需要同时开始工作)都已做好准备工作,可以开始工作了。

Semaphore:允许并发线程数,semaphore.acquire()和semaphore.release()方法总是成对出现,而这对方法之间的代码最多允许多少个线程访问是由Semaphore semaphore=new Semaphore(n)的n决定的。

2. 构造实际场景,使用示例

设想一个赛跑场景,一共有10个运动员参赛,所有运动员必须同时起跑(CyclicBarrier控制),而计分结束必须在所有运动员都跑完全程之后(CountDownLatch控制),但是学校操场只有4条跑道(Semaphore),那么就要让运动员分三批进行比赛,每次最多4人入场:

 1 import com.alibaba.fastjson.JSONObject;
 2 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 3 import org.junit.Test;
 4 
 5 import java.util.concurrent.*;
 6 
 7 public class MultiThreadsTest {
 8     private volatile ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
 9     // 声明一个线程池,用于线程管理
10     ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 4,
11             20, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(),
12             new BasicThreadFactory.Builder().namingPattern("Runner-%d").build(),
13             new ThreadPoolExecutor.CallerRunsPolicy());
14 
15     @Test
16     public void testMultiControl() {
17         // 跑道数
18         int runways = 4;
19         // 参赛总人数
20         int runners = 10;
21         // 参赛人数多于跑道数,需分批比赛
22         int batch = 1;
23         // 当运动员总数多于跑道数,最多runways人入场
24         while (runners >= runways) {
25             runningGame(batch++, runways);
26             runners = runners - runways;
27         }
28         // 最后一批,还剩几个人,就几个人入场
29         runningGame(batch, runners);
30         System.out.println("\n所有选手都已跑完,现在宣布比赛结果:\n" + JSONObject.toJSONString(map));
31     }
32 
33     private void runningGame(int batch, int runners) {
34         System.out.println("第" + batch + "批,共" + runners + "人,运动员正在进场...");
35         // 每批允许runways个运动员入场
36         Semaphore semaphore = new Semaphore(runners);
37         // 每批runways个运动员必须都准备好,同时起跑
38         CyclicBarrier barrier = new CyclicBarrier(runners);
39         // 每批runways个运动员都跑完全程,裁判这一批计分结束
40         CountDownLatch latch = new CountDownLatch(runners);
41         for (int i = runners; i > 0; i--) {
42             // 每个运动员都是一个独立奔跑的线程
43             executor.execute(() -> {
44                 try {
45                     // 每个运动员进场都会占用一个跑道
46                     semaphore.acquire();
47                     // 每个运动员就位都会通知barrier,等所有跑道上的运动员都就位,大家就一起跑!
48                     barrier.await();
49                     running(Thread.currentThread().getName());
50                     // 每个运动员跑完都要让出自己的跑道(为下一批运动员让路)
51                     semaphore.release();
52                 } catch (InterruptedException e) {
53                     e.printStackTrace();
54                 } catch (BrokenBarrierException e) {
55                     e.printStackTrace();
56                 } finally {
57                     // 每个运动员跑完都要通知裁判为自己计分
58                     latch.countDown();
59                 }
60             });
61         }
62         try {
63             // 跑道上所有运动员都跑完,则裁判计分结束
64             latch.await();
65             System.out.println("第" + batch + "批,共" + runners + "人,计分结束");
66         } catch (InterruptedException e) {
67             System.out.println(e.getStackTrace());
68         }
69     }
70 
71     private void running(String name) {
72         long start = System.currentTimeMillis();
73         System.out.println(name + " running started ...");
74         System.out.println(name + ": I'm running.\taudience: Yes, I see.\t" + name + ":I'm done running.\taudience: Well done!");
75         try {
76             // 跑太快,歇会儿
77             TimeUnit.MILLISECONDS.sleep(2);
78         } catch (InterruptedException e) {
79             e.printStackTrace();
80         }
81         System.out.println(name + " running completed");
82         // 裁判将所有运动员的分数都记录在map中
83         map.put(name, (System.currentTimeMillis() - start) + "ms");
84     }
85 }

运行结果

 

 

3. 代码运行结果分析:

乍看上面代码和运行结果好像都没什么问题,每批允许4人进场,总是同时开始跑,也都等到每批选手都跑完后该批计分结束,countDownLatch和CyclicBarrier确实都起到了各自的作用,但是Semaphore呢?好像也确实是限制了只允许4个线程同时访问这段代码,然而在你一次只有4个选手的情况下,它当然是最多4个线程访问了,其实Semaphore的工作已经由方法入参给限定了啊,Semaphore的作用何在呢?那么假如学校原本有4个跑道,但是比赛开始之后却发现有一个入场通道坏了,变成了每次只能有3个人入场,但是场内的裁判并不知道,还在等待着按每4人一批进行比赛,那将会是什么结果呢?

我们修改代码看下:其他部分不变,只把允许入场的运动员数减1,然后再运行Test发现程序一直处于“第一批运动员进场”过程中,说明Semaphore这个变量已经起到了它的作用,就是每次只允许3个运动员进场,而且放入3个运动员后就会进行等待,等待这3个运动员比赛结束再放下一批进场,而场内的裁判并不知道可入场人数已经变化,还在等待着第4名选手入场才吹响开跑号声,因此入场管理员Semaphore和起跑裁判官CyclicBarrier就会陷入无法终止的相互等待,程序一直无法打破这种等待循环,因而进入停滞状态,这就是一种简单的死锁场景了。

 

 

 

 

 

 

 4. 总结

这里只是简单说明这三个工具有用法,场景并不十分合理,其实这三者通常独立使用,一般不会碰到三者同时上场的机会。

 

分类:

技术点:

相关文章:

  • 2021-08-01
  • 2019-05-16
  • 2021-08-06
  • 2019-02-18
  • 2021-09-15
  • 2018-03-15
  • 2021-12-21
猜你喜欢
  • 2021-07-28
  • 2021-07-09
  • 2020-04-29
  • 2019-11-03
  • 2021-08-06
  • 2021-12-22
  • 2021-11-10
相关资源
相似解决方案