-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathjavathread.md
More file actions
5184 lines (3645 loc) · 232 KB
/
javathread.md
File metadata and controls
5184 lines (3645 loc) · 232 KB
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: Java并发编程面试题,73道Java多线程八股文(3.5万字145张手绘图),面渣逆袭必看👍
shortTitle: 面渣逆袭-Java并发编程
author: 三分恶&沉默王二
category:
- 面渣逆袭
tag:
- 面渣逆袭
description: 下载次数超 1 万次,3.5 万字 145 张手绘图,详解 73 道 Java 多线程面试高频题(让天下没有难背的八股),面渣背会这些并发编程八股文,这次吊打面试官,我觉得稳了(手动 dog)。
date: 2026-03-29
head:
- - meta
- name: keywords
content: Java,Thread,Java并发编程,Java多线程,Java面试题,Java并发编程面试题,面试题,八股文,java
---

## 前言
3.5 万字 145 张手绘图,详解 73 道 Java 多线程面试高频题(让天下没有难背的八股),面渣背会这些并发编程八股文,这次吊打面试官,我觉得稳了(手动 dog)。
第一版作者是二哥编程星球的嘉宾三分恶,第二版由二哥结合球友们的面经+技术派+PmHub+mydb 的项目进行全新升级。更适合拿来背诵突击面试+底层原理理解。
亮白版本更适合拿出来打印,这也是很多学生党喜欢的方式,打印出来背诵的效率会更高。

