java.util.concurrent并发包中提供了一系列的的同步工具类,这些基础类不管是否能在项目中使用到,了解一下使用方法和原理对java程序员来说都是有必要的。
CyclicBarrier(栅栏)
4个朋友约好下班一起玩吃鸡,分别是M4,AWM,SKS,WIN94。这四个哥们下班时间不一样,决定好一个时间一起上号搞。
大家约好到家就开游戏,必须珍惜生命,争分夺秒玩游戏。
我们用栅栏来模仿一下场景:
1 public class CyclicBarrierTest {
2
3 private static CyclicBarrier barrier = new CyclicBarrier(4);
4
5 private static class EatChickenPlayer extends Thread {
6
7 private String name;
8
9 //下班时间
10 private Long offWorkTime;
11
12 public EatChickenPlayer(String name, Long time) {
13 this.name = name;
14 this.offWorkTime = time;
15 }
16
17 @Override
18 public void run() {
19 Timer timer = new Timer();
20 timer.schedule(new TimerTask() {
21 @Override
22 public void run() {
23 System.out.println(name + ":我上号了");
24 try {
25 barrier.await();
26 System.out.println(name + ":开打开打");
27 } catch (Exception e) {
28 e.printStackTrace();
29 }
30 }
31 }, offWorkTime);
32 }
33 }
34
35 public static void main(String[] args) {
36 EatChickenPlayer m416 = new EatChickenPlayer("m416", 3000L);
37 EatChickenPlayer AWM = new EatChickenPlayer("AWM", 6000L);
38 EatChickenPlayer SKS = new EatChickenPlayer("SKS", 9000L);
39 EatChickenPlayer win94 = new EatChickenPlayer("win94", 4000L);
40 m416.start();
41 AWM.start();
42 SKS.start();
43 win94.start();
44 }
45 }
运行一下代码,感受一下,
栅栏通常阻塞一系列线程,当所有需要的线程都达到栅栏位置,才能继续执行。所以,栅栏可以理解为等待其他的线程到达对应的位置,在一起执行。
在做一些并发测试的时候,有时候需要所有线程都执行到相应的位置,让它们同时执行。而且栅栏是可以重复利用的,当栅栏开放后,栅栏会进行重置,然后对后续的线程进行拦截。如果await调用超时,或者await的线程被中断,栅栏就被认为是打破了,所有await的线程都会中止并且抛出BrokenBarrierException。如果成功通过栅栏,那么await将为每个线程返回一个唯一的到达索引号,我们可以通过索引来选举一个领导线程。并在下一次循环中,通过领导线程执行一些特殊工作。
CountDownLatch(闭锁)
好不容易,四个人都到到齐了,纷纷上号。要进行游戏,必须等队伍里所有人都点击准备完成,游戏才能开始。这种时候可以时候,我们可以使用闭锁来实现这种业务场景。
1 public class CountDownLatchTest {
2
3 private static CountDownLatch countDownLatch = new CountDownLatch(4);
4
5 public static class EatChicken extends Thread {
6
7 private String name;
8
9 private int time;
10
11 public EatChicken(String name, int time) {
12 this.name = name;
13 this.time = time;
14 }
15
16 @Override
17 public void run() {
18 System.out.println(name + "准备" + time + "秒");
19 try {
20 SECONDS.sleep(time);
21 } catch (InterruptedException e) {
22 e.printStackTrace();
23 }
24 System.out.println(name + "衣服换好了");
25 countDownLatch.countDown();
26 }
27 }
28
29 public static void main(String[] args) throws InterruptedException {
30 System.out.println("-----等待所有人准备-----");
31 EatChicken m416 = new EatChicken("m416", 1);
32 EatChicken AWM = new EatChicken("AWM", 2);
33 EatChicken SKS = new EatChicken("SKS", 2);
34 EatChicken win94 = new EatChicken("win94", 3);
35 m416.start();
36 AWM.start();
37 SKS.start();
38 win94.start();
39
40 countDownLatch.await();
41 System.out.println("-----所有人准备好了-----");
42 System.out.println("-----游戏开始-----");
43 }
运行程序:
闭锁初看跟栅栏非常相似,它们作为工具类,都能作为屏障,等待线程执行到一定的地方。闭锁跟栅栏的区别,就是闭锁是一次性的,当闭锁完全打开后就不能关闭了。栅栏打开之后,放行等待之后,栅栏就被重置,等待下一次开启。闭锁使用countDown()的时候,线程不会阻塞,继续运行。栅栏没有类似的countDown()方法,使用await()的时候,还需要等待的线程数-1,直到需要等待的线程数为0的时候,栅栏打开。
Semaphore(信号量)
有的时候,不仅这四位老哥去玩,时不时的M24也要来一起玩。这个时间,就只能大家一起抢位置了。没能挤进队伍的老哥内心百感交集,欲说还休。
我们可以用信号量来模拟。
1 public class SemaphoreTest {
2
3 private static Semaphore semaphore = new Semaphore(4);
4
5 private static class EatChickenPlayer extends Thread {
6
7 private String name;
8
9
10 public EatChickenPlayer(String name) {
11 this.name = name;
12 }
13
14 @Override
15 public void run() {
16 if (semaphore.tryAcquire()) {
17 System.out.println(name + ":我进入游戏啦");
18 } else {
19 System.out.println(name + ":卧槽,队伍满了");
20 }
21 }
22 }
23
24 public static void main(String[] args) throws InterruptedException {
25 EatChickenPlayer m416 = new EatChickenPlayer("m416");
26 EatChickenPlayer AWM = new EatChickenPlayer("AWM");
27 EatChickenPlayer SKS = new EatChickenPlayer("SKS");
28 EatChickenPlayer win94 = new EatChickenPlayer("win94");
29 EatChickenPlayer M24 = new EatChickenPlayer("M24");
30 m416.start();
31 AWM.start();
32 SKS.start();
33 win94.start();
34 M24.start();
35 }
运行一下代码:
信号量用来控制同时访问某特定资源的操作数量。通过acquire()阻塞获取资格。或者使用tryAcquire()方法获取,及时返回结果,获取权限成功则返回ture,获取失败返回false。当权限已经使用完毕后,调用release()或者release(int permits) 方法释放权限。
FutureTask
游戏终于开始了,落地的时候,发现有四个房子,四位胸怀吃鸡的老哥分配好各走一个房子收集里面的东西。收集出来之后一起分东西。这里把每个老哥去房子里舔装备作为一个Task。我们用FutureTask来模拟一下这个场景:
1 package com.chinaredstar.jc.lock;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Map;
6 import java.util.Random;
7 import java.util.concurrent.*;
8 import java.util.stream.Collectors;
9
10 public class FutureTaskTest {
11
12 /**
13 * 固定的4线程,线程池
14 */
15 private static ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(4);
16
17 static class Equipment {
18 /**
19 * 这里简单模拟一下,
20 * 1、子弹 2、枪械 3、药包
21 */
22 private Integer type;
23
24 /**
25 * 数量
26 */
27 private Integer num;
28
29 public Equipment(Integer type, Integer num) {
30 this.type = type;
31 this.num = num;
32 }
33
34 public Integer getType() {
35 return type;
36 }
37
38 public void setType(Integer type) {
39 this.type = type;
40 }
41
42 public Integer getNum() {
43 return num;
44 }
45
46 public void setNum(Integer num) {
47 this.num = num;
48 }
49
50
51 }
52
53 /**
54 * 舔装备人物类
55 */
56 static class CollectTask implements Callable {
57 private String name;
58
59 public CollectTask(String name) {
60 this.name = name;
61 }
62
63 @Override
64 public List<Equipment> call() throws Exception {
65 return generatorEquipment(name);
66 }
67 }
68
69 public static List<Equipment> generatorEquipment(String name) {
70
71 List<Equipment> list = new ArrayList<>();
72
73 Random r = new Random();
74 //子弹
75 Equipment bullet = new Equipment(1, r.nextInt(100));
76 list.add(bullet);
77 System.out.println(name + ":捡到子弹" + bullet.num + "发");
78 //枪
79 Equipment gun = new Equipment(2, r.nextInt(3));
80 System.out.println(name + ":捡到枪" + gun.num + " 把");
81 list.add(gun);
82 //药包
83 Equipment bandage = new Equipment(3, r.nextInt(10));
84 System.out.println(name + ":捡到绷带" + bandage.num + " 个");
85 list.add(bandage);
86 return list;
87 }
88
89 public static void main(String[] args) throws ExecutionException, InterruptedException {
90 //生成任务
91 CollectTask AWM = new CollectTask("AWM");
92 CollectTask m416 = new CollectTask("m416");
93 CollectTask SKS = new CollectTask("SKS");
94 CollectTask win94 = new CollectTask("win94");
95 //生成任务交给线程池
96 Future AWM_future = threadPoolExecutor.submit(AWM);
97 Future m416_future = threadPoolExecutor.submit(m416);
98 Future SKS_future = threadPoolExecutor.submit(SKS);
99 Future win94_future = threadPoolExecutor.submit(win94);
100
101 //结果放在一起
102 List<Future> futureList = new ArrayList<>();
103 futureList.add(AWM_future);
104 futureList.add(m416_future);
105 futureList.add(SKS_future);
106 futureList.add(win94_future);
107
108 //结果统一在一起
109 List<Equipment> taskResultList = new ArrayList<>();
110 for (Future<List<Equipment>> future : futureList) {
111 taskResultList.addAll(future.get());
112 }
113
114 //打印出来看看都有些啥
115 Map<Integer, Integer> tolal = taskResultList.stream().collect(Collectors.groupingBy(
116 Equipment::getType, Collectors.summingInt(Equipment::getNum)));
117 tolal.entrySet();
118 for (Map.Entry<Integer, Integer> entry : tolal.entrySet()) {
119 if (entry.getKey() == 1) {
120 System.out.println("总共有子弹:" + entry.getValue());
121 }
122 if (entry.getKey() == 2) {
123 System.out.println("总共有枪:" + entry.getValue());
124 }
125 if (entry.getKey() == 3) {
126 System.out.println("总共有药包:" + entry.getValue());
127 }
128 }
129
130 }
131
132 }
运行后的代码结果: