forked from HappySnailSunshine/JavaInterview
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJUC.md
More file actions
2394 lines (1632 loc) · 103 KB
/
Copy pathJUC.md
File metadata and controls
2394 lines (1632 loc) · 103 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
# JUC
## 目录
[TOC]
> 这里大多数问题是自己遇到的,问题答案大多数参考guide哥的文章。
## 并发 多线程
# Interview
## 1.进程和线程的区别
#### 1. 何为进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。

#### 1. 何为线程?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
## 1.多线程创建的三种方式
#### 多线程的三种创建方式:(这个要会手写)
##### 1)继承Thread类,重写run()方法
继承thread方法就可以i调用thread类的start方法,start方法调用java natvie start0();
这个是调用操作系统的方法,start方法
```java
public class MyThread extends Thread{
@Override
public void run() {
// System.out.println("线程启动了");
for (int i = 1; i <= 5; i++) {
System.out.println(getName() + ": " + i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
//thread.run(); //只是普通的方法调用(一个方法执行完,然后再执行一个方法),不会启动新线程
thread.start();
//thread.start(); // IllegalThreadStateException 线程多次启动,会报错
//thread.start();
/*new MyThread().start();
new MyThread().start();*/
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
```
运行结果:
```java
main:1
main:2
main:3
main:4
main:5
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
```
##### 2)实现Runnable接口,重写run()方法
```java
public class MyRunnable implements Runnable {
private int ticket = 30;
@Override
public synchronized void run() {
for (int i = 0; i < 10; i++) { //这里for的意思是,每个线程(窗口),最多可以卖这么多票
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖票,剩下票数:" + --this.ticket);
}
}
}
}
public class RunnableDemo1 {
public static void main(String[] args) {
//创建方式有点和第一种不一样
Runnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable,"窗口1:"); //看API中的构造方法,有一个Thread(Runnable target),将实现了Runnable的对象,可以放进去
Thread thread2 = new Thread(runnable,"窗口2:");
Thread thread3 = new Thread(runnable,"窗口3:");
thread1.start();
thread2.start();
thread3.start();
}
}
```
运行结果:
```java
窗口1:正在卖票,剩下票数:29
窗口1:正在卖票,剩下票数:28
窗口1:正在卖票,剩下票数:27
窗口1:正在卖票,剩下票数:26
窗口1:正在卖票,剩下票数:25
窗口1:正在卖票,剩下票数:24
窗口1:正在卖票,剩下票数:23
窗口1:正在卖票,剩下票数:22
窗口1:正在卖票,剩下票数:21
窗口1:正在卖票,剩下票数:20
窗口3:正在卖票,剩下票数:19
窗口3:正在卖票,剩下票数:18
窗口3:正在卖票,剩下票数:17
窗口3:正在卖票,剩下票数:16
窗口3:正在卖票,剩下票数:15
窗口3:正在卖票,剩下票数:14
窗口3:正在卖票,剩下票数:13
窗口3:正在卖票,剩下票数:12
窗口3:正在卖票,剩下票数:11
窗口3:正在卖票,剩下票数:10
窗口2:正在卖票,剩下票数:9
窗口2:正在卖票,剩下票数:8
窗口2:正在卖票,剩下票数:7
窗口2:正在卖票,剩下票数:6
窗口2:正在卖票,剩下票数:5
窗口2:正在卖票,剩下票数:4
窗口2:正在卖票,剩下票数:3
窗口2:正在卖票,剩下票数:2
窗口2:正在卖票,剩下票数:1
窗口2:正在卖票,剩下票数:0
```
thread类是runnable的接口子类,可以有效避免单继承的局限性
runnable接口可以很好的体现数据共享概念
如果继承thread类,那么就可以继承sart方法,runnable接口中,需要构建thread对象调用thread的start方法
##### 3)实现Callable口,重写call方法
```java
public class MyCallable implements Callable {
private int ticket = 30;
@Override
public synchronized Object call() throws Exception {
for (int i = 0; i < 10; i++) { //这里for循环的意思是,每个线程(窗口),最多可以买这么多票
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":正在卖票,剩下票数:" + --this.ticket);
}
}
return "票卖完了";
}
}
public class CallableDemo1 {
public static void main(String[] args) {
//创建方式有点和前两种都不一样
MyCallable callable1 = new MyCallable();
MyCallable callable2 = new MyCallable();
MyCallable callable3 = new MyCallable();
FutureTask<String> task1 = new FutureTask<String>(callable1);
FutureTask<String> task2 = new FutureTask<String>(callable1);
FutureTask<String> task3 = new FutureTask<String>(callable1);
new Thread(task1,"窗口1:").start();
new Thread(task2,"窗口2:").start();
new Thread(task3,"窗口3:").start();
try {
System.out.println("窗口1返回的结果为:" + task1.get());
System.out.println("窗口2返回的结果为:" + task2.get());
System.out.println("窗口3返回的结果为:" + task3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
```
注意:**要想返回结果,这里必须要FutureTask实例对象的get()方法,才可以得到线程返回来的结果。**
运行结果:
```java
窗口1::正在卖票,剩下票数:29
窗口1::正在卖票,剩下票数:28
窗口1::正在卖票,剩下票数:27
窗口1::正在卖票,剩下票数:26
窗口1::正在卖票,剩下票数:25
窗口1::正在卖票,剩下票数:24
窗口1::正在卖票,剩下票数:23
窗口1::正在卖票,剩下票数:22
窗口1::正在卖票,剩下票数:21
窗口1::正在卖票,剩下票数:20
窗口3::正在卖票,剩下票数:19
窗口3::正在卖票,剩下票数:18
窗口1返回的结果为:票卖完了
窗口3::正在卖票,剩下票数:17
窗口3::正在卖票,剩下票数:16
窗口3::正在卖票,剩下票数:15
窗口3::正在卖票,剩下票数:14
窗口3::正在卖票,剩下票数:13
窗口3::正在卖票,剩下票数:12
窗口3::正在卖票,剩下票数:11
窗口3::正在卖票,剩下票数:10
窗口2::正在卖票,剩下票数:9
窗口2::正在卖票,剩下票数:8
窗口2::正在卖票,剩下票数:7
窗口2::正在卖票,剩下票数:6
窗口2::正在卖票,剩下票数:5
窗口2::正在卖票,剩下票数:4
窗口2::正在卖票,剩下票数:3
窗口2::正在卖票,剩下票数:2
窗口2::正在卖票,剩下票数:1
窗口2::正在卖票,剩下票数:0
窗口2返回的结果为:票卖完了
窗口3返回的结果为:票卖完了
```
## 2.说说并发与并行的区别?
- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
- **并行:** 单位时间内,多个任务同时执行。
## 3.怎么创建一个带返回参数的线程?或者说如果在主线程内要获取其他线程计算的结果应该怎么做?
带有返回参数的线程,就是上面线程的第三种方式,但是一定要用FrtureTask的实例对象的get()方法,才可以得到线程的返回参数。
## 4、多线程呢?线程和线程池都用过吗?你们项目用过线程池吗?用的哪个线程池?怎么用的,举个例子?
Executors 读音:[ɪɡˈzekjətər]
#### 线程池的好处:
这里借用《Java 并发编程的艺术》提到的来说一下**使用线程池的好处**:
- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
#### 项目中有用到多线程,用到多线程的地方:
**1.用户注册完以后,要绑定手机号或者邮箱** ( UserController里面)
如果绑定成功以后,要修改redis里面数据,还要发请求给理财系统那边,将用户信息补全
```java
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new Runnable(){ //如果上面修改成功 那么要发送请求修改那边
@Override
public void run() {
String body = "";
switch (dto.getType()) {
case BizConst.MOBILE:
body = "json={\"jsonEntity\":\"{\\\"membersNo\\\":\\\""+user.getAccount()+"\\\",\\\"UserTel\\\":\\\""+dto.getUsername()+"\\\"}\",\"action\":\"edit\",\"keyid\":0}";
break;
case BizConst.EMAIL:
body = "json={\"jsonEntity\":\"{\\\"membersNo\\\":\\\""+user.getAccount()+"\\\",\\\"Email\\\":\\\""+dto.getUsername()+"\\\"}\",\"action\":\"edit\",\"keyid\":0}";
break;
}
//发送请求
String re = cn.hutool.http.HttpUtil.post("https://api4test.utmz888.com/synHandler.ashx", body );
JSONObject jsonObject = JSON.parseObject(re);
boolean state = jsonObject.getBoolean("Success");
}
});
```
2.OTC商户 用户点击放款的时候
购买你币币的人支付以后,一系列完成以后,用线程池里面的,对被购买的人发送短信,邮件,通知他,对方已经放款成功。
```java
ExecutorService pool = Executors.newFixedThreadPool(40);
pool.submit(()->{
UserEntity targetUser=userService.getById(targetUserId);
smsUtils.send(targetUser.getMobile(),"对方已经放款",null,null);
});
```
同样还有放币以后,也要给购买人发送短信
```java
pool.submit(()->{
smsUtils.send(targetUser.getMobile(),"对方已经放币",null,null);
});
```
用多线程有一个好处就是,正常业务流程走完以后,启用多线程来发送这些比较没那么重要的请求,这样的话就不太影响主方法的进程,主方法继续往下走。
项目中的多线程基本就是这么用的多。
## 5.项目中用到的线程池是用的Spring配置方式还是自己 new 出来的?那你设置了核心和最大值吗?三个参数设置过没有?
#### 项目中用到的线程池:
自己new出来的。项目中用的线程池是Executors.newFixedThreadPool(5)。
但是阿里开发手册上面写的, Executors创建出来的线程池有弊端:
FixedThreadPool和SingleThread允许的请求队列长度为Integer.Max_value ,可能会堆积大量的请求,从而OOM。
#### 线程池常用的七个参数,重要的三个:
**`ThreadPoolExecutor` 3 个最重要的参数)**(先判断核心线程数-->然后判断队列,队列满了-->判断最大容量)
- **`corePoolSize` :** 核心线程数线程数定义了**最小可以同时运行的线程数量**。(即使这些线程处于空闲状态,这些线程也不会被销毁)
- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,**当前可以同时运行的线程数量变为最大线程数**。
- **`workQueue`:** 当新任务来的时候会**先判断当前运行的线程数量是否达到核心线程数**,**如果达到的话,新任务就会被存放在队列中**。
`ThreadPoolExecutor`其他常见参数:
1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁;
2. **`unit`** : `keepAliveTime` 参数的时间单位。
3. **`threadFactory`** :executor 创建新线程的时候会用到。
4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。
下面这张图可以加深你对线程池中各个参数的相互关系的理解(图片来源:《Java 性能调优实战》):

