JDK源码分析系列--J.U.C并发工具类

本文从实际场景出发,对j.u.c包下的四个并发工具的使用进行演示。相信看完以后会对他们的区别和关联有更多了解。


CyclicBarrier

j.u.c.CyclicBarrier中文翻译循环栅栏,即可以在多个线程都执行完成后再触发,且支持循环使用。

场景一

下面场景是5个客人相约到饭店吃饭,只有都到了才能点餐。parties设置为5,会等待5个线程都执行到await()处才会进行下一步。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TestCyclicBarrier {

public static void main(String[] args) {

// parties设置为5
CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
() -> System.out.println("客人都已到达"));

// 创建5个线程
for (int i = 0; i < 5; i++) {
int finalI = i;
Runnable runnable = () -> {
try {
// 模拟5个人分别在第1-5分钟才到店
TimeUnit.SECONDS.sleep(finalI + 1);
System.out.println(Thread.currentThread().getName() + "到达");
// 5个人都到店后,立马点餐
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "点餐");

} catch (Exception e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.setName("客人" + (i + 1));
thread.start();
}
}
}

结果

5个人都到了才能点餐

1
2
3
4
5
6
7
8
9
10
11
客人1到达
客人2到达
客人3到达
客人4到达
客人5到达
客人都已到达
客人5点餐
客人1点餐
客人2点餐
客人4点餐
客人3点餐

5人都到了开始点餐

场景二

下面场景是拼单模式,只要有2个客人就能拼单成功,没有拼到就会一直等待。