2025 年 01 月 22 日开始着手第二版更新。
- 对于高频题,会标注在《[Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)》中出现的位置,哪家公司,原题是什么,并且会加🌟,目录一目了然;如果你想节省时间的话,可以优先背诵这些题目,尽快做到知彼知己,百战不殆。
- 区分八股精华回答版本和原理底层解释,让大家知其然知其所以然,同时又能做到面试时的高效回答。
- 结合项目([技术派](https://javabetter.cn/zhishixingqiu/paicoding.html)、[pmhub](https://javabetter.cn/zhishixingqiu/pmhub.html))来组织语言,让面试官最大程度感受到你的诚意,而不是机械化的背诵。
- 修复第一版中出现的问题,包括球友们的私信反馈,网站留言区的评论,以及 [GitHub 仓库](https://github.com/itwanger/toBeBetterJavaer/issues)中的 issue,让这份面试指南更加完善。
- 增加[二哥编程星球](https://javabetter.cn/zhishixingqiu/)的球友们拿到的一些 offer,对面渣逆袭的感谢,以及对简历修改的一些认可,以此来激励大家,给大家更多信心。
- 优化排版,增加手绘图,重新组织答案,使其更加口语化,从而更贴近面试官的预期。

由于 PDF 没办法自我更新,所以需要最新版的小伙伴,可以微信搜【**沉默王二**】,或者扫描/长按识别下面的二维码,关注二哥的公众号,回复【**222**】即可拉取最新版本。
<div style="text-align: center; margin: 20px 0;">
<img src="https://cdn.paicoding.com/tobebetterjavaer/images/gongzhonghao.png" alt="微信扫码或者长按识别,或者微信搜索“沉默王二”" style="max-width: 100%; height: auto; border-radius: 10px;" />
</div>
当然了,请允许我的一点点私心,那就是星球的 PDF 版本会比公众号早一个月时间,毕竟星球用户都付费过了,我有必要让他们先享受到一点点福利。相信大家也都能理解,毕竟在线版是免费的,CDN、服务器、域名、OSS 等等都是需要成本的。
更别说我付出的时间和精力了,大家觉得有帮助还请给个口碑,让你身边的同事、同学都能受益到。

我把二哥的 Java 进阶之路、JVM 进阶之路、并发编程进阶之路,以及所有面渣逆袭的版本都放进来了,涵盖 Java基础、Java集合、Java并发、JVM、Spring、MyBatis、计算机网络、操作系统、MySQL、Redis、RocketMQ、分布式、微服务、设计模式、Linux 等 16 个大的主题,共有 40 多万字,2000+张手绘图,可以说是诚意满满。
展示一下暗黑版本的 PDF 吧,排版清晰,字体优雅,更加适合夜服,晚上看会更舒服一点。

## 基础
### 1.并行跟并发有什么区别?
- 并行是多核 CPU 上的多任务处理,多个任务在同一时间真正地同时执行。
- 并发是单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行,用于解决 IO 密集型任务的瓶颈。

举个例子,就好像我们去食堂打饭,并行就是每个人对应一个阿姨,同时打饭;而并发就是一个阿姨,轮流给每个人打饭,假如有个人磨磨唧唧,阿姨就会吆喝下一个人,这样就能提高食堂的打饭效率。

#### 你是如何理解线程安全的?
推荐阅读:[多线程带来了哪些问题?](https://javabetter.cn/thread/thread-bring-some-problem.html)
如果一段代码块或者一个方法被多个线程同时执行,还能够正确地处理共享数据,那么这段代码块或者这个方法就是线程安全的。
可以从三个要素来确保线程安全:
**①、原子性**:一个操作要么完全执行,要么完全不执行,不会出现中间状态。

可以通过同步关键字 synchronized 或原子操作,如 AtomicInteger 来保证原子性。
```java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作
```
**②、可见性**:当一个线程修改了共享变量,其他线程能够立即看到变化。

可以通过 volatile 关键字来保证可见性。
```java
private volatile String itwanger = "沉默王二";
```
**③、有序性**:要确保线程不会因为死锁、饥饿、活锁等问题导致无法继续执行。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的华为 OD 面经同学 1 一面面试原题:对于多线程编程的了解?
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的快手面经同学 1 部门主站技术部面试原题:你对线程安全的理解是什么?
memo:2025 年 1 月 22 日修改至此。
### 2.🌟说说进程和线程的区别?
推荐阅读:[进程与线程的区别是什么?](https://javabetter.cn/thread/why-need-thread.html)
进程说简单点就是我们在电脑上启动的一个个应用。它是操作系统分配资源的最小单位。
线程是进程中的独立执行单元。多个线程可以共享同一个进程的资源,如内存;每个线程都有自己独立的栈和寄存器。

#### 如何理解协程?
协程被视为比线程更轻量级的并发单元,可以在单线程中实现并发执行,由我们开发者显式调度。
协程是在用户态进行调度的,避免了线程切换时的内核态开销。
Java 自身是不支持携程的,我们可以使用 Quasar、Kotlin 等框架来实现协程。
```java
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
```
#### 线程间是如何进行通信的?
原则上可以通过消息传递和共享内存两种方法来实现。Java 采用的是共享内存的并发模型。
这个模型被称为 Java 内存模型,简写为 JMM,它决定了一个线程对共享变量的写入,何时对另外一个线程可见。当然了,本地内存是 JMM 的一个抽象概念,并不真实存在。
用一句话来概括就是:共享变量存储在主内存中,每个线程的私有本地内存,存储的是这个共享变量的副本。

线程 A 与线程 B 之间如要通信,需要要经历 2 个步骤:
- 线程 A 把本地内存 A 中的共享变量副本刷新到主内存中。
- 线程 B 到主内存中读取线程 A 刷新过的共享变量,再同步到自己的共享变量副本中。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的字节跳动商业化一面的原题:进程和线程区别,线程共享内存和进程共享内存的区别
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的小米春招同学 K 一面面试原题:协程和线程和进程的区别
> 3. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的字节跳动面经同学 1 Java 后端技术一面面试原题:线程和进程有什么区别?
> 4. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的华为 OD 面经同学 1 一面面试原题:对于多线程编程的了解?
> 5. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的美团面经同学 2 Java 后端技术一面面试原题:进程和线程的区别?
> 6. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的华为面经同学 9 Java 通用软件开发一面面试原题:进程和线程的区别
> 7. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的 小公司面经合集好未来测开面经同学 3 测开一面面试原题:进程和线程的区别
> 8. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的招商银行面经同学 6 招银网络科技面试原题:进程和线程的区别?
> 9. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的用友面试原题:线程和进程的区别
> 10. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的vivo 面经同学 10 技术一面面试原题:线程的概念,线程有哪些状态
> 11. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的海康威视同学 4面试原题:对协程的了解,为什么协程比线程还有更低的资源消耗
memo:2025 年 8 月 17 日修改至此。[今天在帮球友修改简历的时候](https://javabetter.cn/zhishixingqiu/jianli.html),收到他的反馈说:上次也麻烦我帮他改了改简历,也顺利找到了实习,秋招逼近,希望我能再帮他看看实习经历。感谢球友的每一次口碑。

### 3.🌟说说线程有几种创建方式?
推荐阅读:[室友打了一把王者就学会了 Java 多线程](https://javabetter.cn/thread/wangzhe-thread.html)
有三种,分别是继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。

第一种需要重写父类 Thread 的 `run()` 方法,并且调用 `start()` 方法启动线程。
```java
class ThreadTask extends Thread {
public void run() {
System.out.println("看完二哥的 Java 进阶之路,上岸了!");
}
public static void main(String[] args) {
ThreadTask task = new ThreadTask();
task.start();
}
}
```
这种方法的缺点是,如果 ThreadTask 已经继承了另外一个类,就不能再继承 Thread 类了,因为 Java 不支持多重继承。
第二种需要重写 Runnable 接口的 `run()` 方法,并将实现类的对象作为参数传递给 Thread 对象的构造方法,最后调用 `start()` 方法启动线程。
```java
class RunnableTask implements Runnable {
public void run() {
System.out.println("看完二哥的 Java 进阶之路,上岸了!");
}
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
Thread thread = new Thread(task);
thread.start();
}
}
```
这种方法的优点是可以避免 Java 的单继承限制,并且更符合面向对象的编程思想,因为 Runnable 接口将任务代码和线程控制的代码解耦了。
第三种需要重写 Callable 接口的 `call()` 方法,然后创建 FutureTask 对象,参数为 Callable 实现类的对象;紧接着创建 Thread 对象,参数为 FutureTask 对象,最后调用 `start()` 方法启动线程。
```java
class CallableTask implements Callable<String> {
public String call() {
return "看完二哥的 Java 进阶之路,上岸了!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTask task = new CallableTask();
FutureTask<String> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
```
这种方法的优点是可以获取线程的执行结果。
#### 一个 8G 内存的系统最多能创建多少个线程?
推荐阅读:[深入理解 JVM 的运行时数据区](https://javabetter.cn/jvm/neicun-jiegou.html)
理论上大约 8000 个。
创建线程的时候,至少需要分配一个虚拟机栈,在 64 位操作系统中,默认大小为 1M,因此一个线程大约需要 1M 的内存。
但 JVM、操作系统本身的运行就要占一定的内存空间,所以实际上可以创建的线程数远比 8000 少。
详细解释一下。
可以通过 `java -XX:+PrintFlagsFinal -version | grep ThreadStackSize` 命令查看 JVM 栈的默认大小。

其中 ThreadStackSize 的单位是 KB,也就是说默认的 JVM 栈大小是 1024 KB,也就是 1M。
#### 启动一个 Java 程序,你能说说里面有哪些线程吗?
首先是 main 线程,这是程序执行的入口。
然后是垃圾回收线程,它是一个后台线程,负责回收不再使用的对象。
还有编译器线程,比如 JIT,负责把一部分热点代码编译后放到 codeCache 中。

可以通过下面的代码进行检测:
```java
class ThreadLister {
public static void main(String[] args) {
// 获取所有线程的堆栈跟踪
Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
for (Thread thread : threads.keySet()) {
System.out.println("Thread: " + thread.getName() + " (ID=" + thread.getId() + ")");
}
}
}
```
结果如下所示:
```
Thread: Monitor Ctrl-Break (ID=5)
Thread: Reference Handler (ID=2)
Thread: main (ID=1)
Thread: Signal Dispatcher (ID=4)
Thread: Finalizer (ID=3)
```
简单解释下:
- `Thread: main (ID=1)` - 主线程,Java 程序启动时由 JVM 创建。
- `Thread: Reference Handler (ID=2)` - 这个线程是用来处理引用对象的,如软引用、弱引用和虚引用。负责清理被 JVM 回收的对象。
- `Thread: Finalizer (ID=3)` - 终结器线程,负责调用对象的 finalize 方法。对象在垃圾回收器标记为可回收之前,由该线程执行其 finalize 方法,用于执行特定的资源释放操作。
- `Thread: Signal Dispatcher (ID=4)` - 信号调度线程,处理来自操作系统的信号,将它们转发给 JVM 进行进一步处理,例如响应中断、停止等信号。
- `Thread: Monitor Ctrl-Break (ID=5)` - 监视器线程,通常由一些特定的 IDE 创建,用于在开发过程中监控和管理程序执行或者处理中断。
#### 你平时有用过多线程吗?你在代码中是哪些场景用呢?
用得比较多,批量数据处理、异步任务处理、定时任务调度都需要用到多线程。
比如说在[技术派的首页内容加载](https://javabetter.cn/zhishixingqiu/paicoding.html)中,就用到了多线程来并行加载不同的模块,提高页面的响应速度。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的字节跳动面经同学 1 Java 后端技术一面面试原题:有多少种实现线程的方法?
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的农业银行同学 1 面试原题:实现线程的方式和区别
> 3. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的农业银行面经同学 3 Java 后端面试原题:说说线程的创建方法
> 4. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的小公司面经合集同学 1 Java 后端面试原题:线程创建的方式?Runable 和 Callable 有什么区别?
> 5. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的阿里面经同学 5 阿里妈妈 Java 后端技术一面面试原题:一个 8G 内存的系统最多能创建多少线程?(奇怪的问题,答了一些 pcb、页表、虚拟机栈什么的)启动一个 Java 程序,你能说说里面有哪些线程吗?
> 6. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的招商银行面经同学 6 招银网络科技面试原题:如何创建线程?
> 7. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的百度面经同学 1 文心一言 25 实习 Java 后端面试原题:java 如何创建线程?每次都要创建新线程来实现异步操作,很繁琐,有了解线程池吗?
> 8. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的美团面经同学 4 一面面试原题:平时怎么使用多线程
memo:2025 年 9 月 26 日修改至此。今天[有球友在星球里](https://javabetter.cn/zhishixingqiu/)报喜说拿到了字节的意向,感谢二哥的面渣逆袭。

### 4.🌟调用 start 方法时会执行 run 方法,那怎么不直接调用 run方法?
调用 `start()` 会创建一个新的线程,并异步执行 `run()` 方法中的代码。
直接调用 `run()` 方法只是一个普通的同步方法调用,所有代码都在当前线程中执行,不会创建新线程。没有新的线程创建,也就达不到多线程并发的目的。
通过敲代码体验一下。
```java
class MyThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 正确的方式,创建一个新线程,并在新线程中执行 run()
t1.run(); // 仅在主线程中执行 run(),没有创建新线程
}
}
```
来看输出结果:
```
main
Thread-0
```
也就是说,调用 `start()` 方法会通知 JVM,去调用底层的线程调度机制来启动新线程。

调用 `start()` 后,线程进入就绪状态,等待操作系统调度;一旦调度执行,线程会执行其 `run()` 方法中的代码。
> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的小公司面经合集同学 1 Java 后端面试原题:启动一个线程是 run()还是 start()?
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的百度面经同学 1 文心一言 25 实习 Java 后端面试原题:java 如何启动多线程,有哪些方式?
> 3. [二哥编程星球](https://javabetter.cn/zhishixingqiu/)球友[枕云眠美团 AI 面试原题](https://t.zsxq.com/BaHOh):java 线程操作中的 start 和 run 方法区别是什么
memo:2025 年 1 月 26 日修改至此。
### 5.线程有哪些常用的调度方法?
比如说 start 方法用于启动线程并让操作系统调度执行;sleep 方法用于让当前线程休眠一段时间;wait 方法会让当前线程等待,notify 会唤醒一个等待的线程。

#### 说说wait方法和notify方法?
当线程 A 调用共享对象的 `wait()` 方法时,线程 A 会被阻塞挂起,直到:
- 线程 B 调用了共享对象的 `notify()` 方法或者 `notifyAll()` 方法;
- 其他线程调用线程 A 的 `interrupt()` 方法,导致线程 A 抛出 InterruptedException 异常。
线程 A 调用共享对象的 `wait(timeout)`方法后,没有在指定的 timeout 时间内被其它线程唤醒,那么这个方法会因为超时而返回。
当线程 A 调用共享对象的 `notify()` 方法后,会唤醒一个在这个共享对象上调用 wait 系列方法被挂起的线程。
共享对象上可能会有多个线程在等待,具体唤醒哪个线程是随机的。
如果调用的是 notifyAll 方法,会唤醒所有在这个共享变量上调用 wait 系列方法而被挂起的线程。
#### 说说 sleep 方法?
当线程 A 调用了 Thread 的 sleep 方法后,线程 A 会暂时让出指定时间的执行权。
指定的睡眠时间到了后该方法会正常返回,接着参与 CPU 调度,获取到 CPU 资源后可以继续执行。
#### 说说yield方法?
`yield()` 方法的目的是让当前线程让出 CPU 使用权,回到就绪状态。但是线程调度器可能会忽略。
#### 说说interrupt方法?
推荐阅读:[interrupt 方法](https://www.cnblogs.com/myseries/p/10918819.html)
`interrupt()` 方法用于通知线程停止,但不会直接终止线程,需要线程自行处理中断标志。
常与 `isInterrupted()` 或 `Thread.interrupted()` 配合使用。
```java
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Running");
}
System.out.println("Interrupted");
});
thread.start();
thread.interrupt(); // 中断线程
```
#### 说说 stop 方法?
stop 方法用来强制停止线程,目前已经处于废弃状态,因为 stop 方法可能会在不一致的状态下释放锁,破坏对象的一致性。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的帆软同学 3 Java 后端一面的原题:怎么停止一个线程,interrupt 和 stop 区别
memo:2025 年 1 月 27 日修改至此。
### 6.🌟线程有几种状态?
6 种。
new 代表线程被创建但未启动;runnable 代表线程处于就绪或正在运行状态,由操作系统调度;blocked 代表线程被阻塞,等待获取锁;waiting 代表线程等待其他线程的通知或中断;timed_waiting 代表线程会等待一段时间,超时后自动恢复;terminated 代表线程执行完毕,生命周期结束。

也就是说,线程的生命周期可以分为五个主要阶段:新建、就绪、运行、阻塞和终止。线程在运行过程中会根据状态的变化在这些阶段之间切换。
```java
class ThreadStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000); // TIMED_WAITING
synchronized (ThreadStateExample.class) {
ThreadStateExample.class.wait(); // WAITING
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
System.out.println("State after creation: " + thread.getState()); // NEW
thread.start();
System.out.println("State after start: " + thread.getState()); // RUNNABLE
Thread.sleep(500);
System.out.println("State while sleeping: " + thread.getState()); // TIMED_WAITING
synchronized (ThreadStateExample.class) {
ThreadStateExample.class.notify(); // 唤醒线程
}
thread.join();
System.out.println("State after termination: " + thread.getState()); // TERMINATED
}
}
```
用一个表格来做个总结:
| 状态 | 说明 |
| --- | --- |
| NEW | 当线程被创建后,如通过`new Thread()`,它处于新建状态。此时,线程已经被分配了必要的资源,但还没有开始执行。 |
| RUNNABLE | 当调用线程的`start()`方法后,线程进入可运行状态。在这个状态下,线程可能正在运行也可能正在等待获取 CPU 时间片,具体取决于线程调度器的调度策略。 |
| BLOCKED | 线程在试图获取一个锁以进入同步块/方法时,如果锁被其他线程持有,线程将进入阻塞状态,直到它获取到锁。 |
| WAITING | 线程进入等待状态是因为调用了如下方法之一:`Object.wait()`或`LockSupport.park()`。在等待状态下,线程需要其他线程显式地唤醒,否则不会自动执行。 |
| TIME_WAITING | 当线程调用带有超时参数的方法时,如`Thread.sleep(long millis)`、`Object.wait(long timeout)` 或`LockSupport.parkNanos()`,它将进入超时等待状态。线程在指定的等待时间过后会自动返回可运行状态。 |
| TERMINATED | 当线程的`run()`方法执行完毕后,或者因为一个未捕获的异常终止了执行,线程进入终止状态。一旦线程终止,它的生命周期结束,不能再被重新启动。 |
#### 如何强制终止线程?
第一步,调用线程的 `interrupt()` 方法,请求终止线程。
第二步,在线程的 `run()` 方法中检查中断状态,如果线程被中断,就退出线程。
```java
class MyTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("Running...");
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
// 捕获中断异常后,重置中断状态
Thread.currentThread().interrupt();
System.out.println("Thread interrupted, exiting...");
break;
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyTask());
thread.start();
Thread.sleep(3000); // 主线程等待3秒
thread.interrupt(); // 请求终止线程
}
}
```
中断结果:

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的招商银行面经同学 6 招银网络科技面试原题:线程的生命周期和状态?
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的快手同学 2 一面面试原题:线程有哪些状态?
> 3. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的 OPPO 面经同学 1 面试原题:Java里线程的生命周期
> 4. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的同学 D 小米一面原题:线程的生命周期
### 7.什么是线程上下文切换?
线程上下文切换是指 CPU 从一个线程切换到另一个线程执行时的过程。
在线程切换的过程中,CPU 需要保存当前线程的执行状态,并加载下一个线程的上下文。
之所以要这样,是因为 CPU 在同一时刻只能执行一个线程,为了实现多线程并发执行,需要不断地在多个线程之间切换。

为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转的方式,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会让出 CPU 让其他线程占用。

#### 线程可以被多核调度吗?
多核处理器提供了并行执行多个线程的能力。每个核心可以独立执行一个或多个线程,操作系统的任务调度器会根据策略和算法,如优先级调度、轮转调度等,决定哪个线程何时在哪个核心上运行。
> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的字节跳动同学 7 Java 后端实习一面的原题:线程可以被多核调度吗?
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的携程面经同学 1 Java 后端技术一面面试原题:线程上下文切换(我答的内核态和用户态切换时机,和切换需要加载哪些内容)
### 8.守护线程了解吗?
了解,守护线程是一种特殊的线程,它的作用是为其他线程提供服务。
Java 中的线程分为两类,一种是守护线程,另外一种是用户线程。
JVM 启动时会调用 main 方法,main 方法所在的线程就是一个用户线程。在 JVM 内部,同时还启动了很多守护线程,比如垃圾回收线程。
#### 守护线程和用户线程有什么区别呢?
区别之一是当最后一个非守护线程束时, JVM 会正常退出,不管当前是否存在守护线程,也就是说守护线程是否结束并不影响 JVM 退出。
换而言之,只要有一个用户线程还没结束,正常情况下 JVM 就不会退出。
### 9.线程间有哪些通信方式?
线程之间传递信息的方式有多种,比如说使用 volatile 和 synchronized 关键字共享对象、使用 `wait()` 和 `notify()` 方法实现生产者-消费者模式、使用 Exchanger 进行数据交换、使用 Condition 实现线程间的协调等。
#### 简单说说 volatile 和 synchronized 的使用方式?
多个线程可以通过 volatile 和 synchronized 关键字访问和修改同一个对象,从而实现信息的传递。
[关键字 volatile](https://javabetter.cn/thread/volatile.html) 可以用来修饰成员变量,告知程序任何对该变量的访问均需要从共享内存中获取,并同步刷新回共享内存,保证所有线程对变量访问的可见性。
[关键字 synchronized](https://javabetter.cn/thread/synchronized-1.html) 可以修饰方法,或者同步代码块,确保多个线程在同一个时刻只有一个线程在执行方法或代码块。
```java
class SharedObject {
private String message;
private boolean hasMessage = false;
public synchronized void writeMessage(String message) {
while (hasMessage) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
this.message = message;
hasMessage = true;
notifyAll();
}
public synchronized String readMessage() {
while (!hasMessage) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
hasMessage = false;
notifyAll();
return message;
}
}
public class Main {
public static void main(String[] args) {
SharedObject sharedObject = new SharedObject();
Thread writer = new Thread(() -> {
sharedObject.writeMessage("Hello from Writer!");
});
Thread reader = new Thread(() -> {
String message = sharedObject.readMessage();
System.out.println("Reader received: " + message);
});
writer.start();
reader.start();
}
}
```
#### wait() 和 notify() 方法的使用方式了解吗?
一个线程调用共享对象的 `wait()` 方法时,它会进入该对象的等待池,释放已经持有的锁,进入等待状态。
一个线程调用 `notify()` 方法时,它会唤醒在该对象等待池中等待的一个线程,使其进入锁池,等待获取锁。
```java
class MessageBox {
private String message;
private boolean empty = true;
public synchronized void produce(String message) {
while (!empty) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = false;
this.message = message;
notifyAll();
}
public synchronized String consume() {
while (empty) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = true;
notifyAll();
return message;
}
}
public class Main {
public static void main(String[] args) {
MessageBox box = new MessageBox();
Thread producer = new Thread(() -> {
box.produce("Message from producer");
});
Thread consumer = new Thread(() -> {
String message = box.consume();
System.out.println("Consumer received: " + message);
});
producer.start();
consumer.start();
}
}
```
[Condition](https://javabetter.cn/thread/condition.html) 也提供了类似的方法,`await()` 负责阻塞、`signal()` 和 `signalAll()` 负责通知。
通常与锁 [ReentrantLock](https://javabetter.cn/thread/reentrantLock.html) 一起使用,为线程提供了一种等待某个条件成真的机制,并允许其他线程在该条件变化时通知等待线程。
#### Exchanger 的使用方式了解吗?
Exchanger 是一个同步点,可以在两个线程之间交换数据。一个线程调用 `exchange()` 方法,将数据传递给另一个线程,同时接收另一个线程的数据。
```java
class Main {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread thread1 = new Thread(() -> {
try {
String message = "Message from thread1";
String response = exchanger.exchange(message);
System.out.println("Thread1 received: " + response);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread thread2 = new Thread(() -> {
try {
String message = "Message from thread2";
String response = exchanger.exchange(message);
System.out.println("Thread2 received: " + response);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
thread1.start();
thread2.start();
}
}
```
#### CompletableFuture 的使用方式了解吗?
CompletableFuture 是 Java 8 引入的一个类,支持异步编程,允许线程在完成计算后将结果传递给其他线程。
```java
class Main {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟长时间计算
return "Message from CompletableFuture";
});
future.thenAccept(message -> {
System.out.println("Received: " + message);
});
}
}
```
> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的华为 OD 的面试中出现过该原题。
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的阿里面经同学 1 闲鱼后端一面的原题:线程之间传递信息?
> 3. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的理想汽车面经同学 2 一面面试原题:线程内有哪些通信方式?线程之间有哪些通信方式?
memo:2025 年 1 月 28 日修改至此。
### 10.🌟请说说 sleep 和 wait 的区别?(补充)
> 2024 年 03 月 21 日增补
sleep 会让当前线程休眠,不需要获取对象锁,属于 Thread 类的方法;wait 会让获得对象锁的线程等待,要提前获得对象锁,属于 Object 类的方法。
详细解释下。
①、所属类不同
- `sleep()` 方法专属于 `Thread` 类。
- `wait()` 方法专属于 `Object` 类。
②、锁行为不同
如果一个线程在持有某个对象锁时调用了 sleep 方法,它在睡眠期间仍然会持有这个锁。
```java
class SleepDoesNotReleaseLock {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread sleepingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1 会继续持有锁,并且进入睡眠状态");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 醒来了,并且释放了锁");
}
});
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 进入同步代码块");
}
});
sleepingThread.start();
Thread.sleep(1000);
waitingThread.start();
}
}
```
输出结果:
```
Thread 1 会继续持有锁,并且进入睡眠状态
Thread 1 醒来了,并且释放了锁
Thread 2 进入同步代码块
```
从输出中我们可以看到,waitingThread 必须等待 sleepingThread 完成睡眠后才能进入同步代码块。
而当线程执行 wait 方法时,它会释放持有的对象锁,因此其他线程也有机会获取该对象的锁。
```java
class WaitReleasesLock {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 持有锁,准备等待 5 秒");
lock.wait(5000);
System.out.println("Thread 1 醒来了,并且退出同步代码块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 尝试唤醒等待中的线程");
lock.notify();
System.out.println("Thread 2 执行完了 notify");
}
});
waitingThread.start();
Thread.sleep(1000);
notifyingThread.start();
}
}
```
输出结果:
```
Thread 1 持有锁,准备等待 5 秒
Thread 2 尝试唤醒等待中的线程
Thread 2 执行完了 notify
Thread 1 醒来了,并且退出同步代码块
```
这表明 waitingThread 在调用 wait 后确实释放了锁。
③、使用条件不同
- `sleep()` 方法可以在任何地方被调用。
- `wait()` 方法必须在同步代码块或同步方法中被调用,这是因为调用 `wait()` 方法的前提是当前线程必须持有对象的锁。否则会抛出 `IllegalMonitorStateException` 异常。

④、唤醒方式不同
- 调用 sleep 方法后,线程会进入 TIMED_WAITING 状态,即在指定的时间内暂停执行。当指定的时间结束后,线程会自动恢复到 RUNNABLE 状态,等待 CPU 调度再次执行。
- 调用 wait 方法后,线程会进入 WAITING 状态,直到有其他线程在同一对象上调用 notify 或 notifyAll 方法,线程才会从 WAITING 状态转变为 RUNNABLE 状态,准备再次获得 CPU 的执行权。
我们来通过代码再感受一下 `sleep()` 和 `wait()` 在用法上的区别,先看 `sleep()` 的用法:
```java
class SleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程准备休眠 2 秒");
try {
Thread.sleep(2000); // 线程将睡眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程醒来了");
});
thread.start();
}
}
```
再来看 `wait()` 的用法:
```java
class WaitExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程准备等待 2 秒");
lock.wait(2000); // 线程会等待2秒,或者直到其他线程调用 lock.notify()/notifyAll()
System.out.println("线程结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
```
> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的腾讯 Java 后端实习一面原题:说说 sleep 和 wait 的区别
> 2. [二哥编程星球](https://javabetter.cn/zhishixingqiu/)球友[枕云眠美团 AI 面试原题](https://t.zsxq.com/BaHOh):解释一下 java 线程中 sleep 和 wait 方法的主要区别?使用时会对线程状态有什么影响
> 3. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的快手同学 2 一面面试原题:调用wait()方法时是哪个状态,sleep和wait区别?
> 4. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的同学 D 小米一面原题:sleep和wait的区别
memo:2025 年 1 月 29 日修改至此。
### 11.🌟怎么保证线程安全?(补充)
> 2024 年 05 月 01 日增补
线程安全是指在并发环境下,多个线程访问共享资源时,程序能够正确地执行,而不会出现数据不一致的问题。
为了保证线程安全,可以使用 [synchronized 关键字](https://javabetter.cn/thread/synchronized-1.html)对方法加锁,对代码块加锁。线程在执行同步方法、同步代码块时,会获取类锁或者对象锁,其他线程就会阻塞并等待锁。
如果需要更细粒度的锁,可以使用 [ReentrantLock 并发重入锁](https://javabetter.cn/thread/reentrantLock.html)等。
如果需要保证变量的内存可见性,可以使用 [volatile 关键字](https://javabetter.cn/thread/volatile.html)。
对于简单的原子变量操作,还可以使用 [Atomic 原子类](https://javabetter.cn/thread/atomic.html)。
对于线程独立的数据,可以使用 [ThreadLocal](https://javabetter.cn/thread/ThreadLocal.html) 来为每个线程提供专属的变量副本。
对于需要并发容器的地方,可以使用 [ConcurrentHashMap](https://javabetter.cn/thread/ConcurrentHashMap.html)、[CopyOnWriteArrayList](https://javabetter.cn/thread/CopyOnWriteArrayList.html) 等。
#### 有个int的变量为0,十个线程轮流对其进行++操作(循环10000次),结果大于10 万还是小于等于10万,为什么?
在这个场景中,最终的结果会小于 100000,原因是多线程环境下,++ 操作并不是一个原子操作,而是分为读取、加 1、写回三个步骤。
1. 读取变量的值。
2. 将读取到的值加 1。
3. 将结果写回变量。
这样的话,就会有多个线程读取到相同的值,然后对这个值进行加 1 操作,最终导致结果小于 100000。
详细解释下。
多个线程在并发执行 ++ 操作时,可能出现以下竞态条件:
- 线程 1 读取变量值为 0。
- 线程 2 也读取变量值为 0。
- 线程 1 进行加法运算并将结果 1 写回变量。
- 线程 2 进行加法运算并将结果 1 写回变量,覆盖了线程 1 的结果。
可以通过 synchronized 关键字为 ++ 操作加锁。
```java
class Main {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
synchronized (Main.class) {
count++;
}
}
};
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(task);
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Final count: " + count);
}
}
```
或者使用 AtomicInteger 的 `incrementAndGet()` 方法来替代 ++ 操作,保证变量的原子性。
```java
class Main {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
};
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(task);
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Final count: " + count.get());
}
}
```
#### 场景:有一个 key 对应的 value 是一个json 结构,json 当中有好几个子任务,这些子任务如果对 key 进行修改的话,会不会存在线程安全的问题?