Skip to content

Commit 2fe6b9a

Browse files
committed
thread
1 parent 70035c2 commit 2fe6b9a

554 files changed

Lines changed: 942 additions & 73 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/.vuepress/dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit 452fc3c06b193dbb0f5845e7014120abe63dc410
1+
Subproject commit ad6ee90be95fa08615671497b33a84874117c823

docs/framework/SpringCloud/sidebar.md

Lines changed: 0 additions & 51 deletions
This file was deleted.

docs/java/JUC/多个线程顺序执行问题.md

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1+
全文 2929 字,剩下的是代码,P6 及以下阅读只需要 5 分钟,高P 请直接关闭
2+
13
# 手撕面试题:多个线程顺序执行问题
24

3-
最近换工作,除了一些常规算法题,还会遇到各种需要手写的题目,所以打算总结出来,给大家个参考。
5+
大家在换工作面试中,除了一些常规算法题,还会遇到各种需要手写的题目,所以打算总结出来,给大家个参考。
46

5-
第一篇打算总结下阿里最喜欢问的多个线程顺序打印问题,我遇到的是机试,直接写出运行。同类型的题目,比如
7+
第一篇打算总结下阿里最喜欢问的多个线程顺序打印问题,我遇到的是机试,直接写出运行。同类型的题目有很多,比如
68

7-
1. 三个线程分别打印A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串
9+
1. 三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串
810
2. 两个线程交替打印 0~100 的奇偶数
911
3. 通过 N 个线程顺序循环打印从 0 至 100
1012
4. 多线程按顺序调用,A->B->C,AA 打印 5 次,BB 打印10 次,CC 打印 15 次,重复 10 次
1113
5. 用两个线程,一个输出字母,一个输出数字,交替输出 1A2B3C4D...26Z
1214

13-
其实这类题目考察的都是**线程间的通信问题**,基于这类题目,做一个整理,方便日后手撕面试官,说的有点残暴,应该是手撕面试题。
14-
15+
其实这类题目考察的都是**线程间的通信问题**,基于这类题目,做一个整理,方便日后手撕面试官,文明的打工人,手撕面试题。
1516

17+
![](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20201029114231.jpg)
1618

1719
## 使用 Lock
1820

19-
我们以第一题为例:三个线程分别打印A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串。
21+
我们以第一题为例:三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串。
2022

2123
思路:使用一个取模的判断逻辑 **C%M ==N**,题为 3 个线程,所以可以按取模结果编号:0、1、2,他们与 3 取模结果仍为本身,则执行打印逻辑。
2224

23-
main 方法启动后,3 个线程会抢锁,但是 state 的初始值为 0,所以第一次执行 if 语句的内容只能是 **线程 A**,然后还在 for 循环之内,此时 `state = 1`,只有 **线程 B** 才满足 `1% 3 == 1`,所以第二个执行的是 B,同理只有 **线程 C** 才满足 `2% 3 == 2`,所以第三个执行的是 C,执行完 ABC 之后,才去执行第二次 for 循环,所以要把 i++ 写在 for 循环里边,不能写成 `for (int i = 0; i < times;i++)` 这样。
24-
2525
```java
2626
public class PrintABCUsingLock {
2727

@@ -63,6 +63,8 @@ public class PrintABCUsingLock {
6363
}
6464
```
6565

66+
main 方法启动后,3 个线程会抢锁,但是 state 的初始值为 0,所以第一次执行 if 语句的内容只能是 **线程 A**,然后还在 for 循环之内,此时 `state = 1`,只有 **线程 B** 才满足 `1% 3 == 1`,所以第二个执行的是 B,同理只有 **线程 C** 才满足 `2% 3 == 2`,所以第三个执行的是 C,执行完 ABC 之后,才去执行第二次 for 循环,所以要把 i++ 写在 for 循环里边,不能写成 `for (int i = 0; i < times;i++)` 这样。
67+
6668

6769