按照阿里巴巴推荐的设置:
```java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemo {
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
//使用阿里巴巴推荐的创建线程池的方式
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
//创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
Runnable worker = new MyRunnable("" + i);
//执行Runnable
executor.execute(worker);
}
//终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
```
可以看到我们上面的代码指定了:
1. `corePoolSize`: 核心线程数为 5。
2. `maximumPoolSize` :最大线程数 10
3. `keepAliveTime` : 等待时间为 1L。
4. `unit`: 等待时间的单位为 TimeUnit.SECONDS。
5. `workQueue`:任务队列为 `ArrayBlockingQueue`,并且容量为 100;
6. `handler`:饱和策略为 `CallerRunsPolicy`。
## 6.线程池中newCacheExecutorPool和newFixedExecutorPool的区别是什么?fix是什么意思?简单概述
这是两个Executors的两个方法,用于创建这两种线程池。
```java
ExecutorService pool = Executors.newFixedThreadPool(40); //创建固定线程数的线程池
final static ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //创建根据需要创建线程的线程池
//其实还有一种
SingleThreadExecutor() //这个是只有一个线程的线程池
```
详细的常用线程的区别,看一下JavaGuide中java线程池并发总结。
## 7. 线程池的几种配置方式,缺点是什么?
#### 1 FixedThreadPool
##### 1.1 介绍
`FixedThreadPool` 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现:
```java
/**
* 创建一个可重用固定数量线程的线程池
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
```
另外还有一个 `FixedThreadPool` 的实现方法,和上面的类似,所以这里不多做阐述:
```java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
```
**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。**
##### 1.2 执行任务过程介绍
`FixedThreadPool` 的 `execute()` 方法运行示意图(该图片来源:《Java 并发编程的艺术》):

