-
Notifications
You must be signed in to change notification settings - Fork 683
/
Copy pathAQS 万字图文全面解析.md.html
1088 lines (990 loc) · 61.7 KB
/
AQS 万字图文全面解析.md.html
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
<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>AQS 万字图文全面解析.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a class="current-tab" href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决(上).md.html">Java中9种常见的CMS GC问题分析与解决(上).md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决(下).md.html">Java中9种常见的CMS GC问题分析与解决(下).md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类:Unsafe应用解析.md.html">Java魔法类:Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化:优化 select count().md.html">MySQL优化:优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC(多版本并发控制).md.html">MySQL的MVCC(多版本并发控制).md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始,聊微服务架构.md.html">从SpringCloud开始,聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时,我们在讨论些神马?.md.html">当我们在讨论CQRS时,我们在讨论些神马?.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析(Eureka、Zookeeper、Nacos).md.html">服务注册与发现原理剖析(Eureka、Zookeeper、Nacos).md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶:Dockerfile 高阶使用指南及镜像优化.md.html">进阶:Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析(上).md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析(上).md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析(下).md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析(下).md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>AQS 万字图文全面解析</h1>
<h3>前言</h3>
<p>谈到并发,我们不得不说<code>AQS(AbstractQueuedSynchronizer)</code>,所谓的<code>AQS</code>即是抽象的队列式的同步器,内部定义了很多锁相关的方法,我们熟知的<code>ReentrantLock</code>、<code>ReentrantReadWriteLock</code>、<code>CountDownLatch</code>、<code>Semaphore</code>等都是基于<code>AQS</code>来实现的。</p>
<p>我们先看下<code>AQS</code>相关的<code>UML</code>图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjNDZiNGRlMQ.jfif" alt="image.png" /></p>
<p>思维导图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjNWQ2OTMzOA.jfif" alt="image.png" /></p>
<h3>AQS 实现原理</h3>
<p><code>AQS</code>中 维护了一个<code>volatile int state</code>(代表共享资源)和一个<code>FIFO</code>线程等待队列(多线程争用资源被阻塞时会进入此队列)。</p>
<p>这里<code>volatile</code>能够保证多线程下的可见性,当<code>state=1</code>则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个<code>FIFO</code>的等待队列中,比列会被<code>UNSAFE.park()</code>操作挂起,等待其他获取锁的线程释放锁才能够被唤醒。</p>
<p>另外<code>state</code>的操作都是通过<code>CAS</code>来保证其并发修改的安全性。</p>
<p>具体原理我们可以用一张图来简单概括:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjNzdiMjc2NQ.jfif" alt="image.png" /></p>
<p><code>AQS</code> 中提供了很多关于锁的实现方法,</p>
<ul>
<li>getState():获取锁的标志 state 值</li>
<li>setState():设置锁的标志 state 值</li>
<li>tryAcquire(int):独占方式获取锁。尝试获取资源,成功则返回 true,失败则返回 false。</li>
<li>tryRelease(int):独占方式释放锁。尝试释放资源,成功则返回 true,失败则返回 false。</li>
</ul>
<p>这里还有一些方法并没有列出来,接下来我们以<code>ReentrantLock</code>作为突破点通过源码和画图的形式一步步了解<code>AQS</code>内部实现原理。</p>
<h3>目录结构</h3>
<p>文章准备模拟多线程竞争锁、释放锁的场景来进行分析<code>AQS</code>源码:</p>
<p><strong>三个线程(线程一、线程二、线程三)同时来加锁/释放锁</strong></p>
<p><strong>目录如下:</strong></p>
<ul>
<li><strong>线程一</strong>加锁成功时<code>AQS</code>内部实现</li>
<li><strong>线程二/三</strong>加锁失败时<code>AQS</code>中等待队列的数据模型</li>
<li><strong>线程一</strong>释放锁及<strong>线程二</strong>获取锁实现原理</li>
<li>通过线程场景来讲解<strong>公平锁</strong>具体实现原理</li>
<li>通过线程场景来讲解 Condition 中 a<code>wait()</code>和<code>signal()</code>实现原理</li>
</ul>
<p>这里会通过画图来分析每个线程加锁、释放锁后<code>AQS</code>内部的数据结构和实现原理</p>
<h3>场景分析</h3>
<h4>线程一加锁成功</h4>
<p>如果同时有<strong>三个线程</strong>并发抢占锁,此时<strong>线程一</strong>抢占锁成功,<strong>线程二</strong>和<strong>线程三</strong>抢占锁失败,具体执行流程如下:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjNWQyZWIyYw.jfif" alt="image.png" /></p>
<p>此时<code>AQS</code>内部数据为:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjNzc5OTlkNQ.jfif" alt="image.png" /></p>
<p><strong>线程二</strong>、<strong>线程三</strong>加锁失败:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjOGU1ZTI2OA.jfif" alt="image.png" /></p>
<p>有图可以看出,等待队列中的节点<code>Node</code>是一个双向链表,这里<code>SIGNAL</code>是<code>Node</code>中<code>waitStatus</code>属性,<code>Node</code>中还有一个<code>nextWaiter</code>属性,这个并未在图中画出来,这个到后面<code>Condition</code>会具体讲解的。</p>
<p>具体看下抢占锁代码实现:</p>
<pre><code>java.util.concurrent.locks.ReentrantLock .NonfairSync:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
</code></pre>
<p>这里使用的<strong>ReentrantLock 非公平锁</strong>,线程进来直接利用<code>CAS</code>尝试抢占锁,如果抢占成功<code>state</code>值回被改为 1,且设置对象独占锁线程为当前线程。如下所示:</p>
<pre><code>protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
</code></pre>
<h4>线程二抢占锁失败</h4>
<p>我们按照真实场景来分析,<strong>线程一</strong>抢占锁成功后,<code>state</code>变为 1,<strong>线程二</strong>通过<code>CAS</code>修改<code>state</code>变量必然会失败。此时<code>AQS</code>中<code>FIFO</code>(First In First Out 先进先出)队列中数据如图所示:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDMwYjUyNTZkZQ.jfif" alt="image.png" /></p>
<p>我们将<strong>线程二</strong>执行的逻辑一步步拆解来看:</p>
<p><code>java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire()</code>:</p>
<pre><code>public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
</code></pre>
<p>先看看<code>tryAcquire()</code>的具体实现: <code>java.util.concurrent.locks.ReentrantLock .nonfairTryAcquire()</code>:</p>
<pre><code>final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
</code></pre>
<p><code>nonfairTryAcquire()</code>方法中首先会获取<code>state</code>的值,如果不为 0 则说明当前对象的锁已经被其他线程所占有,接着判断占有锁的线程是否为当前线程,如果是则累加<code>state</code>值,这就是可重入锁的具体实现,累加<code>state</code>值,释放锁的时候也要依次递减<code>state</code>值。</p>
<p>如果<code>state</code>为 0,则执行<code>CAS</code>操作,尝试更新<code>state</code>值为 1,如果更新成功则代表当前线程加锁成功。</p>
<p>以<strong>线程二</strong>为例,因为<strong>线程一</strong>已经将<code>state</code>修改为 1,所以<strong>线程二</strong>通过<code>CAS</code>修改<code>state</code>的值不会成功。加锁失败。</p>
<p><strong>线程二</strong>执行<code>tryAcquire()</code>后会返回 false,接着执行<code>addWaiter(Node.EXCLUSIVE)</code>逻辑,将自己加入到一个<code>FIFO</code>等待队列中,代码实现如下:</p>
<p><code>java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter()</code>:</p>
<pre><code>private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
</code></pre>
<p>这段代码首先会创建一个和当前线程绑定的<code>Node</code>节点,<code>Node</code>为双向链表。此时等待对内中的<code>tail</code>指针为空,直接调用<code>enq(node)</code>方法将当前线程加入等待队列尾部:</p>
<pre><code>private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
</code></pre>
<p>第一遍循环时<code>tail</code>指针为空,进入 if 逻辑,使用<code>CAS</code>操作设置<code>head</code>指针,将<code>head</code>指向一个新创建的<code>Node</code>节点。此时<code>AQS</code>中数据:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDMxZGZmMGI2Zg.jfif" alt="image.png" /></p>
<p>执行完成之后,<code>head</code>、<code>tail</code>、<code>t</code>都指向第一个<code>Node</code>元素。</p>
<p>接着执行第二遍循环,进入<code>else</code>逻辑,此时已经有了<code>head</code>节点,这里要操作的就是将<strong>线程二</strong>对应的<code>Node</code>节点挂到<code>head</code>节点后面。此时队列中就有了两个<code>Node</code>节点:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDMyMTk0MTdmNQ.jfif" alt="image.png" /></p>
<p><code>addWaiter()</code>方法执行完后,会返回当前线程创建的节点信息。继续往后执行<code>acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</code> 逻辑,此时传入的参数为<strong>线程二</strong>对应的<code>Node</code>节点信息:</p>
<p><code>java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued()</code>:</p>
<pre><code>final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndChecknIterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
</code></pre>
<p><code>acquireQueued()</code>这个方法会先判断当前传入的<code>Node</code>对应的前置节点是否为<code>head</code>,如果是则尝试加锁。加锁成功过则将当前节点设置为<code>head</code>节点,然后空置之前的<code>head</code>节点,方便后续被垃圾回收掉。</p>
<p>如果加锁失败或者<code>Node</code>的前置节点不是<code>head</code>节点,就会通过<code>shouldParkAfterFailedAcquire</code>方法 将<code>head</code>节点的<code>waitStatus</code>变为了<code>SIGNAL=-1</code>,最后执行<code>parkAndChecknIterrupt</code>方法,调用<code>LockSupport.park()</code>挂起当前线程。</p>
<p>此时<code>AQS</code>中的数据如下图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDMyNzYxZDk2MQ.jfif" alt="image.png" /></p>
<p>此时<strong>线程二</strong>就静静的待在<code>AQS</code>的等待队列里面了,等着其他线程释放锁来唤醒它。</p>
<h4>线程三抢占锁失败</h4>
<p>看完了<strong>线程二</strong>抢占锁失败的分析,那么再来分析<strong>线程三</strong>抢占锁失败就很简单了,先看看<code>addWaiter(Node mode)</code>方法:</p>
<pre><code>private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
</code></pre>
<p>此时等待队列的<code>tail</code>节点指向<strong>线程二</strong>,进入<code>if</code>逻辑后,通过<code>CAS</code>指令将<code>tail</code>节点重新指向<strong>线程三</strong>。接着<strong>线程三</strong>调用<code>enq()</code>方法执行入队操作,和上面<strong>线程二</strong>执行方式是一致的,入队后会修改<strong>线程二</strong>对应的<code>Node</code>中的<code>waitStatus=SIGNAL</code>。最后<strong>线程三</strong>也会被挂起。此时等待队列的数据如图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDJjOGU1ZTI2OA-1590419214352.jfif" alt="image.png" /></p>
<h4>线程一释放锁</h4>
<p>现在来分析下释放锁的过程,首先是<strong>线程一</strong>释放锁,释放锁后会唤醒<code>head</code>节点的后置节点,也就是我们现在的<strong>线程二</strong>,具体操作流程如下:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDMzMmRmNmZkNQ.jfif" alt="image.png" /></p>
<p>执行完后等待队列数据如下:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDM0MzkzYzU4Zg.jfif" alt="image.png" /></p>
<p>此时<strong>线程二</strong>已经被唤醒,继续尝试获取锁,如果获取锁失败,则会继续被挂起。如果获取锁成功,则<code>AQS</code>中数据如图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDM1ZDI0OThhYg.jfif" alt="image.png" /></p>
<p>接着还是一步步拆解来看,先看看<strong>线程一</strong>释放锁的代码:</p>
<pre><code>java.util.concurrent.locks.AbstractQueuedSynchronizer.release()
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
</code></pre>
<p>这里首先会执行<code>tryRelease()</code>方法,这个方法具体实现在<code>ReentrantLock</code>中,如果<code>tryRelease</code>执行成功,则继续判断<code>head</code>节点的<code>waitStatus</code>是否为 0,前面我们已经看到过,<code>head</code>的<code>waitStatue</code>为<code>SIGNAL(-1)</code>,这里就会执行<code>unparkSuccessor()</code>方法来唤醒<code>head</code>的后置节点,也就是我们上面图中<strong>线程二</strong>对应的<code>Node</code>节点。</p>
<p>此时看<code>ReentrantLock.tryRelease()</code>中的具体实现:</p>
<pre><code>protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
</code></pre>
<p>执行完<code>ReentrantLock.tryRelease()</code>后,<code>state</code>被设置成 0,Lock 对象的独占锁被设置为 null。此时看下<code>AQS</code>中的数据:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDM2M2U4NzkwMg.jfif" alt="image.png" /></p>
<p>接着执行<code>java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor()</code>方法,唤醒<code>head</code>的后置节点:</p>
<pre><code>private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
</code></pre>
<p>这里主要是将<code>head</code>节点的<code>waitStatus</code>设置为 0。</p>
<p>此时重新将<code>head</code>指针指向<strong>线程二</strong>对应的<code>Node</code>节点,且使用<code>LockSupport.unpark</code>方法来唤醒<strong>线程二</strong>。</p>
<p>被唤醒的<strong>线程二</strong>会接着尝试获取锁,用<code>CAS</code>指令修改<code>state</code>数据。 执行完成后可以查看<code>AQS</code>中数据:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDM0MzkzYzU4Zg-1590419228808.jfif" alt="image.png" /></p>
<p>此时<strong>线程二</strong>被唤醒,<strong>线程二</strong>接着之前被<code>park</code>的地方继续执行,继续执行<code>acquireQueued()</code>方法。</p>
<h4>线程二唤醒继续加锁</h4>
<pre><code>final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
</code></pre>
<p>此时<strong>线程二</strong>被唤醒,继续执行<code>for</code>循环,判断<strong>线程二</strong>的前置节点是否为<code>head</code>,如果是则继续使用<code>tryAcquire()</code>方法来尝试获取锁,其实就是使用<code>CAS</code>操作来修改<code>state</code>值,如果修改成功则代表获取锁成功。接着将<strong>线程二</strong>设置为<code>head</code>节点,然后空置之前的<code>head</code>节点数据,被空置的节点数据等着被<strong>垃圾回收</strong>。</p>
<p>此时线程二获取锁成功,<code>AQS</code>中队列数据如下:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDM3ZWI5MzQ5MA.jfif" alt="image.png" /></p>
<p>等待队列中的数据都等待着被垃圾回收。</p>
<h4>线程二释放锁/线程三加锁</h4>
<p>当<strong>线程二</strong>释放锁时,会唤醒被挂起的<strong>线程三</strong>,流程和上面大致相同,被唤醒的<strong>线程三</strong>会再次尝试加锁,具体代码可以参考上面内容。具体流程图如下:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNhMWEyNmU5NA.jfif" alt="image.png" /></p>
<p>此时<code>AQS</code>中队列数据如图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNhYjVmZDRjNw.jfif" alt="image.png" /></p>
<h3>公平锁实现原理</h3>
<p>上面所有的加锁场景都是基于<strong>非公平锁</strong>来实现的,<strong>非公平锁</strong>是<code>ReentrantLock</code>的默认实现,那我们接着来看一下<strong>公平锁</strong>的实现原理,这里先用一张图来解释<strong>公平锁</strong>和<strong>非公平锁</strong>的区别:</p>
<p><strong>非公平锁</strong>执行流程:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNjMjExMjgzOQ.jfif" alt="image.png" /></p>
<p>这里我们还是用之前的线程模型来举例子,当<strong>线程二</strong>释放锁的时候,唤醒被挂起的<strong>线程三</strong>,<strong>线程三</strong>执行<code>tryAcquire()</code>方法使用<code>CAS</code>操作来尝试修改<code>state</code>值,如果此时又来了一个<strong>线程四</strong>也来执行加锁操作,同样会执行<code>tryAcquire()</code>方法。</p>
<p>这种情况就会出现竞争,<strong>线程四</strong>如果获取锁成功,<strong>线程三</strong>仍然需要待在等待队列中被挂起。这就是所谓的<strong>非公平锁</strong>,<strong>线程三</strong>辛辛苦苦排队等到自己获取锁,却眼巴巴的看到<strong>线程四</strong>插队获取到了锁。</p>
<p><strong>公平锁</strong>执行流程:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNjMzQ2MmQ2YQ.jfif" alt="image.png" /></p>
<p>公平锁在加锁的时候,会先判断<code>AQS</code>等待队列中是存在节点,如果存在节点则会直接入队等待,具体代码如下.</p>
<p>公平锁在获取锁是也是首先会执行<code>acquire()</code>方法,只不过公平锁单独实现了<code>tryAcquire()</code>方法:</p>
<p><code>#java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire()</code>:</p>
<pre><code>public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
</code></pre>
<p>这里会执行<code>ReentrantLock</code>中公平锁的<code>tryAcquire()</code>方法</p>
<p><code>#java.util.concurrent.locks.ReentrantLock.FairSync.tryAcquire()</code>:</p>
<pre><code>static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
</code></pre>
<p>这里会先判断<code>state</code>值,如果不为 0 且获取锁的线程不是当前线程,直接返回 false 代表获取锁失败,被加入等待队列。如果是当前线程则可重入获取锁。</p>
<p>如果<code>state=0</code>则代表此时没有线程持有锁,执行<code>hasQueuedPredecessors()</code>判断<code>AQS</code>等待队列中是否有元素存在,如果存在其他等待线程,那么自己也会加入到等待队列尾部,做到真正的先来后到,有序加锁。具体代码如下:</p>
<p><code>#java.util.concurrent.locks.AbstractQueuedSynchronizer.hasQueuedPredecessors()</code>:</p>
<pre><code>public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
</code></pre>
<p>这段代码很有意思,返回<code>false</code>代表队列中没有节点或者仅有一个节点是当前线程创建的节点。返回<code>true</code>则代表队列中存在等待节点,当前线程需要入队等待。</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNlMDM2NzlhZA.jfif" alt="image.png" /></p>
<p>先判断<code>head</code>是否等于<code>tail</code>,如果队列中只有一个<code>Node</code>节点,那么<code>head</code>会等于<code>tail</code>。</p>
<p>接着判断<code>(s = h.next) == null</code>,这种属于一种极端情况,在<code>enq()</code>入队操作中,此时不是原子性操作,可能存在这种情况:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE2LzE3MjFjZDI1MTUzNmU3Y2M.jfif" alt="YcAPpD.png" /></p>
<p>在第一个红框处,例如 <strong>线程一</strong> 执行完成,此时 head 已经有值,而还未执行<code>tail=head</code>的时候,此时 <strong>线程二</strong> 判断 <code>head != tail</code>成立。而接着 <strong>线程一</strong> 执行完第二个红框处,此时<code>tail = node</code>,但是并未将<code>head.next</code>指向<code>node</code>。而这时 <strong>线程二</strong> 就会得到<code>head.next == null</code>成立,直接返回 true。这种情况代表有节点正在做入队操作。</p>
<p>如果<code>head.next</code>不为空,那么接着判断<code>head.next</code>节点是否为当前线程,如果不是则返回 false。大家要记清楚,返回 false 代表 FIFO 队列中没有等待获取锁的节点,此时线程可以直接尝试获取锁,如果返回 true 代表有等待线程,当前线程如要入队排列,这就是体现<strong>公平锁</strong>的地方。</p>
<p><strong>非公平锁</strong>和<strong>公平锁</strong>的区别: <strong>非公平锁</strong>性能高于<strong>公平锁</strong>性能。<strong>非公平锁</strong>可以减少<code>CPU</code>唤醒线程的开销,整体的吞吐效率会高点,<code>CPU</code>也不必取唤醒所有线程,会减少唤起线程的数量</p>
<p><strong>非公平锁</strong>性能虽然优于<strong>公平锁</strong>,但是会存在导致<strong>线程饥饿</strong>的情况。在最坏的情况下,可能存在某个线程<strong>一直获取不到锁</strong>。不过相比性能而言,饥饿问题可以暂时忽略,这可能就是<code>ReentrantLock</code>默认创建非公平锁的原因之一了。</p>
<h3>Condition 实现原理</h3>
<h4>Condition 简介</h4>
<p>上面已经介绍了<code>AQS</code>所提供的核心功能,当然它还有很多其他的特性,这里我们来继续说下<code>Condition</code>这个组件。</p>
<pre><code>Condition`是在`java 1.5`中才出现的,它用来替代传统的`Object`的`wait()`、`notify()`实现线程间的协作,相比使用`Object`的`wait()`、`notify()`,使用`Condition`中的`await()`、`signal()`这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用`Condition
</code></pre>
<p>其中<code>AbstractQueueSynchronizer</code>中实现了<code>Condition</code>中的方法,主要对外提供<code>awaite(Object.wait())</code>和<code>signal(Object.notify())</code>调用。</p>
<h4>Condition Demo 示例</h4>
<p>使用示例代码:</p>
<pre><code>/**
* ReentrantLock 实现源码学习
* @author 一枝花算不算浪漫
* @date 2020/4/28 7:20
*/
public class ReentrantLockDemo {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println("线程一加锁成功");
System.out.println("线程一执行 await 被挂起");
condition.await();
System.out.println("线程一被唤醒成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("线程一释放锁成功");
}
}).start();
new Thread(() -> {
lock.lock();
try {
System.out.println("线程二加锁成功");
condition.signal();
System.out.println("线程二唤醒线程一");
} finally {
lock.unlock();
System.out.println("线程二释放锁成功");
}
}).start();
}
}
</code></pre>
<p>执行结果如下图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNlNjc4NTFiMw.jfif" alt="image.png" /></p>
<p>这里<strong>线程一</strong>先获取锁,然后使用<code>await()</code>方法挂起当前线程并<strong>释放锁</strong>,<strong>线程二</strong>获取锁后使用<code>signal</code>唤醒<strong>线程一</strong>。</p>
<h4>Condition 实现原理图解</h4>
<p>我们还是用上面的<code>demo</code>作为实例,执行的流程如下:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDNmOTE5NmRlYw.jfif" alt="image.png" /></p>
<p><strong>线程一</strong>执行<code>await()</code>方法:</p>
<p>先看下具体的代码实现,<code>#java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.await()</code>:</p>
<pre><code> public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
</code></pre>
<p><code>await()</code>方法中首先调用<code>addConditionWaiter()</code>将当前线程加入到<code>Condition</code>队列中。</p>
<p>执行完后我们可以看下<code>Condition</code>队列中的数据:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDQwM2Y0NTRiYw.jfif" alt="image.png" /></p>
<p>具体实现代码为:</p>
<pre><code>private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
</code></pre>
<p>这里会用当前线程创建一个<code>Node</code>节点,<code>waitStatus</code>为<code>CONDITION</code>。接着会释放该节点的锁,调用之前解析过的<code>release()</code>方法,释放锁后此时会唤醒被挂起的<strong>线程二</strong>,<strong>线程二</strong>会继续尝试获取锁。</p>
<p>接着调用<code>isOnSyncQueue()</code>方法是判断当前的线程节点是不是在同步队列中,因为上一步已经释放了锁,也就是说此时可能有线程已经获取锁同时可能已经调用了<code>singal()</code>方法,如果已经唤醒,那么就不应该<code>park</code>了,而是退出<code>while</code>方法,从而继续争抢锁。</p>
<p>此时<strong>线程一</strong>被挂起,<strong>线程二</strong>获取锁成功。</p>
<p>具体流程如下图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDQyNTBmMzA4OQ.jfif" alt="image.png" /></p>
<p><strong>线程二</strong>执行<code>signal()</code>方法:</p>
<p>首先我们考虑下<strong>线程二</strong>已经获取到锁,此时<code>AQS</code>等待队列中已经没有了数据。</p>
<p>接着就来看看<strong>线程二</strong>唤醒<strong>线程一</strong>的具体执行流程:</p>
<pre><code>public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
</code></pre>
<p>先判断当前线程是否为获取锁的线程,如果不是则直接抛出异常。 接着调用<code>doSignal()</code>方法来唤醒线程。</p>
<pre><code>private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
</code></pre>
<p>这里先从<code>transferForSignal()</code>方法来看,通过上面的分析我们知道<code>Condition</code>队列中只有线程一创建的一个<code>Node</code>节点,且<code>waitStatue</code>为<code>CONDITION</code>,先通过<code>CAS</code>修改当前节点<code>waitStatus</code>为 0,然后执行<code>enq()</code>方法将当前线程加入到等待队列中,并返回当前线程的前置节点。</p>
<p>加入等待队列的代码在上面也已经分析过,此时等待队列中数据如下图:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDQyNWZhMmU5ZQ.jfif" alt="image.png" /></p>
<p>接着开始通过<code>CAS</code>修改当前节点的前置节点<code>waitStatus</code>为<code>SIGNAL</code>,并且唤醒当前线程。此时<code>AQS</code>中等待队列数据为:</p>
<p><img src="assets/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzIvMTcxZDJkNDQ0ODQ0ZDI3ZQ.jfif" alt="image.png" /></p>
<p><strong>线程一</strong>被唤醒后,继续执行<code>await()</code>方法中的 while 循环。</p>
<pre><code>public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}