parties设置为2,每当有2个线程执行到await()处就会进行下一步,第三轮由于只有一个线程,则会一直等待。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TestCyclicBarrier {

public static void main(String[] args) {

// parties设置为2
CyclicBarrier cyclicBarrier = new CyclicBarrier(2,
() -> System.out.println("客人都已到达"));

for (int i = 0; i < 5; i++) {
int finalI = i;
Runnable runnable = () -> {
try {
// 模拟5个人分别在第1-5分钟才到店
TimeUnit.SECONDS.sleep(finalI + 1);
System.out.println(Thread.currentThread().getName() + "到达");
// 每2个人到店后,就可以点餐
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "点餐");

} catch (Exception e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.setName("客人" + (i + 1));
thread.start();
}
}
}

结果

客人5只能看着

1
2
3
4
5
6
7
8
9
10
11
客人1到达
客人2到达
客人都已到达
客人2点餐
客人1点餐
客人3到达
客人4到达
客人都已到达
客人3点餐
客人4点餐
客人5到达

客人5无法点餐

CountDownLatch

j.u.c.CountDownLatch意为倒计时门闩,一般用作主线程等所有线程都执行完毕后,再执行自己的逻辑。或者等门闩开启,多个线程同时执行。

场景一

下面场景是等员工都下班了,保安负责给办公室关灯。

设定了count=5,每一个线程调用countDown(),则count–。当count=0时,门闩打开,给主线程放行。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
// 设定了5个门闩
CountDownLatch countDownLatch = new CountDownLatch(5);

for (int i = 0; i < 5; i++) {
int finalI = i;
Runnable runnable = () -> {
try {
// 模拟5个员工分别在第1-5分钟才下班
TimeUnit.SECONDS.sleep(finalI + 1);
System.out.println(Thread.currentThread().getName() + "下班");
// 每调用一次,则count--
countDownLatch.countDown();

} catch (Exception e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.setName("员工" + (i + 1));
thread.start();
}

// 模拟保安等员工全部下班了才关灯
System.out.println("下班时间到,保安在等待关灯。。。");
countDownLatch.await();
System.out.println("关灯");

}
}

结果一

1
2
3
4
5
6
7
下班时间到,保安在等待关灯。。。
员工1下班
员工2下班
员工3下班
员工4下班
员工5下班
关灯

所有人都下班了才关灯

结果二

如果不设置门闩,运行结果是

1
2
3
4
5
6
7
下班时间到,保安在等待关灯。。。
关灯
员工1下班
员工2下班
员工3下班
员工4下班
员工5下班

下班直接关灯

这样就得摸黑下班了( ´•̥ו̥` )

场景二

下面场景是员工陆续下班后,等待班车发车,然后一起出发。

设定了count=1,当执行了countDown(),所有阻塞在await()的线程会同时启动

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class TestCountDownLatch2 {
public static void main(String[] args) throws InterruptedException {
// 设定了1个门闩
CountDownLatch countDownLatch = new CountDownLatch(1);

for (int i = 0; i < 5; i++) {
int finalI = i;
Runnable runnable = () -> {
try {
// 模拟5个员工分别在第1-5分钟才下班
TimeUnit.SECONDS.sleep(finalI + 1);
System.out.println(Thread.currentThread().getName() + "下班,等车中");
// 会阻塞在这里
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "出发");

} catch (Exception e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.setName("员工" + (i + 1));
thread.start();
}

// 模拟在第6分钟时发车
TimeUnit.SECONDS.sleep(6);
System.out.println("到点了");
countDownLatch.countDown();
}
}

结果

早下班也没用,班车没来

1
2
3
4
5
6
7
8
9
10
11
员工1下班,等车中
员工2下班,等车中
员工3下班,等车中
员工4下班,等车中
员工5下班,等车中
到点了
员工1出发
员工5出发
员工4出发
员工2出发
员工3出发

到点发车

Semaphore

j.u.c.Semaphore意为信号量,可以理解为资源许可证,只有拿到许可证的线程,才能执行对应的工作,在执行完后需要归还。

场景一

下面场景是公司的餐厅座位有限,员工只能排队用餐。

设置permits=2,表示只有2张许可证,拿不到许可的线程只能等待

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) {

// 模拟餐厅有2个座位
Semaphore semaphore = new Semaphore(2);

for (int i = 0; i < 5; i++) {
Runnable runnable = () -> {
try {
// 占了一个座位
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "到达餐厅开始吃饭");
// 吃饭耗时2秒
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "吃完了离开餐厅");
// 空出来一个座位
semaphore.release();

} catch (Exception e) {
e.printStackTrace();
}
};

Thread thread = new Thread(runnable);
thread.setName("员工" + (i + 1));
thread.start();
}
}

结果

到底什么公司这么坑啊!!

1
2
3
4
5
6
7
8
9
10
员工1到达餐厅开始吃饭
员工2到达餐厅开始吃饭
员工2吃完了离开餐厅
员工1吃完了离开餐厅
员工3到达餐厅开始吃饭
员工4到达餐厅开始吃饭
员工4吃完了离开餐厅
员工3吃完了离开餐厅
员工5到达餐厅开始吃饭
员工5吃完了离开餐厅

最多只有2人同时在餐厅

Exchanger

j.u.c.Exchanger意为交换机,可以使两个线程在执行exchange()时交换信息。

未完待续。。。

场景一

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class TestExchanger {

public static void main(String[] args) {

Exchanger<Object> exchanger = new Exchanger<>();

Runnable runnable = () -> {
try {
String field = "咖啡";
Object o = exchanger.exchange(field);
System.out.println(Thread.currentThread().getName() + " 用 " + field + " 换了 " + o);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.setName("员工A");
thread.start();

Runnable runnable2 = () -> {
try {
String field = "奶茶";
Object o = exchanger.exchange(field);
System.out.println(Thread.currentThread().getName() + " 用 " + field + " 换了 " + o);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread2 = new Thread(runnable2);
thread2.setName("员工B");
thread2.start();
}
}

结果

1
2
员工B 用 奶茶 换了 咖啡
员工A 用 咖啡 换了 奶茶

两个线程交换信息

文章作者: SongGT
文章链接: http://www.songguangtao.xyz/2024/02/05/32.JDK源码分析系列--J.U.C并发工具类/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 SongGuangtao's Blog
大哥大嫂[微信打赏]
过年好[支付宝打赏]