**上图说明:**
1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务;
2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`;
3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行;
##### 1.3 为什么不推荐使用`FixedThreadPool`?
**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :**
1. 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize;
2. 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。
3. 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数;
4. 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。
#### 2 SingleThreadExecutor 详解
##### 2.1 介绍
`SingleThreadExecutor` 是只有一个线程的线程池。下面看看**SingleThreadExecutor 的实现:**
```java
/**
*返回只有一个线程的线程池
*/
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
```
```java
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
```
从上面源代码可以看出新创建的 `SingleThreadExecutor` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 1.其他参数和 `FixedThreadPool` 相同。
##### 2.2 执行任务过程介绍
**`SingleThreadExecutor` 的运行示意图(该图片来源:《Java 并发编程的艺术》):**

**上图说明;**
1. 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务;
2. 当前线程池中有一个运行的线程后,将任务加入 `LinkedBlockingQueue`
3. 线程执行完当前的任务后,会在循环中反复从`LinkedBlockingQueue` 中获取任务来执行;
##### 2.3 为什么不推荐使用`SingleThreadExecutor`?
`SingleThreadExecutor` 使用无界队列 `LinkedBlockingQueue` 作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。`SingleThreadExecutor` 使用无界队列作为线程池的工作队列会对线程池带来的影响与 `FixedThreadPool` 相同。说简单点就是可能会导致 OOM,
#### 3 CachedThreadPool 详解
##### 3.1 介绍
`CachedThreadPool` 是一个会根据需要创建新线程的线程池。下面通过源码来看看 `CachedThreadPool` 的实现:
```java
/**
* 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
```
```java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
```
`CachedThreadPool` 的`corePoolSize` 被设置为空(0),`maximumPoolSize`被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。
##### 3.2 执行任务过程介绍
**CachedThreadPool 的 execute()方法的执行示意图(该图片来源:《Java 并发编程的艺术》):**

**上图说明:**
1. 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2;
2. 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成;
##### 3.3 为什么不推荐使用`CachedThreadPool`?
`CachedThreadPool`允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
## 8.如何配置ThreadPool,主要有几个参数?
上面第七个说的三种是阿里手册不推荐的线程池创建方式。
#### 推荐的是:推荐使用 `ThreadPoolExecutor` 构造函数创建线程池
**在《阿里巴巴 Java 开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。**
**为什么呢?**
> **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。**
**另外《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险**
> Executors 返回线程池对象的弊端如下:
>
> - **`FixedThreadPool` 和 `SingleThreadExecutor`** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
**`ThreadPoolExecutor` 3 个最重要的参数:**
- **`corePoolSize` :** 核心线程数线程数定义了**最小可以同时运行的线程数量**。
- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,**当前可以同时运行的线程数量变为最大线程数**。
- **`workQueue`:** 当新任务来的时候会**先判断当前运行的线程数量是否达到核心线程数**,**如果达到的话,新任务就会被存放在队列中**。
`ThreadPoolExecutor`其他常见参数:
1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁;
2. **`unit`** : `keepAliveTime` 参数的时间单位。
3. **`threadFactory`** :executor 创建新线程的时候会用到。
4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。
下面这张图可以加深你对线程池中各个参数的相互关系的理解(图片来源:《Java 性能调优实战》):

## 9.Callable和Runable有什么区别,run()和start()有什么区别,join()和yield()有什么区别,execute()和submit()的区别,shutdown()和shutdownNow()的区别,isTerminated()和isShutdown()区别,
#### 1 `Runnable` vs `Callable`
`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。
工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。
`Runnable.java`
```java
@FunctionalInterface
public interface Runnable {
/**
* 被线程执行,没有返回值也无法抛出异常
*/
public abstract void run();
}
```
`Callable.java`
```java
@FunctionalInterface
public interface Callable<V> {
/**
* 计算结果,或在无法这样做时抛出异常。
* @return 计算得出的结果
* @throws 如果无法计算结果,则抛出异常
*/
V call() throws Exception;
}
```
#### 2 `execute()` vs `submit()`
1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码:
```java
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
```
上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。
```java
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
```
我们再来看看`execute()`方法:
```java
public void execute(Runnable command) {
...
}
```
#### 3 `shutdown()`VS`shutdownNow()`
- **`shutdown()`** :关闭线程池,线程池的状态变为 `SHUTDOWN`。线程池不再接受新任务了,但是队列里的任务得执行完毕。
- **`shutdownNow()`** :关闭线程池,线程的状态变为 `STOP`。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
#### 4 `isTerminated()` VS `isShutdown()`
- **`isShutDown`** 当调用 `shutdown()` 方法后返回为 true。
- **`isTerminated`** 当调用 `shutdown()` 方法后,并且所有提交的任务完成后返回为 true
#### 5.run( ) VS start()
```java
public class CallableDemo1 {
public static void main(String[] args) {
//创建方式有点和前两种都不一样
MyCallable callable1 = new MyCallable();
MyCallable callable2 = new MyCallable();
MyCallable callable3 = new MyCallable();
FutureTask<String> task1 = new FutureTask<String>(callable1);
FutureTask<String> task2 = new FutureTask<String>(callable1);
FutureTask<String> task3 = new FutureTask<String>(callable1);
new Thread(task1,"窗口1:").run();
new Thread(task2,"窗口2:").run();
new Thread(task3,"窗口3:").run();
try {
System.out.println("窗口1返回的结果为:" + task1.get());
System.out.println("窗口2返回的结果为:" + task2.get());
System.out.println("窗口3返回的结果为:" + task3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
```
看上面线程创建的第三种方式,如果把.start(),修改为.run(),结果会怎么呢?
```java
main:正在卖票,剩下票数:29
main:正在卖票,剩下票数:28
main:正在卖票,剩下票数:27
main:正在卖票,剩下票数:26
main:正在卖票,剩下票数:25
main:正在卖票,剩下票数:24
main:正在卖票,剩下票数:23
main:正在卖票,剩下票数:22
main:正在卖票,剩下票数:21
main:正在卖票,剩下票数:20
main:正在卖票,剩下票数:19
main:正在卖票,剩下票数:18
main:正在卖票,剩下票数:17
main:正在卖票,剩下票数:16
main:正在卖票,剩下票数:15
main:正在卖票,剩下票数:14
main:正在卖票,剩下票数:13
main:正在卖票,剩下票数:12
main:正在卖票,剩下票数:11
main:正在卖票,剩下票数:10
main:正在卖票,剩下票数:9
main:正在卖票,剩下票数:8
main:正在卖票,剩下票数:7
main:正在卖票,剩下票数:6
main:正在卖票,剩下票数:5
main:正在卖票,剩下票数:4
main:正在卖票,剩下票数:3
main:正在卖票,剩下票数:2
main:正在卖票,剩下票数:1
main:正在卖票,剩下票数:0
窗口1返回的结果为:票卖完了
窗口2返回的结果为:票卖完了
窗口3返回的结果为:票卖完了
```
就是说,调用start()方法,其实执行的是异步操作,三个线程进行售票。
如果在main()方法里面调用的是run()方法,则进行的是同步操作,就是说,其实只有main方法在执行。
#### 6.join() VS yield()
线程的转换状态:

线程的生命周期:

由上面图可以看出:
**yield()**(这个也叫礼让状态)方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法**只能使同优先级或者高优先级的线程得到执行机会**,这也和sleep()方法不同。
**join()** (这个也叫自闭状态)方法会使当前线程等待调用join()方法的线程结束后才能继续执行,例如:
```java
public class TestJoin {
public static void main(String[] args) {
Thread thread = new Thread(new JoinDemo());
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程第" + i + "次执行!");
if (i >= 2)
try {
// t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class JoinDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程1第" + i + "次执行!");
}
}
}
```
运行结果:
```java
主线程第0次执行!
主线程第1次执行!
主线程第2次执行!
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
主线程第3次执行!
主线程第4次执行!
主线程第5次执行!
主线程第6次执行!
主线程第7次执行!
主线程第8次执行!
主线程第9次执行!
主线程第10次执行!
主线程第11次执行!
主线程第12次执行!
主线程第13次执行!
主线程第14次执行!
主线程第15次执行!
主线程第16次执行!
主线程第17次执行!
主线程第18次执行!
主线程第19次执行!
```
参考:https://www.cnblogs.com/aqiu-jiang/p/11850692.html
#### 7.sleep() VS wait()
- 两者最主要的区别在于:**sleep 方法没有释放锁,而 wait 方法释放了锁** 。
- 两者都可以暂停线程的执行。
- Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。
## 10.线程池刚创建时具体有几个线程,什么时候会创建新线程?
#### 0个
```java
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 0, TimeUnit.DAYS,
new ArrayBlockingQueue<>(1), new ThreadFactory() {
```
就是上面这句话写了以后,其实是没有创建线程的。
创建线程的是任务提交的时候。
如果线程数量达到核心线程数,再增加新的任务,会先将任务挂起到就绪队列。如果就绪队列已满,才会创建新的线程。
#### 还有一些常见问题:
Q. 线程池是什么时候创建线程的?
*A.任务提交的时候*
Q.任务runnable task是先放到core到maxThread之间的线程,还是先放到队列?
*A.先放队列!!!*
Q. 队列中的任务是什么时候取出来的?
A. worker中 runWorker() 一个任务完成后,会取下一个任务
Q. 什么时候会触发reject策略?
A.队列满并且maxthread也满了, 还有新任务,默认策略是reject
Q. core到maxThread之间的线程什么时候会die?
A. 没有任务时,或者抛异常时。
core线程也会die的,core到maxThread之间的线程有可能会晋升到core线程区间,
core max只是个计数,线程并不是创建后就固定在一个区间了
Q. task抛出异常,线程池中这个work thread还能运行其他任务吗?
A. 不能。 但是会创建新的线程, 新线程可以运行其他task。
对于 schedulerThreadPoolExecutor? 虽然有新线程,但是旧的循环任务不会再继续执行了, 开发实践推荐任务中捕获所有Exception
参考:https://www.cnblogs.com/yszzu/p/10122658.html 这篇文章写得很好,有原码分析。
## 11.线程的状态和生命周期
线程的转换状态:

线程的生命周期:

## 12.乐观锁和悲观锁区别,讲讲轻量级锁和CAS自旋锁?
#### 乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
#### 悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
#### 乐观锁应用:
##### 1.版本号机制(项目中使用的是每次更新数据以后更新时间,判断时间是不是一样)
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
举一个简单的例子:
假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。
操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。
##### 2.CAS算法
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
#### 乐观锁的缺点
ABA 问题是乐观锁一个常见的问题
##### 1 ABA 问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

注:t1,t2线程是同时更新同一变量56的值
因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。
假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。
(上图通俗的解释是:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)
参考:https://www.jianshu.com/p/21be831e851e(CAS算法)
https://blog.csdn.net/qq_34337272/article/details/81072874 (乐观锁和悲观锁)
上面是乐观锁和悲观锁。
#### JDK1.6 之后的底层优化(偏向锁,轻量级锁,自旋锁和自适应自旋,锁消除,锁粗化)
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
**①偏向锁**