6870
## 使用 wait/notify
@@ -120,7 +122,7 @@ public class PrintABCUsingWaitNotify {
120122

121123
使用对象监视器实现,两个线程 A、B 竞争同一把锁,只要其中一个线程获取锁成功,就打印 ++i,并通知另一线程从等待集合中释放,然后自身线程加入等待集合并释放锁即可。
122124

123-
![img](https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/201906/i-p-d-t-p-o-e-logo.png)
125+
![图:throwable-blog](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20201029160341.png)
124126

125127
```java
126128
public class OddEvenPrinter {
@@ -209,13 +211,13 @@ public class NumAndLetterPrinter {
209211

210212

211213

212-
213-
214214
## 使用 Lock/Condition
215215

216-
还是以第一题为例,使用 Condition 的思路,其实和 wait/notify 的思路一样。
216+
还是以第一题为例,使用 Condition 来实现,其实和 wait/notify 的思路一样。
217217

218-
> Condition中的 `await()` 方法相当于 Object 的 `wait()` 方法,Condition 中的 `signal()` 方法相当于Object 的 `notify()` 方法,Condition 中的 `signalAll()` 相当于 Object 的 `notifyAll()` 方法。不同的是,Object 中的 `wait(),notify(),notifyAll()`方法是和`"同步锁"`(synchronized关键字)捆绑使用的;而Condition 是需要与`"互斥锁"/"共享锁"`捆绑使用的。
218+
> Condition 中的 `await()` 方法相当于 Object 的 `wait()` 方法,Condition 中的 `signal()` 方法相当于Object 的 `notify()` 方法,Condition 中的 `signalAll()` 相当于 Object 的 `notifyAll()` 方法。
219+
>
220+
> 不同的是,Object 中的 `wait(),notify(),notifyAll()`方法是和`"同步锁"`(synchronized关键字)捆绑使用的;而 Condition 是需要与`"互斥锁"/"共享锁"`捆绑使用的。
219221
220222
```java
221223
public class PrintABCUsingLockCondition {
@@ -273,14 +275,18 @@ public class PrintABCUsingLockCondition {
273275

274276

275277

278+
> 以上几种方式,其实都会存在一个锁的抢夺过程,如果抢锁的的线程数量足够大,就会出现很多线程抢到了锁但不该自己执行,然后就又解锁或 wait() 这种操作,这样其实是有些浪费资源的。
279+
280+
281+
276282
## 使用 Semaphore
277283

278284
> 在信号量上我们定义两种操作: 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
279285
>
280286
> 1. acquire(获取) 当一个线程调用 acquire 操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
281287
> 2. release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
282288
283-
先解决第一题:三个线程循环打印A,B,C
289+
先看下如何解决第一题:三个线程循环打印 A,B,C
284290

285291
```java
286292
public class PrintABCUsingSemaphore {
@@ -326,7 +332,7 @@ public class PrintABCUsingSemaphore {
326332

327333

328334

329-
如果题目中是多个线程循环打印的话,一般使用信号量解决是效率较高的方案,上一个线程持有下一个线程的信号量,通过一个信号量数组将全部关联起来,这种方式不会存在浪费资源,锁竞争的情况
335+
如果题目中是多个线程循环打印的话,一般使用信号量解决是效率较高的方案,上一个线程持有下一个线程的信号量,通过一个信号量数组将全部关联起来,这种方式不会存在浪费资源的情况
330336

331337
接着用信号量的方式解决下第三题:通过 N 个线程顺序循环打印从 0 至 100
332338

@@ -376,14 +382,93 @@ public class LoopPrinter {
376382

377383

378384

379-
## 写在最后
385+
## 使用 LockSupport
386+
387+
LockSupport 是 JDK 底层的基于 `sun.misc.Unsafe` 来实现的类,用来创建锁和其他同步工具类的基本线程阻塞原语。它的静态方法`unpark()``park()`可以分别实现阻塞当前线程和唤醒指定线程的效果,所以用它解决这样的问题会更容易一些。
388+
389+
(在 AQS 中,就是通过调用 `LockSupport.park( )``LockSupport.unpark()` 来实现线程的阻塞和唤醒的。)
390+
391+
```java
392+
public class PrintABCUsingLockSupport {
393+
394+
private static Thread threadA, threadB, threadC;
395+
396+
public static void main(String[] args) {
397+
threadA = new Thread(() -> {
398+
for (int i = 0; i < 10; i++) {
399+
// 打印当前线程名称
400+
System.out.print(Thread.currentThread().getName());
401+
// 唤醒下一个线程
402+
LockSupport.unpark(threadB);
403+
// 当前线程阻塞
404+
LockSupport.park();
405+
}
406+
}, "A");
407+
threadB = new Thread(() -> {
408+
for (int i = 0; i < 10; i++) {
409+
// 先阻塞等待被唤醒
410+
LockSupport.park();
411+
System.out.print(Thread.currentThread().getName());
412+
// 唤醒下一个线程
413+
LockSupport.unpark(threadC);
414+
}
415+
}, "B");
416+
threadC = new Thread(() -> {
417+
for (int i = 0; i < 10; i++) {
418+
// 先阻塞等待被唤醒
419+
LockSupport.park();
420+
System.out.print(Thread.currentThread().getName());
421+
// 唤醒下一个线程
422+
LockSupport.unpark(threadA);
423+
}
424+
}, "C");
425+
threadA.start();
426+
threadB.start();
427+
threadC.start();
428+
}
429+
}
430+
```
431+
432+
理解了思路,解决其他问题就容易太多了。
433+
434+
比如,我们再解决下第五题:用两个线程,一个输出字母,一个输出数字,交替输出 1A2B3C4D...26Z
435+
436+
```java
437+
public class NumAndLetterPrinter {
438+
439+
private static Thread numThread, letterThread;
440+
441+
public static void main(String[] args) {
442+
letterThread = new Thread(() -> {
443+
for (int i = 0; i < 26; i++) {
444+
System.out.print((char) ('A' + i));
445+
LockSupport.unpark(numThread);
446+
LockSupport.park();
447+
}
448+
}, "letterThread");
380449

381-
实现的方式还有很多,可以用 LockSupport
450+
numThread = new Thread(() -> {
451+
for (int i = 1; i <= 26; i++) {
452+
System.out.print(i);
453+
LockSupport.park();
454+
LockSupport.unpark(letterThread);
455+
}
456+
}, "numThread");
457+
numThread.start();
458+
letterThread.start();
459+
}
460+
}
461+
```
462+
463+
464+
465+
## 写在最后
382466

467+
好了,以上就是常用的五种实现方案,多练习几次,手撕没问题。
383468

469+
当然,这类问题,解决方式不止是我列出的这些,还会有 join、CountDownLatch、也有放在队列里解决的,思路有很多,面试官想考察的其实只是对多线程的编程功底,其实自己练习的时候,是个很好的巩固理解 JUC 的过程。
384470

385-
参考:
471+
> 以梦为马,越骑越傻。诗和远方,越走越慌。不忘初心是对的,但切记要出发,加油吧,程序员。
386472
387-
https://www.jianshu.com/p/40078ed436b4
473+
> 在路上的你,可以微信搜「 **JavaKeeper** 」一起前行,无套路领取 500+ 本电子书和 30+ 视频教学和源码,本文 **GitHub** [github.com/JavaKeeper](https://github.com/Jstarfish/JavaKeeper) 已经收录,服务端开发、面试必备技能兵器谱,有你想要的。
388474
389-
https://www.throwable.club/2019/05/20/interview-problem-double-thread-print-odd-even-alternately/

docs/java/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
<img src="https://img.shields.io/badge/language-Java-blue.svg">
44
<img src="https://img.shields.io/badge/platform-Linux-red.svg">
55
<a href="https://juejin.im/user/5b8f1d426fb9a019d7477421"><img src="https://img.shields.io/badge/%E6%8E%98%E9%87%91-@lazyegg-FFA500.svg?style=flat&colorA=1970fe"></a>
6-
<a href="https://www.lazyegg.net"><img src="https://img.shields.io/badge/Blog-lazyegg-80d4f9.svg?style=flat"></a>
6+
<a href="https://segmentfault.com/u/jstarfish"><img src="https://img.shields.io/badge/segmentfault-jstarfish-009a61.svg?style=flat"></a>
77
<a href="https://blog.csdn.net/u011870547"><img src="https://img.shields.io/badge/CSDN-@大新之助-fd6f32.svg?style=flat&colorA=B22222"></a>
88
</p>
99

10+
1011
## 欢迎加入我们
1112

1213
- 搜索公众号 **`JavaKeeper`** 或者 **`扫描右侧二维码`** 进行关注
1314
- 持续输出一个深耕在互联网的 Javaer 的所学和所见
1415

16+
17+
18+
> 以梦为马,越骑越傻。诗和远方,越走越慌。不忘初心是对的,但切记要出发,加油吧,程序员。
19+
20+
> 在路上的你,可以微信搜「 **JavaKeeper** 」一起前行,无套路领取 500+ 本电子书和 30+ 视频教学和源码,本文 **GitHub** [github.com/JavaKeeper](https://github.com/Jstarfish/JavaKeeper) 已经收录,服务端开发、面试必备技能兵器谱,有你想要的!
21+

0 commit comments

Comments
 (0)