-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
695 lines (332 loc) · 365 KB
/
atom.xml
File metadata and controls
695 lines (332 loc) · 365 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>GoofySatoshi's Blog</title>
<subtitle>生活、技术与阅读</subtitle>
<link href="https://icarus-blog.top/atom.xml" rel="self"/>
<link href="https://icarus-blog.top/"/>
<updated>2025-12-11T06:48:10.627Z</updated>
<id>https://icarus-blog.top/</id>
<author>
<name>GoofySatoshi</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>JVM核心知识点八股文</title>
<link href="https://icarus-blog.top/jvm-interview-2025/"/>
<id>https://icarus-blog.top/jvm-interview-2025/</id>
<published>2025-12-11T07:30:00.000Z</published>
<updated>2025-12-11T06:48:10.627Z</updated>
<content type="html"><![CDATA[<h1 id="JVM-核心知识点八股文(高频面试版)"><a href="#JVM-核心知识点八股文(高频面试版)" class="headerlink" title="JVM 核心知识点八股文(高频面试版)"></a>JVM 核心知识点八股文(高频面试版)</h1><p>本文整理 JVM 面试中最常考察的核心知识点,覆盖内存结构、垃圾回收、类加载、性能调优等高频考点,适合面试复习和知识点梳理。</p><h2 id="一、JVM-内存结构(运行时数据区)"><a href="#一、JVM-内存结构(运行时数据区)" class="headerlink" title="一、JVM 内存结构(运行时数据区)"></a>一、JVM 内存结构(运行时数据区)</h2><p>JDK 8 及以上版本的内存结构移除了永久代(PermGen),替换为元空间(Metaspace),核心分为以下区域:</p><h3 id="1-线程私有区域"><a href="#1-线程私有区域" class="headerlink" title="1. 线程私有区域"></a>1. 线程私有区域</h3><ul><li><strong>程序计数器</strong>:记录当前线程执行的字节码行号,唯一不会抛出 OOM 的区域;</li><li><strong>虚拟机栈</strong>:存储方法调用的栈帧(局部变量表、操作数栈、动态链接、方法出口),栈深度溢出抛 <code>StackOverflowError</code>,内存不足抛 OOM;</li><li><strong>本地方法栈</strong>:为 Native 方法服务,与虚拟机栈逻辑一致,同样会抛 <code>StackOverflowError</code>/OOM。</li></ul><h3 id="2-线程共享区域"><a href="#2-线程共享区域" class="headerlink" title="2. 线程共享区域"></a>2. 线程共享区域</h3><ul><li><strong>堆</strong>:JVM 最大的内存区域,存储对象实例和数组,是垃圾回收(GC)的核心区域;<ul><li>分代:新生代(Eden + Survivor0 + Survivor1,比例 8:1:1)、老年代;</li><li>溢出:对象无法分配内存时抛 <code>OutOfMemoryError: Java heap space</code>。</li></ul></li><li><strong>元空间(Metaspace)</strong>:替代永久代,存储类元信息、常量池、方法字节码等,直接使用本地内存,默认无上限(可通过 <code>-XX:MetaspaceSize</code>/<code>-XX:MaxMetaspaceSize</code> 限制),溢出抛 <code>OutOfMemoryError: Metaspace</code>。</li></ul><h3 id="3-直接内存(非运行时数据区)"><a href="#3-直接内存(非运行时数据区)" class="headerlink" title="3. 直接内存(非运行时数据区)"></a>3. 直接内存(非运行时数据区)</h3><ul><li>基于 NIO 的堆外内存,不受 JVM 堆大小限制,但受物理内存限制,溢出抛 <code>OutOfMemoryError: Direct buffer memory</code>。</li></ul><h2 id="二、垃圾回收(GC)核心"><a href="#二、垃圾回收(GC)核心" class="headerlink" title="二、垃圾回收(GC)核心"></a>二、垃圾回收(GC)核心</h2><h3 id="1-如何判断对象可回收?"><a href="#1-如何判断对象可回收?" class="headerlink" title="1. 如何判断对象可回收?"></a>1. 如何判断对象可回收?</h3><ul><li><strong>引用计数法</strong>:对象引用数为 0 则标记为可回收,缺点:无法解决循环引用问题;</li><li><strong>可达性分析算法</strong>:以 GC Roots 为起点,遍历对象引用链,无引用链的对象标记为可回收;<ul><li>GC Roots 包括:虚拟机栈中引用的对象、本地方法栈中引用的对象、方法区中类静态属性/常量引用的对象。</li></ul></li></ul><h3 id="2-常见-GC-算法"><a href="#2-常见-GC-算法" class="headerlink" title="2. 常见 GC 算法"></a>2. 常见 GC 算法</h3><ul><li><strong>标记-清除(Mark-Sweep)</strong>:标记可回收对象 → 清除,缺点:内存碎片、效率低;</li><li><strong>标记-复制(Mark-Copy)</strong>:将内存分为两块,只使用一块,回收时将存活对象复制到另一块,优点:无内存碎片,缺点:内存利用率低(仅 50%),新生代(Eden/Survivor)采用此算法;</li><li><strong>标记-整理(Mark-Compact)</strong>:标记可回收对象 → 存活对象向一端移动 → 清除端外内存,无碎片、利用率 100%,老年代采用此算法。</li></ul><h3 id="3-经典垃圾收集器"><a href="#3-经典垃圾收集器" class="headerlink" title="3. 经典垃圾收集器"></a>3. 经典垃圾收集器</h3><table><thead><tr><th>收集器</th><th>适用区域</th><th>核心特点</th><th>回收线程</th></tr></thead><tbody><tr><td>Serial</td><td>新生代</td><td>单线程、STW 时间长、简单高效</td><td>单线程</td></tr><tr><td>ParNew</td><td>新生代</td><td>Serial 多线程版本</td><td>多线程</td></tr><tr><td>Parallel Scavenge</td><td>新生代</td><td>关注吞吐量(运行代码时间/总时间)</td><td>多线程</td></tr><tr><td>Serial Old</td><td>老年代</td><td>Serial 老年代版本</td><td>单线程</td></tr><tr><td>Parallel Old</td><td>老年代</td><td>Parallel Scavenge 老年代版本</td><td>多线程</td></tr><tr><td>CMS</td><td>老年代</td><td>低延迟、并发回收、三步 STW</td><td>多线程</td></tr><tr><td>G1</td><td>整堆</td><td>分区回收、兼顾吞吐量和延迟</td><td>多线程</td></tr><tr><td>ZGC/Shenandoah</td><td>整堆</td><td>超低延迟(毫秒级)、大内存友好</td><td>多线程</td></tr></tbody></table><h3 id="4-GC-触发时机"><a href="#4-GC-触发时机" class="headerlink" title="4. GC 触发时机"></a>4. GC 触发时机</h3><ul><li><strong>Minor GC(新生代 GC)</strong>:Eden 区满时触发,频率高、STW 时间短;</li><li><strong>Major GC(老年代 GC)</strong>:老年代空间不足时触发,常伴随 Minor GC,STW 时间长;</li><li><strong>Full GC</strong>:整堆回收(新生代+老年代+元空间),触发场景:老年代满、元空间满、System.gc() 显式调用、Minor GC 后老年代无法容纳晋升对象。</li></ul><h2 id="三、类加载机制"><a href="#三、类加载机制" class="headerlink" title="三、类加载机制"></a>三、类加载机制</h2><h3 id="1-类加载的完整流程"><a href="#1-类加载的完整流程" class="headerlink" title="1. 类加载的完整流程"></a>1. 类加载的完整流程</h3><ul><li><strong>加载</strong>:将类的字节码文件(.class)加载到内存,生成 Class 对象;</li><li><strong>验证</strong>:校验字节码合法性(文件格式、元数据、字节码指令、符号引用验证);</li><li><strong>准备</strong>:为类静态变量分配内存并赋默认值(如 int → 0,引用 → null);</li><li><strong>解析</strong>:将符号引用替换为直接引用(内存地址);</li><li><strong>初始化</strong>:执行静态代码块、为静态变量赋初始值(程序员定义的值)。</li></ul><h3 id="2-类加载器分类"><a href="#2-类加载器分类" class="headerlink" title="2. 类加载器分类"></a>2. 类加载器分类</h3><ul><li><strong>启动类加载器(Bootstrap ClassLoader)</strong>:C++ 实现,加载 JRE/lib 核心类(如 rt.jar);</li><li><strong>扩展类加载器(Extension ClassLoader)</strong>:Java 实现,加载 JRE/lib/ext 扩展类;</li><li><strong>应用类加载器(Application ClassLoader)</strong>:Java 实现,加载用户自定义类(classpath 下);</li><li><strong>自定义类加载器</strong>:继承 ClassLoader,实现自定义加载逻辑(如热部署、加密类加载)。</li></ul><h3 id="3-双亲委派机制"><a href="#3-双亲委派机制" class="headerlink" title="3. 双亲委派机制"></a>3. 双亲委派机制</h3><ul><li><strong>核心规则</strong>:类加载时先委托父加载器加载,父加载器无法加载时再自己加载;</li><li><strong>优点</strong>:避免类重复加载、保证核心类(如 java.lang.String)不被篡改;</li><li><strong>打破场景</strong>:Tomcat 类加载(为不同 Web 应用隔离类)、热部署、SPI 加载(如 JDBC)。</li></ul><h2 id="四、JVM-性能调优"><a href="#四、JVM-性能调优" class="headerlink" title="四、JVM 性能调优"></a>四、JVM 性能调优</h2><h3 id="1-核心调优参数"><a href="#1-核心调优参数" class="headerlink" title="1. 核心调优参数"></a>1. 核心调优参数</h3><pre><code class="bash"># 堆内存设置-Xms2g # 初始堆内存(建议与-Xmx一致,避免频繁扩容)-Xmx2g # 最大堆内存-Xmn512m # 新生代内存(建议占堆的 1/4~1/3)-XX:SurvivorRatio=8 # Eden:S0:S1 = 8:1:1-XX:NewRatio=2 # 老年代:新生代 = 2:1(JDK8 前)# 元空间设置-XX:MetaspaceSize=128m # 元空间初始大小-XX:MaxMetaspaceSize=256m # 元空间最大大小# GC 收集器设置-XX:+UseParallelGC # 新生代使用 Parallel Scavenge-XX:+UseParallelOldGC # 老年代使用 Parallel Old-XX:+UseG1GC # 使用 G1 收集器-XX:MaxGCPauseMillis=200 # G1 最大停顿时间# 日志配置-XX:+PrintGCDetails # 打印 GC 详细日志-XX:+PrintGCTimeStamps # 打印 GC 时间戳-Xloggc:/tmp/gc.log # GC 日志输出路径</code></pre>]]></content>
<summary type="html"><h1 id="JVM-核心知识点八股文(高频面试版)"><a href="#JVM-核心知识点八股文(高频面试版)" class="headerlink" title="JVM 核心知识点八股文(高频面试版)"></a>JVM 核心知识点八股文(高频面试版)</h1><p>本文</summary>
<category term="后端面试" scheme="https://icarus-blog.top/categories/%E5%90%8E%E7%AB%AF%E9%9D%A2%E8%AF%95/"/>
<category term="JVM" scheme="https://icarus-blog.top/tags/JVM/"/>
<category term="垃圾回收" scheme="https://icarus-blog.top/tags/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"/>
<category term="内存模型" scheme="https://icarus-blog.top/tags/%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/"/>
<category term="八股文" scheme="https://icarus-blog.top/tags/%E5%85%AB%E8%82%A1%E6%96%87/"/>
</entry>
<entry>
<title>LeetCode1015.可被K整除的最小整数</title>
<link href="https://icarus-blog.top/2025/11/25/LeetCode1015-%E5%8F%AF%E8%A2%ABK%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B4%E6%95%B0/"/>
<id>https://icarus-blog.top/2025/11/25/LeetCode1015-%E5%8F%AF%E8%A2%ABK%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B4%E6%95%B0/</id>
<published>2025-11-25T12:30:00.000Z</published>
<updated>2025-12-11T06:48:10.627Z</updated>
<content type="html"><![CDATA[<h1 id="1015-可被K整除的最小整数"><a href="#1015-可被K整除的最小整数" class="headerlink" title="1015.可被K整除的最小整数"></a><a href="https://leetcode.cn/problems/smallest-integer-divisible-by-k/description/">1015.可被K整除的最小整数</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给定正整数 <code>k</code> ,你需要找出可以被 <code>k</code> 整除的、仅包含数字 <code>1</code> 的最 小 正整数 <code>n</code> 的长度。<br></p><p>返回 <code>n</code> 的长度。如果不存在这样的 <code>n</code> ,就返回<code>-1</code>。<br></p><p>注意: <code>n</code> 可能不符合 <code>64</code> 位带符号整数。<br></p><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> k = 1 <br><br><strong>输出:</strong> 1 <br><br><strong>解释:</strong> 最小的答案是 n = 1,其长度为 1。<br></p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> k = 2<br><br><strong>输出:</strong> -1<br><br><strong>解释:</strong> 不存在可被 2 整除的正整数 n 。<br></p></blockquote><h2 id="示例-3"><a href="#示例-3" class="headerlink" title="示例 3:"></a>示例 3:</h2><blockquote><p><strong>输入:</strong> k = 3<br><br><strong>输出:</strong> 3<br><br><strong>解释:</strong> 最小的答案是 n = 111,其长度为 3。<br></p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= k <= 10^5</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>循环遍历乘<code>10</code>取余<code>k</code>,为了避免溢出对<code>k</code>取余,对<code>k</code>取余不会影响结果</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">smallestRepunitDivByK</span><span class="params">(<span class="type">int</span> k)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i < <span class="number">100000</span>; i++) {</span><br><span class="line"> res = (res * <span class="number">10</span> + <span class="number">1</span>) % k;</span><br><span class="line"> <span class="keyword">if</span> (res == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> i;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1015-可被K整除的最小整数"><a href="#1015-可被K整除的最小整数" class="headerlink" title="1015.可被K整除的最小整数"></a><a href="https://leetcode.cn/problems/sma</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="哈希表" scheme="https://icarus-blog.top/tags/%E5%93%88%E5%B8%8C%E8%A1%A8/"/>
<category term="数学" scheme="https://icarus-blog.top/tags/%E6%95%B0%E5%AD%A6/"/>
</entry>
<entry>
<title>LeetCode1018.可被5整除的二进制</title>
<link href="https://icarus-blog.top/2025/11/24/LeetCode1018-%E5%8F%AF%E8%A2%AB5%E6%95%B4%E9%99%A4%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6/"/>
<id>https://icarus-blog.top/2025/11/24/LeetCode1018-%E5%8F%AF%E8%A2%AB5%E6%95%B4%E9%99%A4%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6/</id>
<published>2025-11-24T14:56:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="1018-可被5整除的二进制"><a href="#1018-可被5整除的二进制" class="headerlink" title="1018.可被5整除的二进制"></a><a href="https://leetcode.cn/problems/binary-prefix-divisible-by-5/description/">1018.可被5整除的二进制</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给定一个二进制数组 <code>nums</code> ( 索引从<code>0</code>开始 )。<br></p><p>我们将<code>xi</code> 定义为其二进制表示形式为子数组 <code>nums[0..i]</code> (从最高有效位到最低有效位)。<br></p><p>例如,如果 <code>nums =[1,0,1]</code> ,那么 <code>x0 = 1</code>, <code>x1 = 2</code>, 和 <code>x2 = 5</code>。<br><br>返回布尔值列表 <code>answer</code>,只有当 <code>xi</code> 可以被 <code>5</code> 整除时,答案 <code>answer[i]</code> 为 <code>true</code>,否则为 <code>false</code>。<br></p><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> nums = [0,1,1] <br><br><strong>输出:</strong> [true,false,false] <br><br><strong>解释:</strong> 输入数字为 0, 01, 011;也就是十进制中的 0, 1, 3 。只有第一个数可以被 5 整除,因此 answer[0] 为 true 。<br></p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> nums = [1,1,1]<br><br><strong>输出:</strong> [false,false,false]<br></p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= nums.length <= 105</code></li><li><code>nums[i]</code> 仅为 <code>0</code> 或 <code>1</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>循环遍历左移一位取余<code>5</code>,为了避免溢出对5取余</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> List<Boolean> <span class="title function_">prefixesDivBy5</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length, cnt = <span class="number">0</span>;</span><br><span class="line"> List<Boolean> res = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>(n);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> cnt = ((cnt << <span class="number">1</span>) + nums[i]) % <span class="number">5</span>;</span><br><span class="line"> res.add(cnt == <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1018-可被5整除的二进制"><a href="#1018-可被5整除的二进制" class="headerlink" title="1018.可被5整除的二进制"></a><a href="https://leetcode.cn/problems/binary</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="位运算" scheme="https://icarus-blog.top/tags/%E4%BD%8D%E8%BF%90%E7%AE%97/"/>
<category term="数组" scheme="https://icarus-blog.top/tags/%E6%95%B0%E7%BB%84/"/>
</entry>
<entry>
<title>LeetCode1262.可被三整除的最大和</title>
<link href="https://icarus-blog.top/2025/11/23/LeetCode1262-%E5%8F%AF%E8%A2%AB%E4%B8%89%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C/"/>
<id>https://icarus-blog.top/2025/11/23/LeetCode1262-%E5%8F%AF%E8%A2%AB%E4%B8%89%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C/</id>
<published>2025-11-23T14:29:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="1262-可被三整除的最大和"><a href="#1262-可被三整除的最大和" class="headerlink" title="1262.可被三整除的最大和"></a><a href="https://leetcode.cn/problems/greatest-sum-divisible-by-three/description/">1262.可被三整除的最大和</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给你一个整数数组 <code>nums</code>,请你找出并返回能被三整除的元素 最大和。<br></p><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> nums = [3,6,5,1,8] <br><br><strong>输出:</strong> 18 <br><br><strong>解释:</strong> 选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。<br></p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> nums = [4]<br><br><strong>输出:</strong> 0<br><br><strong>解释:</strong> 4 不能被 3 整除,所以无法选出数字,返回 0。</p></blockquote><h2 id="示例-3"><a href="#示例-3" class="headerlink" title="示例 3:"></a>示例 3:</h2><blockquote><p><strong>输入:</strong> nums = [1,2,3,4,4]<br><br><strong>输出:</strong> 12<br><br><strong>解释:</strong> 选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。<br></p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= nums.length <= 4 * 104</code></li><li><code>1 <= nums[i] <= 10^4</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>虽然A过了,但是我写的贪心太丑陋了,放下灵神的代码吧</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxSumDivThree</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">var</span> <span class="variable">a1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span><Integer>();</span><br><span class="line"> <span class="type">var</span> <span class="variable">a2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span><Integer>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < nums.length; i++) {</span><br><span class="line"> sum += nums[i];</span><br><span class="line"> <span class="keyword">if</span> (nums[i] % <span class="number">3</span> == <span class="number">1</span>)</span><br><span class="line"> a1.add(nums[i]);</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (nums[i] % <span class="number">3</span> == <span class="number">2</span>)</span><br><span class="line"> a2.add(nums[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (sum % <span class="number">3</span> == <span class="number">0</span>) <span class="keyword">return</span> sum;</span><br><span class="line"> Collections.sort(a1);</span><br><span class="line"> Collections.sort(a2);</span><br><span class="line"> <span class="keyword">if</span> (sum % <span class="number">3</span> == <span class="number">2</span>) {</span><br><span class="line"> <span class="type">var</span> <span class="variable">tmp</span> <span class="operator">=</span> a1;</span><br><span class="line"> a1 = a2;</span><br><span class="line"> a2 = tmp;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (!a1.isEmpty()) {</span><br><span class="line"> res = sum - a1.get(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (a2.size() >= <span class="number">2</span>) {</span><br><span class="line"> res = Math.max(res, sum - a2.get(<span class="number">0</span>) - a2.get(<span class="number">1</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1262-可被三整除的最大和"><a href="#1262-可被三整除的最大和" class="headerlink" title="1262.可被三整除的最大和"></a><a href="https://leetcode.cn/problems/greate</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="数组" scheme="https://icarus-blog.top/tags/%E6%95%B0%E7%BB%84/"/>
<category term="贪心" scheme="https://icarus-blog.top/tags/%E8%B4%AA%E5%BF%83/"/>
</entry>
<entry>
<title>LeetCode65.有效数字</title>
<link href="https://icarus-blog.top/2025/11/22/LeetCode65-%E6%9C%89%E6%95%88%E6%95%B0%E5%AD%97/"/>
<id>https://icarus-blog.top/2025/11/22/LeetCode65-%E6%9C%89%E6%95%88%E6%95%B0%E5%AD%97/</id>
<published>2025-11-22T17:30:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="65-有效数字"><a href="#65-有效数字" class="headerlink" title="65.有效数字"></a><a href="https://leetcode.cn/problems/valid-number/description/">65.有效数字</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给定一个字符串 <code>s</code> ,返回 <code>s</code> 是否是一个 有效数字。<br></p><p>例如,下面的都是有效数字:<code>2</code>, <code>0089</code>, <code>-0.1</code>, <code>+3.14</code>, <code>4.</code>, <code>-.9</code>, <code>2e10</code>, <code>-90E3</code>, <code>3e+7</code>, <code>+6e-1</code>, <code>53.5e93</code>, <code>-123.456e789</code>,而接下来的不是:<code>abc</code>, <code>1a</code>, <code>1e</code>, <code>e3</code>, <code>99e2.5</code>, <code>--6</code>, <code>-+3</code>, <code>95a54e53</code>。<br></p><p>一般的,一个 有效数字 可以用以下的规则之一定义:<br></p><ol><li>一个 整数 后面跟着一个 可选指数。<br></li><li>一个 十进制数 后面跟着一个 可选指数。<br></li></ol><p>一个 整数 定义为一个 可选符号 <code>-</code> 或 <code>+</code> 后面跟着 数字。<br><br>一个 十进制数 定义为一个 可选符号 <code>-</code> 或 <code>+</code> 后面跟着下述规则:<br></p><ol><li>数字 后跟着一个 小数点 <code>.</code>。<br></li><li>数字 后跟着一个 小数点 <code>.</code> 再跟着 数位。<br></li><li>一个 小数点 <code>. </code>后跟着 数位。<br><br>指数 定义为指数符号 <code>e</code> 或 <code>E</code>,后面跟着一个 整数。<br></li></ol><p>数字 定义为一个或多个数位。<br></p><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> s = “0” <br><br><strong>输出:</strong> true <br></p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> s = “e”<br><br><strong>输出:</strong> false<br></p></blockquote><h2 id="示例-3"><a href="#示例-3" class="headerlink" title="示例 3:"></a>示例 3:</h2><blockquote><p><strong>输入:</strong> s = “”<br><br><strong>输出:</strong> false<br></p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= s.length <= 20</code></li><li><code>s</code> 仅含英文字母(大写和小写),数字(<code>0-9</code>),加号 <code>+</code> ,减号 <code>-</code> ,或者点 <code>.</code> 。</li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>模式匹配,需要细心点,不然容易WA,写出三种数字判断函数,从e||E做拆分,带入函数得出结果</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="comment">// 判断是否是整数</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isInteger</span><span class="params">(String s, <span class="type">int</span> l, <span class="type">int</span> r)</span> {</span><br><span class="line"> <span class="type">char</span> <span class="variable">ch</span> <span class="operator">=</span> s.charAt(l++);</span><br><span class="line"> <span class="keyword">if</span> (ch != <span class="string">'-'</span> && ch != <span class="string">'+'</span> && (ch < <span class="string">'0'</span> || ch > <span class="string">'9'</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> <span class="variable">numCnt</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (ch >= <span class="string">'0'</span> && ch <= <span class="string">'9'</span>) {</span><br><span class="line"> numCnt++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (l <= r) {</span><br><span class="line"> <span class="type">char</span> <span class="variable">item</span> <span class="operator">=</span> s.charAt(l++);</span><br><span class="line"> <span class="keyword">if</span> (item < <span class="string">'0'</span> || item > <span class="string">'9'</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> numCnt++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> numCnt != <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否为十进制数</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isDecimal</span><span class="params">(String s, <span class="type">int</span> l, <span class="type">int</span> r)</span> {</span><br><span class="line"> <span class="type">char</span> <span class="variable">ch</span> <span class="operator">=</span> s.charAt(l++);</span><br><span class="line"> <span class="keyword">if</span> (ch != <span class="string">'-'</span> && ch != <span class="string">'+'</span> && ch != <span class="string">'.'</span> && (ch < <span class="string">'0'</span> || ch > <span class="string">'9'</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> <span class="variable">pointCnt</span> <span class="operator">=</span> <span class="number">0</span>, numCnt = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (ch == <span class="string">'.'</span>) {</span><br><span class="line"> pointCnt++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (ch >= <span class="string">'0'</span> && ch <= <span class="string">'9'</span>) {</span><br><span class="line"> numCnt++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (l > r) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (l <= r) {</span><br><span class="line"> <span class="type">char</span> <span class="variable">item</span> <span class="operator">=</span> s.charAt(l++);</span><br><span class="line"> <span class="keyword">if</span> (item == <span class="string">'.'</span>) {</span><br><span class="line"> <span class="keyword">if</span> (pointCnt == <span class="number">1</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> pointCnt++;</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (item == <span class="string">'e'</span> || item == <span class="string">'E'</span>) {</span><br><span class="line"> <span class="keyword">return</span> numCnt != <span class="number">0</span> && isIndex(s, l - <span class="number">1</span>, r);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (item < <span class="string">'0'</span> || item > <span class="string">'9'</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> numCnt++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> numCnt != <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否为指数</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isIndex</span><span class="params">(String s, <span class="type">int</span> l, <span class="type">int</span> r)</span> {</span><br><span class="line"> <span class="type">char</span> <span class="variable">ch</span> <span class="operator">=</span> s.charAt(l++);</span><br><span class="line"> <span class="keyword">if</span> (ch != <span class="string">'e'</span> && ch != <span class="string">'E'</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (l > r) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> isInteger(s, l, r);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isNumber</span><span class="params">(String s)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> s.length() - <span class="number">1</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">e</span> <span class="operator">=</span> Math.max(s.indexOf(<span class="string">'e'</span>), s.indexOf(<span class="string">'E'</span>));</span><br><span class="line"> <span class="keyword">if</span> (e == -<span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> isInteger(s, <span class="number">0</span>, r) || isDecimal(s, <span class="number">0</span>, r);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">index</span> <span class="operator">=</span> isIndex(s, e, r);</span><br><span class="line"> <span class="keyword">return</span> (isInteger(s, <span class="number">0</span>, e - <span class="number">1</span>) || isDecimal(s, <span class="number">0</span>, e - <span class="number">1</span>)) && index;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="65-有效数字"><a href="#65-有效数字" class="headerlink" title="65.有效数字"></a><a href="https://leetcode.cn/problems/valid-number/description/"></summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="字符串" scheme="https://icarus-blog.top/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
</entry>
<entry>
<title>LeetCode32.最长有效括号</title>
<link href="https://icarus-blog.top/2025/11/22/LeetCode32-%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7/"/>
<id>https://icarus-blog.top/2025/11/22/LeetCode32-%E6%9C%80%E9%95%BF%E6%9C%89%E6%95%88%E6%8B%AC%E5%8F%B7/</id>
<published>2025-11-22T16:29:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="LeetCode32-最长有效括号"><a href="#LeetCode32-最长有效括号" class="headerlink" title="LeetCode32.最长有效括号"></a><a href="https://leetcode.cn/problems/longest-valid-parentheses/description/">LeetCode32.最长有效括号</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给你一个只包含 <code>(</code> 和 <code>)</code> 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。<br></p><p>左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 <code>(()())</code>。<br></p><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> s = “(()” <br><br><strong>输出:</strong> 2 <br><br><strong>解释:</strong> 最长有效括号子串是 “()”<br></p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> s = “)()())”<br><br><strong>输出:</strong> 4<br><br><strong>解释:</strong>最长有效括号子串是 “()()”</p></blockquote><h2 id="示例-3"><a href="#示例-3" class="headerlink" title="示例 3:"></a>示例 3:</h2><blockquote><p><strong>输入:</strong> s = “”<br><br><strong>输出:</strong> 0<br></p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>0 <= s.length <= 3 * 104</code></li><li><code>s[i]</code> 为 <code>(</code> 或 <code>)</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>栈存下标,每次入栈取栈顶元素看当前待入栈元素是否为<code>)</code>,且栈顶元素为<code>(</code>,若满足条件则先弹出元素,然后取<code>ans = Math.max(i-stack(top),ans)</code><br>需要注意下边界条件,即栈为空边界</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">longestValidParentheses</span><span class="params">(String s)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">ans</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> Deque<Integer> stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span><>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < s.length(); i++) {</span><br><span class="line"> <span class="keyword">if</span> (!stack.isEmpty() && s.charAt(i) == <span class="string">')'</span> && s.charAt(stack.peekFirst()) == <span class="string">'('</span>) {</span><br><span class="line"> stack.pop();</span><br><span class="line"> <span class="keyword">if</span> (stack.isEmpty()) {</span><br><span class="line"> ans = i + <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">Integer</span> <span class="variable">index</span> <span class="operator">=</span> stack.peekFirst();</span><br><span class="line"> ans = Math.max(i - index, ans);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> stack.push(i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="LeetCode32-最长有效括号"><a href="#LeetCode32-最长有效括号" class="headerlink" title="LeetCode32.最长有效括号"></a><a href="https://leetcode.cn/proble</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="字符串" scheme="https://icarus-blog.top/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
<category term="栈" scheme="https://icarus-blog.top/tags/%E6%A0%88/"/>
</entry>
<entry>
<title>LeetCode3190.使所有元素都可以被3整除的最少操作数</title>
<link href="https://icarus-blog.top/2025/11/22/LeetCode3190-%E4%BD%BF%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E9%83%BD%E5%8F%AF%E4%BB%A5%E8%A2%AB3%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0/"/>
<id>https://icarus-blog.top/2025/11/22/LeetCode3190-%E4%BD%BF%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E9%83%BD%E5%8F%AF%E4%BB%A5%E8%A2%AB3%E6%95%B4%E9%99%A4%E7%9A%84%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%95%B0/</id>
<published>2025-11-22T15:44:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="3190-使所有元素都可以被3整除的最少操作数"><a href="#3190-使所有元素都可以被3整除的最少操作数" class="headerlink" title="3190.使所有元素都可以被3整除的最少操作数"></a><a href="https://leetcode.cn/problems/find-minimum-operations-to-make-all-elements-divisible-by-three/description/">3190.使所有元素都可以被3整除的最少操作数</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给你一个整数数组 <code>nums</code> 。一次操作中,你可以将 <code>nums</code> 中的 任意 一个元素增加或者减少 <code>1</code> 。</p><p>请你返回将 <code>nums</code> 中所有元素都可以被 <code>3</code> 整除的 最少 操作次数。</p><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> nums = [1,2,3,4] <br><br><strong>输出:</strong> 3 <br><br><strong>解释:</strong> 通过以下 3 个操作,数组中的所有元素都可以被 3 整除:<br></p><ul><li>将 1 减少 1 。</li><li>将 2 增加 1 。</li><li>将 4 减少 1 。</li></ul></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> nums = [3,6,9]<br><br><strong>输出:</strong> 0<br></p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= nums.length <= 50</code></li><li><code>1 <= nums[i] <= 50</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>暴力破解就好,循环代替思考</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">minimumOperations</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="keyword">return</span> Arrays.stream(nums)</span><br><span class="line"> .map(num -> {</span><br><span class="line"> <span class="keyword">return</span> num % <span class="number">3</span> == <span class="number">0</span> ? <span class="number">0</span> : <span class="number">1</span>;</span><br><span class="line"> })</span><br><span class="line"> .sum();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="3190-使所有元素都可以被3整除的最少操作数"><a href="#3190-使所有元素都可以被3整除的最少操作数" class="headerlink" title="3190.使所有元素都可以被3整除的最少操作数"></a><a href="https://</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="数组" scheme="https://icarus-blog.top/tags/%E6%95%B0%E7%BB%84/"/>
</entry>
<entry>
<title>LeetCode1930.长度为3的不同回文子序列</title>
<link href="https://icarus-blog.top/2025/11/21/LeetCode1930-%E9%95%BF%E5%BA%A6%E4%B8%BA3%E7%9A%84%E4%B8%8D%E5%90%8C%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97/"/>
<id>https://icarus-blog.top/2025/11/21/LeetCode1930-%E9%95%BF%E5%BA%A6%E4%B8%BA3%E7%9A%84%E4%B8%8D%E5%90%8C%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97/</id>
<published>2025-11-21T14:56:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="1930-长度为3的不同回文子序列"><a href="#1930-长度为3的不同回文子序列" class="headerlink" title="1930.长度为3的不同回文子序列"></a><a href="https://leetcode.cn/problems/unique-length-3-palindromic-subsequences/description/">1930.长度为3的不同回文子序列</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给你一个字符串 <code>s</code> ,返回 <code>s</code> 中 长度为 <code>3</code> 的不同回文子序列 的个数。<br></p><p>即便存在多种方法来构建相同的子序列,但相同的子序列只计数一次。<br></p><p>回文 是正着读和反着读一样的字符串。<br></p><p>子序列 是由原字符串删除其中部分字符(也可以不删除)且不改变剩余字符之间相对顺序形成的一个新字符串。<br></p><ul><li>例如,<code>"ace"</code> 是 <code>"abcde"</code> 的一个子序列。</li></ul><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> s = “aabca” <br><br><strong>输出:</strong> 3 <br><br><strong>解释:</strong> 长度为 3 的 3 个回文子序列分别是:<br></p><ul><li>“aba” (“aabca” 的子序列)</li><li>“aaa” (“aabca” 的子序列)</li><li>“aca” (“aabca” 的子序列)</li></ul></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> s = “adc”<br><br><strong>输出:</strong> 0<br><br><strong>解释:</strong>“adc” 不存在长度为 3 的回文子序列。</p></blockquote><h2 id="示例-3"><a href="#示例-3" class="headerlink" title="示例 3:"></a>示例 3:</h2><blockquote><p><strong>输入:</strong> s = “bbcbaba”<br><br><strong>输出:</strong> 4<br><br><strong>解释:</strong> 长度为 3 的 4 个回文子序列分别是:</p><ul><li>“bbb” (“bbcbaba” 的子序列)</li><li>“bcb” (“bbcbaba” 的子序列)</li><li>“bab” (“bbcbaba” 的子序列)</li><li>“aba” (“bbcbaba” 的子序列)</li></ul></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>3 <= s.length <= 105</code></li><li><code>s 仅由小写英文字母组成</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>长度为3 ,从<code>a</code>-<code>z</code>枚举左端点,枚举左端点等同于枚举右端点,然后从两个端点中间枚举其他字母累加求和</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">countPalindromicSubsequence</span><span class="params">(String s)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">char</span> <span class="variable">point</span> <span class="operator">=</span> <span class="string">'a'</span>; point <= <span class="string">'z'</span>; point++) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">L</span> <span class="operator">=</span> s.indexOf(point);</span><br><span class="line"> <span class="type">int</span> <span class="variable">R</span> <span class="operator">=</span> s.lastIndexOf(point);</span><br><span class="line"> <span class="keyword">if</span> (L != R) {</span><br><span class="line"> <span class="type">boolean</span>[] set = <span class="keyword">new</span> <span class="title class_">boolean</span>[<span class="number">26</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">M</span> <span class="operator">=</span> L + <span class="number">1</span>; M < R; M++) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> s.charAt(M) - <span class="string">'a'</span>;</span><br><span class="line"> <span class="keyword">if</span> (set[index])<span class="keyword">continue</span>;</span><br><span class="line"> set[index] = <span class="literal">true</span>;</span><br><span class="line"> res++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1930-长度为3的不同回文子序列"><a href="#1930-长度为3的不同回文子序列" class="headerlink" title="1930.长度为3的不同回文子序列"></a><a href="https://leetcode.cn/proble</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="哈希表" scheme="https://icarus-blog.top/tags/%E5%93%88%E5%B8%8C%E8%A1%A8/"/>
<category term="字符串" scheme="https://icarus-blog.top/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
<category term="枚举" scheme="https://icarus-blog.top/tags/%E6%9E%9A%E4%B8%BE/"/>
</entry>
<entry>
<title>LeetCode757.设置交集大小至少为2</title>
<link href="https://icarus-blog.top/2025/11/19/LeetCode757-%E8%AE%BE%E7%BD%AE%E4%BA%A4%E9%9B%86%E5%A4%A7%E5%B0%8F%E8%87%B3%E5%B0%91%E4%B8%BA2/"/>
<id>https://icarus-blog.top/2025/11/19/LeetCode757-%E8%AE%BE%E7%BD%AE%E4%BA%A4%E9%9B%86%E5%A4%A7%E5%B0%8F%E8%87%B3%E5%B0%91%E4%B8%BA2/</id>
<published>2025-11-19T16:56:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="757-设置交集大小至少为2"><a href="#757-设置交集大小至少为2" class="headerlink" title="757.设置交集大小至少为2"></a><a href="https://leetcode.cn/problems/set-intersection-size-at-least-two/description/">757.设置交集大小至少为2</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>给你一个二维整数数组 <code>intervals</code> ,其中 <code>intervals[i] = [starti, endi]</code> 表示从 <code>starti</code> 到 <code>endi</code> 的所有整数,包括 <code>starti</code> 和 <code>endi</code> 。</p><p>包含集合 是一个名为 <code>nums</code> 的数组,并满足 <code>intervals</code> 中的每个区间都 至少 有 两个 整数在 <code>nums</code> 中。</p><ul><li>例如,如果 <code>intervals = [[1,3], [3,7], [8,9]]</code> ,那么 <code>[1,2,4,7,8,9]</code> 和 <code>[2,3,4,8,9]</code> 都符合 包含集合 的定义。<br><br> 返回包含集合可能的最小大小。</li></ul><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> intervals = [[1,3],[3,7],[8,9]] <br><br><strong>输出:</strong> 5 <br><br><strong>解释:</strong> nums = [2, 3, 4, 8, 9].<br>可以证明不存在元素数量为 4 的包含集合。</p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> intervals = [[1,3],[1,4],[2,5],[3,5]]<br><br><strong>输出:</strong> 3<br><br><strong>解释:</strong> nums = [2, 3, 4].<br>可以证明不存在元素数量为 2 的包含集合。 </p></blockquote><h2 id="示例-3"><a href="#示例-3" class="headerlink" title="示例 3:"></a>示例 3:</h2><blockquote><p><strong>输入:</strong> intervals = [[1,2],[2,3],[2,4],[4,5]]<br><br><strong>输出:</strong> 5<br><br><strong>解释:</strong> nums = [1, 2, 3, 4, 5].<br>可以证明不存在元素数量为 4 的包含集合。 </p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= intervals.length <= 3000</code></li><li><code>intervals[i].length == 2</code></li><li><code>0 <= starti < endi <= 108</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>排序贪心,先将数组排序 排序优先级 右区间端点增序,左区间端点降序<br><br>枚举状态,累加求个和返回结果<br><br>最难理解的应该是排序的思路了,按 “右端点升序,右端点相同时左端点降序” 排序,本质是为了让选择的元素尽可能覆盖更多后续区间</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">intersectionSizeTwo</span><span class="params">(<span class="type">int</span>[][] intervals)</span> {</span><br><span class="line"> Arrays.sort(intervals,</span><br><span class="line"> (u, v) -> {</span><br><span class="line"> <span class="keyword">if</span> (u[<span class="number">1</span>] != v[<span class="number">1</span>]) {</span><br><span class="line"> <span class="keyword">return</span> u[<span class="number">1</span>] - v[<span class="number">1</span>]; <span class="comment">// 右端点升序</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> v[<span class="number">0</span>] - u[<span class="number">0</span>]; <span class="comment">// 右端点相同则左端点降序</span></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="type">int</span> <span class="variable">l</span> <span class="operator">=</span> intervals[<span class="number">0</span>][<span class="number">1</span>] - <span class="number">1</span>, r = intervals[<span class="number">0</span>][<span class="number">1</span>], res = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < intervals.length; i++) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">L</span> <span class="operator">=</span> intervals[i][<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> <span class="variable">R</span> <span class="operator">=</span> intervals[i][<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">if</span> (l >= L && r <= R) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (r < L) {</span><br><span class="line"> l = R - <span class="number">1</span>;</span><br><span class="line"> r = R;</span><br><span class="line"> res += <span class="number">2</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> ((r == L) || (r <= R && l < L)) {</span><br><span class="line"> l = r;</span><br><span class="line"> r = R;</span><br><span class="line"> res++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="757-设置交集大小至少为2"><a href="#757-设置交集大小至少为2" class="headerlink" title="757.设置交集大小至少为2"></a><a href="https://leetcode.cn/problems/set-in</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="数组" scheme="https://icarus-blog.top/tags/%E6%95%B0%E7%BB%84/"/>
<category term="贪心" scheme="https://icarus-blog.top/tags/%E8%B4%AA%E5%BF%83/"/>
<category term="排序" scheme="https://icarus-blog.top/tags/%E6%8E%92%E5%BA%8F/"/>
</entry>
<entry>
<title>LeetCode2154. 将找到的值乘以2</title>
<link href="https://icarus-blog.top/2025/11/19/LeetCode2154-%E5%B0%86%E6%89%BE%E5%88%B0%E7%9A%84%E5%80%BC%E4%B9%98%E4%BB%A52/"/>
<id>https://icarus-blog.top/2025/11/19/LeetCode2154-%E5%B0%86%E6%89%BE%E5%88%B0%E7%9A%84%E5%80%BC%E4%B9%98%E4%BB%A52/</id>
<published>2025-11-19T09:15:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="2154-将找到的值乘以2"><a href="#2154-将找到的值乘以2" class="headerlink" title="2154. 将找到的值乘以2"></a><a href="https://leetcode.cn/problems/keep-multiplying-found-values-by-two/description/">2154. 将找到的值乘以2</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><ul><li><p>给你一个整数数组 <code>nums</code> ,另给你一个整数 <code>original</code> ,这是需要在 <code>nums</code> 中搜索的第一个数字。</p></li><li><p>接下来,你需要按下述步骤操作:</p></li></ul><ol><li>如果在 <code>nums</code> 中找到 <code>original</code> ,将 <code>original</code> 乘以 <code>2</code> ,得到新 <code>original</code>(即,令 <code>original = 2 * original</code>)。<br>否则,停止这一过程。</li><li>只要能在数组中找到新 <code>original</code> ,就对新 <code>original</code> 继续 重复 这一过程。</li><li>返回 <code>original</code> 的 最终 值。</li></ol><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> nums = [5,3,6,1,12], original = 3 <br><br><strong>输出:</strong> 24 <br><br><strong>解释:</strong></p><ul><li>3 能在 nums 中找到。3 * 2 = 6 。</li><li>6 能在 nums 中找到。6 * 2 = 12 。</li><li>12 能在 nums 中找到。12 * 2 = 24 。</li><li>24 不能在 nums 中找到。因此,返回 24 。</li></ul></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> nums = [2,7,9], original = 4 <br><br><strong>输出:</strong> 4 <br><br><strong>解释:</strong></p><ul><li>4 不能在 nums 中找到。因此,返回 4 。</li></ul></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= nums.length <= 1000</code></li><li><code>1 <= nums[i], original <= 1000</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>解法一:排序+顺序遍历 <br><br>解法二:HashSet + if: set::contains(original) -> original*=2;</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFinalValue</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> original)</span> {</span><br><span class="line"> Arrays.sort(nums);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i<nums.length;i++){</span><br><span class="line"> <span class="keyword">if</span>(nums[i]==original){</span><br><span class="line"> original*=<span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> original;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFinalValue</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> original)</span> {</span><br><span class="line"> Set<Integer> set = IntStream.of(nums).boxed().collect(Collectors.toSet());</span><br><span class="line"> <span class="keyword">while</span> (set.contains(original)) {</span><br><span class="line"> original *= <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> original;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="2154-将找到的值乘以2"><a href="#2154-将找到的值乘以2" class="headerlink" title="2154. 将找到的值乘以2"></a><a href="https://leetcode.cn/problems/keep-mul</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="哈希表" scheme="https://icarus-blog.top/tags/%E5%93%88%E5%B8%8C%E8%A1%A8/"/>
<category term="数组" scheme="https://icarus-blog.top/tags/%E6%95%B0%E7%BB%84/"/>
<category term="排序" scheme="https://icarus-blog.top/tags/%E6%8E%92%E5%BA%8F/"/>
</entry>
<entry>
<title>LeetCode717. 1 比特与 2 比特字符</title>
<link href="https://icarus-blog.top/2025/11/18/LeetCode717-1-%E6%AF%94%E7%89%B9%E4%B8%8E-2-%E6%AF%94%E7%89%B9%E5%AD%97%E7%AC%A6/"/>
<id>https://icarus-blog.top/2025/11/18/LeetCode717-1-%E6%AF%94%E7%89%B9%E4%B8%8E-2-%E6%AF%94%E7%89%B9%E5%AD%97%E7%AC%A6/</id>
<published>2025-11-18T12:11:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="717-1-比特与-2-比特字符"><a href="#717-1-比特与-2-比特字符" class="headerlink" title="717. 1 比特与 2 比特字符"></a><a href="https://leetcode.cn/problems/1-bit-and-2-bit-characters/description/">717. 1 比特与 2 比特字符</a></h1><h2 id="题目描述:"><a href="#题目描述:" class="headerlink" title="题目描述:"></a>题目描述:</h2><p>有两种特殊字符:</p><ul><li>第一种字符可以用一比特 0 表示<br></li><li>第二种字符可以用两比特(10 或 11)表示<br><br>给你一个以 0 结尾的二进制数组 bits ,如果最后一个字符必须是一个一比特字符,则返回 <code>true</code> 。</li></ul><h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例 1:"></a>示例 1:</h2><blockquote><p><strong>输入:</strong> bits = [1, 0, 0] <br><br><strong>输出:</strong> true <br><br><strong>解释:</strong> 唯一的解码方式是将其解析为一个两比特字符和一个一比特字符。所以最后一个字符是一比特字符。</p></blockquote><h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例 2:"></a>示例 2:</h2><blockquote><p><strong>输入:</strong> bits = [1,1,1,0]<br><br><strong>输出:</strong> false<br><br><strong>解释:</strong> 唯一的解码方式是将其解析为两比特字符和两比特字符。所以最后一个字符不是一比特字符。</p></blockquote><h2 id="提示"><a href="#提示" class="headerlink" title="提示:"></a>提示:</h2><ul><li><code>1 <= bits.length <= 1000</code></li><li><code>bits[i] 为 0 或 1</code></li></ul><h2 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h2><blockquote><p>思维题,倒序判断最后一段1奇偶性,最后一个0被占用了,一旦构成01就是不合法的<br><br>最后一段1为奇数则一定不合法<br><br>最后一段1为偶数则一定合法<br><br>这个可以枚举验证一下</p></blockquote><h2 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isOneBitCharacter</span><span class="params">(<span class="type">int</span>[] bits)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> bits.length;</span><br><span class="line"> <span class="keyword">if</span> (bits[n - <span class="number">1</span>] == <span class="number">1</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> bits.length - <span class="number">2</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">cnt</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (r > -<span class="number">1</span> && bits[r] == <span class="number">1</span>) {</span><br><span class="line"> cnt++;</span><br><span class="line"> r--;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cnt % <span class="number">2</span> == <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="717-1-比特与-2-比特字符"><a href="#717-1-比特与-2-比特字符" class="headerlink" title="717. 1 比特与 2 比特字符"></a><a href="https://leetcode.cn/problems</summary>
<category term="LeetCode" scheme="https://icarus-blog.top/categories/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/categories/LeetCode/%E7%AE%97%E6%B3%95/"/>
<category term="LeetCode" scheme="https://icarus-blog.top/tags/LeetCode/"/>
<category term="算法" scheme="https://icarus-blog.top/tags/algorithm/"/>
<category term="数组" scheme="https://icarus-blog.top/tags/%E6%95%B0%E7%BB%84/"/>
</entry>
<entry>
<title>JUC 并发工具类</title>
<link href="https://icarus-blog.top/2025/10/24/JUC-%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB/"/>
<id>https://icarus-blog.top/2025/10/24/JUC-%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB/</id>
<published>2025-10-24T18:00:00.000Z</published>
<updated>2025-12-11T06:48:10.627Z</updated>
<content type="html"><![CDATA[<h1 id="一、ReentranLock工具类"><a href="#一、ReentranLock工具类" class="headerlink" title="一、ReentranLock工具类"></a>一、ReentranLock工具类</h1><p>Java官方在早期jdk1.5版本就引入了ReentranLock并发工具类,这是一种可重入的独占锁(排它锁),它允许同一个线程多次获取同一个锁而不会被阻塞。相比synchronized,它提供了更灵活的控制能力,如可中断锁、可超时获取锁、条件变量等,用于解决高并发场景下需要灵活控制的业务场景。<br>下面是使用ReentranLock的演示代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * ReentrantLock使用演示:多线程操作共享资源的线程安全控制</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReentrantLockDemo</span> {</span><br><span class="line"> <span class="comment">// 创建可重入锁实例(默认非公平锁,传入true可创建公平锁)</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"> <span class="comment">// 共享资源:计数器</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="comment">// 创建5个线程,每个线程对计数器累加1000次</span></span><br><span class="line"> Thread[] threads = <span class="keyword">new</span> <span class="title class_">Thread</span>[<span class="number">5</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < <span class="number">5</span>; i++) {</span><br><span class="line"> threads[i] = <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j < <span class="number">1000</span>; j++) {</span><br><span class="line"> increment(); <span class="comment">// 调用加锁的累加方法</span></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> threads[i].start();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 等待所有线程执行完毕</span></span><br><span class="line"> <span class="keyword">for</span> (Thread thread : threads) {</span><br><span class="line"> thread.join();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 输出最终结果(预期5*1000=5000)</span></span><br><span class="line"> System.out.println(<span class="string">"最终计数:"</span> + count);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 加锁的累加方法(演示基础锁用法)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> {</span><br><span class="line"> lock.lock();<span class="comment">// 1. 获取锁(必须在try外获取,避免获取锁前异常导致unlock错误)</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> count++;<span class="comment">// 2. 临界区:操作共享资源</span></span><br><span class="line"> reentrantMethod();<span class="comment">// 演示可重入特性:同一线程可再次获取锁(需对应释放)</span></span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 3. 释放锁(必须在finally中,确保锁一定被释放)</span></span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 演示可重入特性:同一线程可再次获取已持有的锁</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">reentrantMethod</span><span class="params">()</span> {</span><br><span class="line"> lock.lock(); <span class="comment">// 再次获取锁(重入)</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 可重入操作(例如日志记录等)</span></span><br><span class="line"> System.out.println(<span class="string">"重入方法执行"</span>);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock(); <span class="comment">// 对应释放重入的锁</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="公平锁和非公平锁"><a href="#公平锁和非公平锁" class="headerlink" title="公平锁和非公平锁"></a>公平锁和非公平锁</h2><p>ReentranLock支持公平锁和非公平锁两种模式,使用方式非常简单:</p><ul><li>公平锁:线程在获取锁的时候,按照等待的先后顺序获取锁</li><li>非公平锁:线程在获取锁的时候,不按照等待的先后顺序获取锁,而是随机获取锁。ReentranLock默认是非公平锁<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ReentranLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentranLock</span>();<span class="comment">//参数默认false,非公平锁</span></span><br><span class="line"><span class="type">ReentranLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentranLock</span>(<span class="literal">true</span>);<span class="comment">//公平锁</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="可重入锁"><a href="#可重入锁" class="headerlink" title="可重入锁"></a>可重入锁</h2><p>可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象需要是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentranLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentranLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentranLock</span>();<span class="comment">//创建 ReentranLock</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recursiveCall</span><span class="params">(<span class="type">int</span> num)</span>{</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="keyword">if</span>(num == <span class="number">0</span>){</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"执行递归,num = "</span> + num);</span><br><span class="line"> recursiveCall(num - <span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">finally</span>{</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>{</span><br><span class="line"> <span class="type">Counter</span> <span class="variable">test</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Counter</span>();</span><br><span class="line"> test.recursiveCall(<span class="number">10</span>);</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="结合Condition实现生产者消费者"><a href="#结合Condition实现生产者消费者" class="headerlink" title="结合Condition实现生产者消费者"></a>结合Condition实现生产者消费者</h2><p>java.util.concurrent类库中提供Condition类来实现线程之间的协调/调用Condition.await()方法使线程等待,其他线程调用Condition.singal()或Condition.singnalAll()方法唤醒等待的线程。<br>注意:调用Condition的await()和signal()方法,都必须在lock保护之内。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Condition;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReentrantLockDemo</span> {</span><br><span class="line"> <span class="comment">// 独立的队列类</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Queue</span> {</span><br><span class="line"> <span class="keyword">private</span> Object[] items;</span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="variable">putIndex</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="variable">takeIndex</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> ReentrantLock lock;</span><br><span class="line"> <span class="keyword">private</span> Condition notEmpty; <span class="comment">// 私有条件变量</span></span><br><span class="line"> <span class="keyword">private</span> Condition notFull;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Queue</span><span class="params">(<span class="type">int</span> capacity)</span> {</span><br><span class="line"> <span class="built_in">this</span>.items = <span class="keyword">new</span> <span class="title class_">Object</span>[capacity];</span><br><span class="line"> lock = <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"> notEmpty = lock.newCondition();</span><br><span class="line"> notFull = lock.newCondition();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(Object value)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (size == items.length) {</span><br><span class="line"> notFull.await(); <span class="comment">// 队列满则等待</span></span><br><span class="line"> }</span><br><span class="line"> items[putIndex] = value;</span><br><span class="line"> <span class="keyword">if</span> (++putIndex == items.length) {</span><br><span class="line"> putIndex = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> size++;</span><br><span class="line"> notEmpty.signal(); <span class="comment">// 唤醒消费者</span></span><br><span class="line"> System.out.println(<span class="string">"producer 生产:"</span> + value); </span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (size == <span class="number">0</span>) {</span><br><span class="line"> notEmpty.await(); <span class="comment">// 队列空则等待</span></span><br><span class="line"> }</span><br><span class="line"> <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> items[takeIndex];</span><br><span class="line"> items[takeIndex] = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (++takeIndex == items.length) {</span><br><span class="line"> takeIndex = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> size--;</span><br><span class="line"> notFull.signal(); <span class="comment">// 唤醒生产者</span></span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 生产者线程</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Producer</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> {</span><br><span class="line"> <span class="keyword">private</span> Queue queue;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Producer</span><span class="params">(Queue queue)</span> {</span><br><span class="line"> <span class="built_in">this</span>.queue = queue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> queue.put(<span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">1000</span>)); <span class="comment">// 生产随机数</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 消费者线程</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Consumer</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> {</span><br><span class="line"> <span class="keyword">private</span> Queue queue;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Consumer</span><span class="params">(Queue queue)</span> {</span><br><span class="line"> <span class="built_in">this</span>.queue = queue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> System.out.println(<span class="string">"consumer 消费:"</span> + queue.take());</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">Queue</span> <span class="variable">queue</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Queue</span>(<span class="number">5</span>); <span class="comment">// 创建队列</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Producer</span>(queue)).start(); <span class="comment">// 启动生产者</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Consumer</span>(queue)).start(); <span class="comment">// 启动消费者</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="二、Semaphore-信号量"><a href="#二、Semaphore-信号量" class="headerlink" title="二、Semaphore 信号量"></a>二、Semaphore 信号量</h1><p>用于控制同时访问某个资源的线程数量</p><p>应用场景:</p><ul><li>限流:可以用于限制对共享资源的并发访问数量,以控制系统的流量</li><li>资源池:可以用于实现资源池,以维护一组有限的共享资源<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SemaphoreDemo</span>{</span><br><span class="line"> <span class="comment">// 声明许可数量</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Semaphore</span> <span class="variable">semaphore</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Semaphore</span>(<span class="number">2</span>);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Executor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Executor</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i<<span class="number">10</span>;i++){</span><br><span class="line"> executor.execute(()->getProductInfo());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getProductInfo</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 申请许可</span></span><br><span class="line"> semaphore.acquire();</span><br><span class="line"> log.info(<span class="string">"请求服务"</span>);</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>);</span><br><span class="line"> } <span class="keyword">catch</span>(InterruptedException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 释放许可</span></span><br><span class="line"> semaphore.release;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"返回商品详情信息"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 限流算法</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getProductInfo2</span><span class="params">()</span>{</span><br><span class="line"> <span class="comment">// 尝试申请许可</span></span><br><span class="line"> <span class="keyword">if</span>(!semaphore.tryAcquire()){</span><br><span class="line"> log.error(<span class="string">"请求被流控了"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"请求被流控了"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> log.info(<span class="string">"请求服务"</span>);</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>);</span><br><span class="line"> } <span class="keyword">catch</span>(InterruptedException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 释放许可</span></span><br><span class="line"> semaphore.release;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"返回商品详情信息"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="三、CountDownLatch-闭锁"><a href="#三、CountDownLatch-闭锁" class="headerlink" title="三、CountDownLatch 闭锁"></a>三、CountDownLatch 闭锁</h2><p>同步协助类,允许一个或多个线程等待,直到其他线程完成操作集<br>核心方法说明</p><ul><li>CountDownLatch(int count):构造方法,初始化计数器计数器值。</li><li>countDown():计数器减 1(线程执行完后调用)。</li><li>await():当前线程阻塞,直到计数器变为 0。</li><li>await(long timeout, TimeUnit unit):带超时时间的等待,超时后即使计数器未到 0 也会继续执行。</li><li>CountDownLatch 的计数器是一次性的,一旦计数器变为 0,再次调用 countDown() 也不会有任何效果。<br>应用场景:</li><li>百米赛跑,学生考试统一交卷,商品详情数据汇总。</li><li>并行任务同步:可以用于协调多个并行任务的完成情况,确保所有任务都完成后再继续执行下一步操作。</li><li>多任务汇总:可以用于统计多个线程的完成情况,以确定所有线程都已完成工作。</li><li>资源初始化:可以用于等待资源的初始化完成,以便在资源初始化后开始使用。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.CountDownLatch;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CountDownLatchDemo2</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="type">int</span> <span class="variable">threadCount</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"> <span class="comment">// 计数器初始化为1(主线程发出1个"开始"信号)</span></span><br><span class="line"> <span class="type">CountDownLatch</span> <span class="variable">startSignal</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < threadCount; i++) {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">runnerId</span> <span class="operator">=</span> i + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> System.out.println(<span class="string">"选手 "</span> + runnerId + <span class="string">" 准备就绪,等待发令..."</span>);</span><br><span class="line"> <span class="comment">// 等待主线程的"开始"信号(计数器变为0)</span></span><br><span class="line"> startSignal.await();</span><br><span class="line"> <span class="comment">// 收到信号后执行</span></span><br><span class="line"> System.out.println(<span class="string">"选手 "</span> + runnerId + <span class="string">" 开始跑步!"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 主线程程准备3秒后发出"开始"信号</span></span><br><span class="line"> Thread.sleep(<span class="number">3000</span>);</span><br><span class="line"> System.out.println(<span class="string">"主线程:各就各位位,预备——跑!"</span>);</span><br><span class="line"> <span class="comment">// 计数器减1(变为0),唤醒所有等待的子线程</span></span><br><span class="line"> startSignal.countDown();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="四、CyclicBarrier-回环栅栏-循环屏障"><a href="#四、CyclicBarrier-回环栅栏-循环屏障" class="headerlink" title="四、CyclicBarrier 回环栅栏/循环屏障"></a>四、CyclicBarrier 回环栅栏/循环屏障</h2><p>实现让一组线程等待至某个状态(屏障点)之后再全部同时执行,而且可以被重复使用<br>核心方法说明:</p><ul><li>CyclicBarrier(int parties):构造方法,指定需要等待的线程数量(parties)。</li><li>CyclicBarrier(int parties, Runnable barrierAction):指定等待线程数 + 屏障动作(所有线程到达后执行)。</li><li>await():当前线程到达屏障并等待,直到所有线程到达或被中断。</li><li>await(long timeout, TimeUnit unit):带超时的等待,超时后抛出 TimeoutException。</li><li>reset():重置屏障计数器,让其可以重新使用(未到达的线程会收到 BrokenBarrierException)。</li><li>getNumberWaiting():获取当前已到达屏障的线程数。</li><li>isBroken():判断屏障是否被打破(如线程中断、超时等)。<br>应用场景:</li><li>批量数据处理,人满发车</li><li>多线程任务:可以用于将复杂的任务分配给多个线程执行,并在所有线程完成工作后触发后续操作</li><li>数据处理:可以用于协调多个线程间的数据处理,在所有线程处理完数据后触发后续操作</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.CyclicBarrier;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CyclicBarrierDemo2</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">runnerCount</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// 初始化屏障,指定2个线程等待,以及屏障动作</span></span><br><span class="line"> <span class="type">CyclicBarrier</span> <span class="variable">barrier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(runnerCount, () -> {</span><br><span class="line"> System.out.println(<span class="string">"===== 本轮轮比赛开始! ====="</span>);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 启动2个运动员线程,循环参与3轮比赛</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < runnerCount; i++) {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">runnerId</span> <span class="operator">=</span> i + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">round</span> <span class="operator">=</span> <span class="number">1</span>; round <= <span class="number">3</span>; round++) { <span class="comment">// 3轮比赛</span></span><br><span class="line"> System.out.println(<span class="string">"第"</span> + round + <span class="string">"轮:运动员"</span> + runnerId + <span class="string">"准备中..."</span>);</span><br><span class="line"> Thread.sleep((<span class="type">long</span>) (Math.random() * <span class="number">1000</span>));</span><br><span class="line"> System.out.println(<span class="string">"第"</span> + round + <span class="string">"轮:运动员"</span> + runnerId + <span class="string">"已就位"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等待其他运动员</span></span><br><span class="line"> barrier.await();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 所有就位后,执行本轮比赛</span></span><br><span class="line"> System.out.println(<span class="string">"第"</span> + round + <span class="string">"轮:运动员"</span> + runnerId + <span class="string">"冲刺!"</span>);</span><br><span class="line"> Thread.sleep(<span class="number">500</span>); <span class="comment">// 模拟比赛过程</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="五、Exchanger-数据交换器"><a href="#五、Exchanger-数据交换器" class="headerlink" title="五、Exchanger 数据交换器"></a>五、Exchanger 数据交换器</h2><p>用于线程间协作的工具类,用于两个线程间交换数据<br>核心方法说明:</p><ul><li>Exchanger():构造方法,创建一个用于交换数据的同步器。</li><li>exchange(V x):当前线程携带数据 x 到达交换点,阻塞等待另一个线程,交换数据后返回对方的数据。</li><li>exchange(V x, long timeout, TimeUnit unit):带超时时间的交换,超时未完成则抛出 TimeoutException。<br>应用场景:</li><li>交易场景(一手交钱,一手交货),对账场景</li><li>数据交换:在多线程环境中,两个线程可以通过Exchanger进行数据交换</li><li>数据采集:在数据采集系统中,可以使用Exchanger在采集线程和处理线程间进行数据交换。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.Exchanger;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeoutException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExchangerDemo2</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> Exchanger<Integer> exchanger = <span class="keyword">new</span> <span class="title class_">Exchanger</span><>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 线程A:立即到达交换点,等待3秒</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">data</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"> System.out.println(<span class="string">"线程A准备交换:"</span> + data + <span class="string">"(最多等3秒)"</span>);</span><br><span class="line"> <span class="comment">// 超时等待:3秒后若线程B未到达,则抛出TimeoutException</span></span><br><span class="line"> <span class="type">Integer</span> <span class="variable">received</span> <span class="operator">=</span> exchanger.exchange(data, <span class="number">3</span>, TimeUnit.SECONDS);</span><br><span class="line"> System.out.println(<span class="string">"线程A收到交换的数据:"</span> + received);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (TimeoutException e) {</span><br><span class="line"> System.out.println(<span class="string">"线程A:等待超时,未完成交换"</span>);</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 线程B:延迟5秒到达(超过线程A的等待时间)</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">5000</span>); <span class="comment">// 延迟5秒</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">data</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line"> System.out.println(<span class="string">"线程B准备交换:"</span> + data);</span><br><span class="line"> <span class="comment">// 此时线程A已超时退出,线程B会一直阻塞吗?</span></span><br><span class="line"> <span class="comment">// 不会:若交换失败(如对方超时),此处会抛出InterruptedException</span></span><br><span class="line"> <span class="type">Integer</span> <span class="variable">received</span> <span class="operator">=</span> exchanger.exchange(data);</span><br><span class="line"> System.out.println(<span class="string">"线程B收到交换的数据:"</span> + received);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> System.out.println(<span class="string">"线程B:交换失败(对方已超时)"</span>);</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="六、Phaser-阶段协同器"><a href="#六、Phaser-阶段协同器" class="headerlink" title="六、Phaser 阶段协同器"></a>六、Phaser 阶段协同器</h1><p>CyclicBarrier 和 CountDownLatch的进化版,管理多个阶段的执行,可以让程序员灵活地控制线程的执行顺序和阶段性的执行<br>核心方法:</p><ul><li>Phaser(int parties)构造方法,指定初始参与线程数(parties)</li><li>register()注册 1 个新线程参与(返回当前阶段号)</li><li>bulkRegister(int parties)批量注册多个线程(返回当前阶段号)</li><li>arriveAndAwaitAdvance()当前线程完成当前阶段,等待其他线程,所有线程到达后进入下一阶段</li><li>arriveAndDeregister()线程完成当前阶段并注销(不再参与后续阶段),返回当前阶段号</li><li>arrive()线程完成当前阶段但不等待(用于无需阻塞的场景)</li><li>getPhase()获取当前阶段号(从 0 开始)</li><li>getRegisteredParties()获取当前注册的线程数</li><li>isTerminated()判断是否已终止(onAdvance() 返回 true 后为 true)</li><li>onAdvance(int phase, int parties)回调方法,阶段切换时执行,返回 true 表示终止,false 继续下一阶段<br>应用场景:</li><li>多线程任务分配</li><li>多级任务流程</li><li>模拟并行计算</li><li>阶段性任务</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.Phaser;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PhaserDemo1</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">threadCount</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"> <span class="comment">// 初始化Phaser:参数为初始参与线程数(可动态增减)</span></span><br><span class="line"> <span class="type">Phaser</span> <span class="variable">phaser</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Phaser</span>(threadCount) {</span><br><span class="line"> <span class="comment">// 重写onAdvance方法,定义阶段切换逻辑(返回true表示终止,false继续)</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">onAdvance</span><span class="params">(<span class="type">int</span> phase, <span class="type">int</span> registeredParties)</span> {</span><br><span class="line"> System.out.println(<span class="string">"\n===== 阶段"</span> + phase + <span class="string">"完成,共"</span> + registeredParties + <span class="string">"个线程参与 ====="</span>);</span><br><span class="line"> <span class="comment">// 当所有阶段完成(这里设置3个阶段)或无参与线程时终止</span></span><br><span class="line"> <span class="keyword">return</span> phase >= <span class="number">2</span> || registeredParties == <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < threadCount; i++) {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">threadId</span> <span class="operator">=</span> i + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="comment">// 模拟3个阶段的任务</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">phase</span> <span class="operator">=</span> <span class="number">0</span>; !phaser.isTerminated(); phase++) {</span><br><span class="line"> System.out.println(<span class="string">"线程"</span> + threadId + <span class="string">" 正在执行阶段"</span> + phase);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 模拟任务执行时间</span></span><br><span class="line"> Thread.sleep((<span class="type">long</span>) (Math.random() * <span class="number">1000</span>));</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 通知当前阶段完成,等待其他线程</span></span><br><span class="line"> phaser.arriveAndAwaitAdvance();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"线程"</span> + threadId + <span class="string">" 所有阶段完成"</span>);</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="一、ReentranLock工具类"><a href="#一、ReentranLock工具类" class="headerlink" title="一、ReentranLock工具类"></a>一、ReentranLock工具类</h1><p>Java官方在早期j</summary>
<category term="并发编程" scheme="https://icarus-blog.top/categories/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
<category term="并发工具类" scheme="https://icarus-blog.top/tags/%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB/"/>
<category term="ReentranLock" scheme="https://icarus-blog.top/tags/ReentranLock/"/>
<category term="Semaphore" scheme="https://icarus-blog.top/tags/Semaphore/"/>
<category term="CountDownLatch" scheme="https://icarus-blog.top/tags/CountDownLatch/"/>
<category term="CyclicBarrier" scheme="https://icarus-blog.top/tags/CyclicBarrier/"/>
<category term="Exchanger" scheme="https://icarus-blog.top/tags/Exchanger/"/>
<category term="Phaser" scheme="https://icarus-blog.top/tags/Phaser/"/>
</entry>
<entry>
<title>ThreadLocal、CAS、Atomic及并发安全</title>
<link href="https://icarus-blog.top/2025/10/23/ThreadLocal%E3%80%81CAS%E3%80%81Atomic%E5%8F%8A%E5%B9%B6%E5%8F%91%E5%AE%89%E5%85%A8/"/>
<id>https://icarus-blog.top/2025/10/23/ThreadLocal%E3%80%81CAS%E3%80%81Atomic%E5%8F%8A%E5%B9%B6%E5%8F%91%E5%AE%89%E5%85%A8/</id>
<published>2025-10-23T14:00:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="一、ThreadLocal"><a href="#一、ThreadLocal" class="headerlink" title="一、ThreadLocal"></a>一、ThreadLocal</h1><h2 id="1-为什么要有ThreadLocal"><a href="#1-为什么要有ThreadLocal" class="headerlink" title="1. 为什么要有ThreadLocal ?"></a>1. 为什么要有ThreadLocal ?</h2><p>ThreadLocal的核心作用是:让变量成为“线程私有”的副本,每个线程操作自己的那份,互不干扰,以此避免多线程共享变量的安全问题;同时,线程内的变量能在任意方法里直接获取,不用一层层传参数,简化代码。</p><h2 id="2-ThreadLocal的使用"><a href="#2-ThreadLocal的使用" class="headerlink" title="2. ThreadLocal的使用"></a>2. ThreadLocal的使用</h2><p>ThreadLocal的使用非常简单,只有下面四个方法:<br>为了性能上的优化,Thread内部设置了一个ThreadLocal.ThreadLocalMap的成员变量。这样每个线程访问自己内部的变量可以无需传参即可跨方法传递使用。</p><ul><li>void set(Object value)</li><li>public Object get()</li><li>public void remove()</li><li>protected Object initialValue()</li></ul><h2 id="3-ThreadLocal的实现"><a href="#3-ThreadLocal的实现" class="headerlink" title="3. ThreadLocal的实现"></a>3. ThreadLocal的实现</h2><h3 id="1-结构设计"><a href="#1-结构设计" class="headerlink" title="1.结构设计"></a>1.结构设计</h3><p>ThreadLocal的内部实现非常有意思,每个Thread内部设置了一个ThreadLocal.ThreadLocalMap的成员变量。ThreadLocalMap内部用来存储值是一个Entry数组,可以看作是一个基于数组结构实现的哈希表,Key值为ThreadLocal,Value值为具体存储的值,下标值的计算是根据计算出的哈希值对数组长度取余</p><h3 id="2-哈希冲突"><a href="#2-哈希冲突" class="headerlink" title="2.哈希冲突"></a>2.哈希冲突</h3><p>HashMap在面对哈希冲突时,使用链表+红黑树的方式来解决哈希冲突。但是ThreadLcoal在面对哈希冲突时,使用开放线性寻址法来解决哈希冲突(其实源码是加一个固定的偏移量),哈希冲突比较频繁的时候,会进行扩容来减少哈希冲突,并且重新根据哈希值计算每个元素的坐标。初始容量为16,扩容的阈值为内部存储元素占据Entry数组的2/3,每次扩容后数组长度都会翻倍</p><h3 id="3-内存泄漏"><a href="#3-内存泄漏" class="headerlink" title="3.内存泄漏"></a>3.内存泄漏</h3><p>内存泄漏是指程序运行过程中,由于程序代码的bug出现了无法被回收的内存区域,由于无法回收这部分内存空间。这种情况被称为内存泄漏。<br>使用ThreadLocal的时候,必须要提防一个内存泄漏的问题,实际上正常使用ThreadLocal(将ThreadLocal作为类中静态常量使用的时候)的时候,是不会产生内存泄漏的问题的。只有将ThreadLocal作为局部变量使用时,会出现无法回收内存空间的内存泄漏现象。这还和JVM的垃圾回收机制有关系。<br>在局部变量的使用场景下,每个value都会被设置到Entry,放到Entry数组中,虽然key是弱引用指向,每次垃圾回收都会回收掉Key值(弱引用特质,每次gc会回收内存空间),由于Java项目中都会使用线程池(天生多线程),线程对象会一直存活,JVM在进行垃圾回收时,由于有强引用指向ThreadLocalMap的value值(key回收掉了,value还在)<br>那value可以每次gc都回收掉吗?这样不就不会产生内存泄漏了么?我们需要站在设计者的角度进行考虑.如果把value值也设计成弱引用,java程序中发生gc是非常高频的,如果业务执行中,发生gc后使这个存储的value值失效,就会出现空指针异常。<br>为什么Key值设置成弱引用呢?<br>ThreadLocal的正确使用方式是调用完成后在可靠的代码区域进行remove回收内存,但肯定会存在有部分程序在编写时,不正常调用remove或者在安全代码区域外调用remove,进而导致remove代码失效,如果Key值设置为强引用,会导致key和value的双重泄漏,所以设置成弱引用本质上是一种防御性编程(xswl,想的太深了)</p><blockquote><p>弱引用使用场景一般是用来做本地的缓存实现(GC后自动回收)</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadLocal</span>{</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span> <span class="keyword">extends</span> <span class="title class_">WeakReference</span><ThreadLocal<?>> {</span><br><span class="line"> <span class="comment">// 存储当前ThreadLocal对应的值(线程私有副本)</span></span><br><span class="line"> Object value;</span><br><span class="line"> <span class="comment">// 构造方法:key是ThreadLocal实例,用弱引用存储;value是具体值</span></span><br><span class="line"> Entry(ThreadLocal<?> k, Object v) {</span><br><span class="line"> <span class="built_in">super</span>(k); <span class="comment">// 调用WeakReference的构造方法,将k包装为弱引用</span></span><br><span class="line"> value = v;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="二、CAS-Atomic原子操作详解"><a href="#二、CAS-Atomic原子操作详解" class="headerlink" title="二、CAS & Atomic原子操作详解"></a>二、CAS & Atomic原子操作详解</h1><h2 id="什么是原子操作?如何实现原子操作?"><a href="#什么是原子操作?如何实现原子操作?" class="headerlink" title="什么是原子操作?如何实现原子操作?"></a>什么是原子操作?如何实现原子操作?</h2><blockquote><p>原子性:实际上是事务的概念,指的是多个操作不可分割的特性,要么全部执行,要么全部不执行,这种特性叫做原子性。</p></blockquote><blockquote><p>CAS机制(Compare and swap):CAS基本思想是比较并替换</p></blockquote><p>内存地址V,预期值A,新值B,具体执行逻辑只有两步:</p><ul><li>1.比较:判断内存地址V中存储值是否为预期值A </li><li>2.替换:<ul><li>若相等,将V中的值更新为B</li><li>若不等,不做任何操作或重试</li></ul></li></ul><p>一般实现原子操作都会使用锁机制,虽然锁机制可以满足简单基本的业务需求,但是有的时候我们需要更有效、灵活的机制。sychronized这种基于阻塞的锁机制,持有锁的时候,其他线程会被全部阻塞,直到持有锁的线程释放锁。为了解决这个问题,Java提供了Atomic的原子工具类。</p><p>在JDK早期版本:Aotmic内部机制一般均为循环CAS重试来实现的,CAS这种无锁无阻塞循环尝试更新的操作,在性能上通常比sychronized锁机制更良好。<br>但是JDK不断对sychronized锁进行优化,现在两者性能上差距基本上没什么区别了。<br>虽然CAS在锁竞争弱的情况下性能上比阻塞锁机制要优秀,但是这是有代价的。CAS有下面三个缺陷</p><h3 id="ABA问题"><a href="#ABA问题" class="headerlink" title="ABA问题"></a>ABA问题</h3><p>ABA问题是CAS操作的弊病之一,在多线程并发的条件下,虽然期望值是A,但可能已经被其他线程修改成A了,如果此时进行更新操作,就会因为这种ABA问题发生并发条件下的数据脏写。ABA问题的解决方案一般是使用版本号进行记录数据版本,来根据版本号+期望值 双期望值来决定要不要更新数据。</p><h3 id="循环开销问题"><a href="#循环开销问题" class="headerlink" title="循环开销问题"></a>循环开销问题</h3><p>虽然CAS是无锁不阻塞的方式进行尝试更新数据,但是高并发环境下大量线程竞争,循环导致的额外开销会非常大,导致CPU的空转开销,造成性能上的浪费,这种情况下更适合使用sychronized阻塞锁来保证数据的原子性、安全性。</p><h3 id="只能保证一个共享变量的原子操作"><a href="#只能保证一个共享变量的原子操作" class="headerlink" title="只能保证一个共享变量的原子操作"></a>只能保证一个共享变量的原子操作</h3><p>CAS的机制决定了只能保证对一个共享变量进行原子操作。但并不是绝对的,我们可以把多个共享变量合并成一个共享变量的对象进行操作(但是这种情况下又会导致锁竞争加剧,性能下降严重)</p><h2 id="AtomicInteger"><a href="#AtomicInteger" class="headerlink" title="AtomicInteger"></a>AtomicInteger</h2><ul><li>int addAndGet():CAS原子加操作,并返回结果</li><li>boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式设置该值</li><li>int getAndIncrement():原子方式加1</li><li>int getAndSet(int newValue):原子方式设置新值</li></ul><h2 id="解决ABA问题的Atomic原子类"><a href="#解决ABA问题的Atomic原子类" class="headerlink" title="解决ABA问题的Atomic原子类"></a>解决ABA问题的Atomic原子类</h2><p>AtomicMarkableReference和AtomicStampedReference<br>为了解决ABA问题,Java提供了两个原子类,这两者的区别是前者只关心有没有被修改过,不关心具体被修改过几次。后者会记录修改次数<br>由于原子类对高并发的写入性能开销比较大,所以jdk1.8之后引入了LongAdder类来解决写热点的问题,其内部用一个base的long类型,和一个Cell[]数组,使用数组来分散写热点事件。</p><h1 id="三、线程安全问题"><a href="#三、线程安全问题" class="headerlink" title="三、线程安全问题"></a>三、线程安全问题</h1><h2 id="线程安全性"><a href="#线程安全性" class="headerlink" title="线程安全性"></a>线程安全性</h2><p>所谓线程安全,即所写代码在并发情况下使用时,总是能表现出正确的行为。反之,未实现线程安全的代码,表现的行为是不可预知的。</p><h2 id="线程封闭"><a href="#线程封闭" class="headerlink" title="线程封闭"></a>线程封闭</h2><p>实现好的并发是一件非常困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。就是把对象封装到一个线程里,只有这一个线程能看到这个对象。这个对象就算不是线程安全的也不会出现任何安全问题。</p><h3 id="栈封闭"><a href="#栈封闭" class="headerlink" title="栈封闭"></a>栈封闭</h3><p>栈封闭是我们编程中遇到的最多的线程封闭。最常见的就是局部变量,由于局部变量不是被多个线程共享,所以不会出现并发问题。所以使用局部变量不使用全局变量,能在一定程度上避免并发带来的安全问题,比如ThreadLocal就是一个实现线程封闭的很好的数据结构。</p><h3 id="无状态的对象"><a href="#无状态的对象" class="headerlink" title="无状态的对象"></a>无状态的对象</h3><p>没有任何成员变量的类,就叫做无状态的类,这种类一定是线程安全的。但其内部对于其他对象进行操作并不一定是线程安全的(类本身线程安全,但是内部行为不一定对操作对象是线程安全的)。</p><h2 id="加锁和CAS"><a href="#加锁和CAS" class="headerlink" title="加锁和CAS"></a>加锁和CAS</h2><p>我们最常用保证线程安全的手段,是使用synchronized关键字,使用显式锁以及各种原子变量,修改数据时使用CAS机制等</p><h2 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h2><p>死锁是指两个或两个以上的进程在执行过程中,互相持有其他线程所需要的锁资源,并且等待其他线程释放锁资源,又不会释放自己已经持有的锁资源,而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时系统就处于死锁状态。<br>学术上死锁发生死锁必须具备四个条件:</p><ul><li>互斥条件:资源具有独占性,同一时间只能被一个进程占用。例如,打印机同一时间只能处理一个打印任务。</li><li>请求与保持条件:进程已持有至少一个资源,同时又向其他进程请求新的资源,且该资源正被其他进程占用。例如,进程 A 持有打印机,又请求进程 B 正在使用的扫描仪。</li><li>不可剥夺条件:进程已获得的资源,在未主动释放前,不能被其他进程强制剥夺。例如,进程 B 正在使用的扫描仪,不能被系统强制收回分配给进程 A。</li><li>循环等待条件:多个进程之间形成闭环的资源等待链,每个进程都在等待下一个进程所持有的资源。例如,进程 A 等待进程 B 的资源,进程 B 等待进程 C 的资源,进程 C 等待进程 A 的资源。</li></ul><h3 id="死锁的预防核心"><a href="#死锁的预防核心" class="headerlink" title="死锁的预防核心"></a>死锁的预防核心</h3><p>预防死锁的本质的就是破坏四个条件中的任意一个,即可从根本上避免死锁发生。常见思路包括:采用资源预先分配策略(破坏请求与保持条件)、允许资源强制回收(破坏不可剥夺条件)、终止死锁中的线程(破坏循环等待)等。一般情况下互斥性是无法避免的。避免死锁还有一些常见算法:有序资源分配法和银行家算法等。</p><h2 id="线程安全的单例模式"><a href="#线程安全的单例模式" class="headerlink" title="线程安全的单例模式"></a>线程安全的单例模式</h2><p>单例模式是一种比较常见的设计模式,最常见的是DCL(double check lock)单例实现,下面是说明和代码实现:</p><ol><li>volatile 关键字:防止instance = new Singleton()这句代码的指令重排序(分配内存→初始化对象→引用指向内存)。如果没有 volatile,可能导致其他线程获取到 “未完全初始化” 的实例(引用已指向内存,但对象还没初始化完)。</li><li>双重检查:<ul><li>第一次检查(同步块外):避免每次调用getInstance()都进入同步块,减少性能损耗(多数情况下实例已初始化,直接返回)。</li><li>第二次检查(同步块内):防止多线程同时通过第一次检查后,在同步块内重复创建实例。</li></ul></li><li>私有构造方法:禁止外部通过new Singleton()创建实例,确保唯一实例由getInstance()控制。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> {</span><br><span class="line"> <span class="comment">// 1. 私有静态实例变量,用volatile修饰(关键!防止指令重排序)</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> Singleton instance;</span><br><span class="line"> <span class="comment">// 2. 私有构造方法,禁止外部通过new创建实例</span></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span> {}</span><br><span class="line"> <span class="comment">// 3. 公共静态方法,提供全局访问点</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// 第一次检查:未初始化时才进入同步块(减少同步开销)</span></span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 同步块:保证多线程下的原子性</span></span><br><span class="line"> <span class="keyword">synchronized</span> (Singleton.class) {</span><br><span class="line"> <span class="comment">// 第二次检查:防止多个线程同时通过第一次检查后重复创建实例</span></span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="literal">null</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<summary type="html"><h1 id="一、ThreadLocal"><a href="#一、ThreadLocal" class="headerlink" title="一、ThreadLocal"></a>一、ThreadLocal</h1><h2 id="1-为什么要有ThreadLocal"><</summary>
<category term="并发编程" scheme="https://icarus-blog.top/categories/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
<category term="Java" scheme="https://icarus-blog.top/tags/Java/"/>
<category term="并发" scheme="https://icarus-blog.top/tags/%E5%B9%B6%E5%8F%91/"/>
<category term="ThreadLocal" scheme="https://icarus-blog.top/tags/ThreadLocal/"/>
<category term="CAS" scheme="https://icarus-blog.top/tags/CAS/"/>
<category term="Atomic" scheme="https://icarus-blog.top/tags/Atomic/"/>
<category term="线程安全" scheme="https://icarus-blog.top/tags/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8/"/>
</entry>
<entry>
<title>多线程并发、等待及通知机制</title>
<link href="https://icarus-blog.top/2025/10/21/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%B9%B6%E5%8F%91%E3%80%81%E7%AD%89%E5%BE%85%E9%80%9A%E7%9F%A5%E6%9C%BA%E5%88%B6/"/>
<id>https://icarus-blog.top/2025/10/21/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%B9%B6%E5%8F%91%E3%80%81%E7%AD%89%E5%BE%85%E9%80%9A%E7%9F%A5%E6%9C%BA%E5%88%B6/</id>
<published>2025-10-21T15:00:00.000Z</published>
<updated>2025-12-11T06:48:10.629Z</updated>
<content type="html"><![CDATA[<h1 id="1-基础概念"><a href="#1-基础概念" class="headerlink" title="1. 基础概念"></a>1. 基础概念</h1><h3 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h3><p>进程 = 程序 + 执行</p><p>进程是系统分配资源的基本单位(内存、CPU时间片)<br>进程是用来实现多进程并发执行的一个实体,实现对CPU的虚拟化,让每个进程感觉都拥有一个CPU,核心技术就是上下文切换和进程调度。<br>进程是正在运行的程序的实例。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。<br>进程是执行中的程序,除了可执行代码外还包含进程的活动信息和数据,比如用来存放函数变量、局部变量、返回值的用户栈,存放进程相关数据的数据段,内核中进程间切换的内核栈,动态分配的堆。</p><p>早期操作系统程序都是单个运行的,CPU利用率低下,为了提高CPU的利用率,加载多个程序到内存并发运行,在单核CPU中这种属于伪并发。其实在同一时间只运行一个程序</p><img width="504" height="336" alt="image" src="https://github.com/user-attachments/assets/28eb9528-c6b1-4ec1-8d43-01861e49b0fe" /><h3 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h3><p>通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。</p><p>线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。</p><p>同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。</p><p>一个进程可以有很多线程,每条线程并行执行不同的任务。</p><p>在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。</p><h3 id="进程间的通信"><a href="#进程间的通信" class="headerlink" title="进程间的通信"></a>进程间的通信</h3><p>同一台计算机的进程通信称为IPC(Inter-process-conmumunication),不同计算机之间的通信被称为RPC(Remote-process-conmumunication),需要通过网络,并遵守共同的协议,比如Dubbo就是一个RPC框架,而Http协议也经常用在RPC上,比如SpringCloud微服务</p><p><strong>进程间有几种通信方式?</strong></p><ul><li>管道:分为匿名管道(pipe)及命名管道(named pipe)<ul><li>匿名管道可用具有亲缘关系的父子进程间的通信</li><li>命名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间进行通信</li></ul></li><li>信号(sign):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。</li><li>消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定规则向消息队列中添加新信息;对消息队列有读权限的进程可以从消息队列中读取消息</li><li>共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依赖某种同步操作,入互斥锁和信号量等。</li><li>信号量(semaphore):主要作为进程间及同一种进程的不同线程之间的同步和互斥手段。</li><li>套接字(socket):这是一种更为一般的进程间通信机制。可用于网络中不同机器之间的进程间通信,应用非常广泛。同一机器中的进程还可以使用Unix domain socket(比如同一机器中的MySQL中的控制台mysql shell 和MuSQL服务程序的连接),这种方式不需要经过网络协议栈,不需要打包拆包、计算校验、维护序号和应答等,比纯粹基于网络的进程间通信肯定效率更高。</li></ul><h3 id="CPU核心数和线程数的关系"><a href="#CPU核心数和线程数的关系" class="headerlink" title="CPU核心数和线程数的关系"></a>CPU核心数和线程数的关系</h3><p>目前,主流的CPU都是多核的,线程是CPU调度的最小单位。同一CPU核心只能运行一个线程,也就是说CPU内核和同时运行的线程数是1:1的关系,也就是说8核CPU同时可以执行8个线程的代码。但Intel引入超线程技术后,产生了逻辑处理器的概念,使得核心数与线程数形成1:2的关系。在我们前面的Windows任务管理器贴图就能看出来,内核数是6,而逻辑处理器数是12个</p><h3 id="上下文切换(Context-switch)"><a href="#上下文切换(Context-switch)" class="headerlink" title="上下文切换(Context switch)"></a>上下文切换(Context switch)</h3><p>既然操作系统要在多个进程(线程)之间进行调度,而每个线程在使用CPU时总是要使用CPU中的资源,比如CPU寄存器和程序计数器。这就意味着,操作系统要保证线程在调度前后的正常执行,所以,操作系统中就有上下文切换的概念,它是指CPU从一个进程或线程到另一个进程或线程的切换。<br>上下文是CPU寄存器和程序计数器在任何时间点的内容。<br>寄存器是CPU内部的一小部分非常快的内存(相对于CPU内部的缓存和CPU外部较慢的RAM主内存),它通过提供对常用值的快速访问来加快计算机程序的执行。<br>程序计数器是一种专门的寄存器,它指示CPU在其指令序列中的位置,并保存着正在执行的指令的地址或下一条要执行的指令的地址,这取决于具体的系统。<br>上下文切换可以更详细地描述为内核(即操作系统的核心)对CPU上的进程(包括线程)执行以下活动:<br>1.暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方<br>2.从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它<br>3.返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。</p><ul><li>从数据角度来看,以程序员的角度来看,是方法调用过程中的各种局部变量与资源</li><li>从线程角度来看,是方法的调用栈中存储的各类信息。<br>引发上下文切换的原因一般包括:线程、进程切换、系统调用等等。上下文切换通常是计算密集型的,因为涉及一系列数据在各种寄存器、缓存中的来回拷贝。就CPU时间而言,一次上下文切换大概需要5000~20000个时钟周期,相对一个简单指令几个乃至十几个左右的执行时钟周期,可以看出这个成本的巨大。</li></ul><h3 id="并行和并发"><a href="#并行和并发" class="headerlink" title="并行和并发"></a>并行和并发</h3><p>举个例子,如果有条高速公路A上面有8条车道,那么最大的并行车辆就是8辆。一个CPU就相当于一个高速公路,核心数或线程数就相当于车道数量。<br>当我们谈论并发时,一定要加个单位时间,也就是说单位时间内并发量是多少?离开了单位时间讨论并发是没有意义的<br>综合来说:<br> 并发Concurrent:指应用能够交替执行不同的任务,比如单CPU核心下执行多线程并非是同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不断去切换这两个任务,来达到“同时执行效果”<br> 并行Parallel:指应用能够同时执行不同的任务数量能力<br>两者的区别:并发是交替执行、并行是同时执行。</p><h1 id="2-Java中的线程"><a href="#2-Java中的线程" class="headerlink" title="2. Java中的线程"></a>2. Java中的线程</h1><h3 id="Java程序天生就是多线程的"><a href="#Java程序天生就是多线程的" class="headerlink" title="Java程序天生就是多线程的"></a>Java程序天生就是多线程的</h3><p>一个Java程序从Main方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但是实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main()的线程。<br>而一个Java程序的运行就算是没有用户自己开启的线程,实际上也有很多JVM自行启动的线程,一般来说有:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OnlyMain</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">ThreadMXBean</span> <span class="variable">threadMXBean</span> <span class="operator">=</span> ManagementFactory.getThreadMXBean();</span><br><span class="line"> ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds());</span><br><span class="line"> <span class="keyword">for</span> (ThreadInfo threadInfo : threadInfos) {</span><br><span class="line"> System.out.printf(<span class="string">"[%s] %s\n"</span>,threadInfo.getThreadId(), threadInfo.getThreadName());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[1] main //main线程,用户程序入口</span><br><span class="line">[2] Reference Handler // 清除Reference的线程</span><br><span class="line">[3] Finalizer //调用对象的finalize方法的线程</span><br><span class="line">[4] Signal Dispatcher // 分发处理发送给JVM信号的线程</span><br><span class="line">[5] Attach Listener // 内存dump,线程dump,类信息统计,获取系统属性等</span><br><span class="line">[13] Common-Cleaner // 执行对象的清理操作,尤其是针对那些需要显式释放的非 Java 堆资源(native 内存、文件句柄、网络连接等)</span><br><span class="line">[14] Monitor Ctrl-Break //监控Ctrl-Break中断信号的</span><br><span class="line">[15] Notification Thread // MBean(管理 Bean)可以通过发送 “通知”(Notification)来告知外部其状态变化(如属性修改、事件触发等)。</span><br></pre></td></tr></table></figure><p>不同JDK版本可能会有所差异,但是Java的特性决定了Java程序天生就是多线程的</p><h3 id="线程的启动和中止"><a href="#线程的启动和中止" class="headerlink" title="线程的启动和中止"></a>线程的启动和中止</h3><h4 id="面试题:线程的启动方式有几种?"><a href="#面试题:线程的启动方式有几种?" class="headerlink" title="面试题:线程的启动方式有几种?"></a>面试题:线程的启动方式有几种?</h4><p>官方说法是两种,一种是创建派生Thread类用以执行,另一种是实现Runnable接口。<br>本质上无论是线程池还是其他方式,都是这两种方式实现的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CallableTest</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">UseCallable</span> <span class="keyword">implements</span> <span class="title class_">Callable</span><String> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello world"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException {</span><br><span class="line"> <span class="comment">// 无返回值的线程启动方式</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(</span><br><span class="line"> ()-{</span><br><span class="line"> System.out.println(<span class="string">"无返回值"</span>);</span><br><span class="line"> }</span><br><span class="line"> ).start();</span><br><span class="line"> <span class="comment">// 有返回值的线程启动方式</span></span><br><span class="line"> <span class="type">UseCallable</span> <span class="variable">useCallable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UseCallable</span>();</span><br><span class="line"> FutureTask<String> futureTask = <span class="keyword">new</span> <span class="title class_">FutureTask</span><>(useCallable);</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(futureTask).start();</span><br><span class="line"> System.out.println(futureTask.get());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="线程中止"><a href="#线程中止" class="headerlink" title="线程中止"></a>线程中止</h4><p>1.线程自然终止<br> 线程的run方法执行完成,或者是抛出了未处理的异常导致线程提前结束</p><p>2.线程手动终止<br> 暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop().但是这些API是过期的,不建议使用。以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占用着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程的不会保证线程的资源正常释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法</p><h4 id="线程中断"><a href="#线程中断" class="headerlink" title="线程中断"></a>线程中断</h4><pre><code>安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作,中断好比其他线程对该线程打了个招呼,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。线程通过检测自身的中断标志位是否被置为True来进行响应。线程会通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。如果一个线程处于阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在检查中断标识时如果发现中断标识为True,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标识位清除,即立即设置为false。不建议自定义一个取消标志位(例:public static boolean flag)来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好。</code></pre><p><strong>处于死锁状态下的线程无法被中断</strong></p><h4 id="run和start区别"><a href="#run和start区别" class="headerlink" title="run和start区别"></a>run和start区别</h4><p>单独调用run方法,执行的是普通方法,并不会创建线程,start方法是创建线程后,在新建线程中执行run方法</p><h3 id="线程的状态-生命周期"><a href="#线程的状态-生命周期" class="headerlink" title="线程的状态/生命周期"></a>线程的状态/生命周期</h3><p>Java中的线程状态分为以下6种:</p><ul><li>1.初始(New):新创建了一个线程对象,还没有调用start()方法。</li><li>2.运行(Runnable):Java线程中将就绪(ready)和运行中(running)两种状态笼统称为”运行”。</li><li>3.阻塞(Blocked):阻塞态,表示线程阻塞于锁</li><li>4.等待(Waiting):进入该状态的线程需要等待其他线程做出一些特定动作(通知或者中断)。</li><li>5.超时等待(Timed_Waiting):该状态不同于Waiting,它可以在指定时间后自行返回。</li><li>6.终止(Terminated):表示该线程已经执行完毕。</li></ul><h3 id="其他线程相关方法"><a href="#其他线程相关方法" class="headerlink" title="其他线程相关方法"></a>其他线程相关方法</h3><p>yield()方法主动让出CPU资源,但让出的时间不可控且资源不会释放</p><h3 id="线程的优先级"><a href="#线程的优先级" class="headerlink" title="线程的优先级"></a>线程的优先级</h3><p>在Java线程中,通过priority这个整形成员变量来控制优先级,优先级的范围为1~10,可以在线程构建的时候通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。</p><h3 id="线程的调度"><a href="#线程的调度" class="headerlink" title="线程的调度"></a>线程的调度</h3><p>线程调度指系统为线程分配CPU使用权的过程,主要调度方式分为两种:</p><ul><li>协同式线程调度</li><li>抢占式线程调度</li></ul><p>使用协程式线程调度的多线程系统,线程执行的时间由线程本身来控制,线程把自己的工作执行完成后,主动通知系统切换到另一个线程上。使用协同式线程调度的最大好处是实现简单,由于线程要把自己的事情做完之后通知系统进行线程切换,所以就没有线程同步的问题,但是坏处也很明显,如果一个线程出了问题,程序就会阻塞。</p><p>使用抢占式线程调度的多线程系统,每个线程执行的时间以及是否切换都由系统决定。这种情况下,线程的执行时间不可控,所以不会有【一个线程导致整个进程阻塞】这种问题的出现。</p><p>Java线程调度使用了抢占式调度的方式,在Java中,Thread.yield()可以让出CPU执行时间,但是对于获取执行时间,线程本身是没有办法的。对于获取CPU执行时间,线程唯一可以使用的手段是设置线程优先级,Java程序设置了10个级别的程序优先级,当两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。</p><h3 id="线程和协程"><a href="#线程和协程" class="headerlink" title="线程和协程"></a>线程和协程</h3><p>线程其实是操作系统层面的实体,Java中的线程是怎么和操作系统层面对应起来的呢?<br>其实任何语言实现线程主要有三种方式:使用内核线程实现(1:1),使用用户线程实现(1:N),使用用户线程+轻量级进程混合实现(N:M)</p><h4 id="内核线程实现"><a href="#内核线程实现" class="headerlink" title="内核线程实现"></a>内核线程实现</h4><p>使用内核线程的实现方式也被称为1:1实现。将内核线程(操作系统内核支持的线程),由于内核线程的支持,每个线程都成为了一个独立的调度单元,即使某个线程在系统调用中被阻塞了,也不会影响整个进程继续工作,相关的调度工作也不需要额外考虑,由操作系统处理。<br>局限性:由于基于内核线程实现,所以各种线程的操作:创建、析构以及同步,都需要涉及到系统调用。系统调用的代价是十分昂贵的,需要在用户态和内核态进行来回切换。其次,每个语言层面的线程都需要有一个内核线程的支持,需要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持的线程数量是有限的。</p><h4 id="用户线程实现"><a href="#用户线程实现" class="headerlink" title="用户线程实现"></a>用户线程实现</h4><p>用户线程属于完全建立在用户空间的线程库上,内核不能感知到,用户线程的建立、同步、销毁和调度完成完全在用户态完成,不需要内核的帮助。程序如果实现得当,不需要进行用户态-内核态的切换,操作非常快速且低消耗,能够支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。</p><p>用户线程的优势在于不需要操作系统内核支援,缺陷同样也在于没有内核线程的支援上,所有的线程操作都需要用户程序自己去处理。线程的创建、销毁、切换和调度都是用户必须考虑的问题,而且由于操作系统只把处理器资源分配给到进程,那诸如“阻塞如何处理”,“多处理器系统如何将线程映射到其他处理器上”这类问题解决起来非常困难,甚至有些在语言层面是不可能实现的。因为使用用户线程实现的程序往往比较复杂,所以一般的应用程序都不倾向使用用户线程。Jav语言曾经使用过用户线程,最终又放弃了。但是近年来以高并发为卖点的Golang等语言又普遍支持了用户线程。</p><h4 id="混合实现"><a href="#混合实现" class="headerlink" title="混合实现"></a>混合实现</h4><p>线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。这种混合实现下,即存在用户线程,又存在内核线程。</p><p>用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然可以保持低开销、快速,并且可以支持大规模的用户线程并发。<br>同样又可以使用内核提供的线程调度功能以及处理器映射,并且用户线程的系统调用要通过内核线程来完成。在这种混合模式中,用户线程与轻量级进程的数量比是不定的。是N:M的关系</p><h4 id="Java线程的实现"><a href="#Java线程的实现" class="headerlink" title="Java线程的实现"></a>Java线程的实现</h4><p>Java线程在早期的CLassic虚拟机上(1.2之前),是用户线程实现的,但是JDK1.3起,主流商用的Java虚拟机的线程模型普遍被替换为基于操作系统原生线程模型来实现(1:1线程模型)。<br>以Hotspot为例,它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以Hotspot自己是不回去干涉线程调度的,全权交给底层的操作系统进行处理。<br>Java的线程调度最终取决于操作系统,映射到操作系统的原生线程,所以操作系统的优先级有时候并不能和Java一一对应,所以Java优先级并不是特别靠谱。</p><h4 id="协程"><a href="#协程" class="headerlink" title="协程"></a>协程</h4><p>随着互联网行业的发展,目前内核线程实现在很多场景已经有点不适宜了。互联网架构在处理一次对外部业务请求的响应,往往需要分布在不同机器上的大量服务共同协作来实现,也就是我们常说的微服务,这种服务细分的架构在减少了单个服务复杂度、增加复用性的同时,也不可避免地增加了服务的数量,缩短了留给每个服务的响应时间。这要求每一个服务都必须在极短的时间内完成计算,这样组合多个服务的总耗时才不会太长;也要求每一服务提供者都要同时处理更庞大的请求,这样才不会出现请求由于某个服务被阻塞而出现等待。<br>Java目前并发编程机制与上述互联网的架构演进趋势产生了一些矛盾,1:1的内核线程模型依然是如今Java虚拟机线程实现的主流选择,但是这种线程模型的天生缺陷切换、调度成本高昂,系统能容纳的线程数量也很有限。以前处理一个请求可以允许花费很长时间在单体应用中,具有这种线程切换的成本也是无伤大雅的,但现在在每个请求本身的执行时间变得很短、数量变得很多的前提下,用户本身的业务线程切换的开销甚至可能会接近用于计算本身的开销,这就会造成严重的资源浪费。<br>另外我们常见的JavaWeb服务器,比如Tomcat的线程池的容量通常在几十个到两百之间,当把数以百万计的请求往线程池里面灌的时候,系统即使能处理过来,其中的切换损耗也是相当可观的。<br>这样的话对于Java语言来说,用户线程的引入成为了解决上述问题一个非常可行的方案。其次,Go语言等支持用户线程的新型编程语言给Java带来了巨大的压力,也使得Java在面对是否引入用户线程这个问题面前避无可避。<br>用户线程又被称为协程,内核线程的切换开销来自于保护和恢复线程的成本,如果改用用户线程,这部分开销依然不能够省略掉,但是,把保护、恢复现场以及调度的工作从操作系统交到程序员手上,则可以通过很多手段来缩减这些开销。<br>由于最初多数用户线程是被设计为协同式调度,所以用户线程有了一个别名-协程,完整地做调用栈的保护、恢复工作,所以今天也被称为“有栈协程”</p><h4 id="纤程-Java中的协程"><a href="#纤程-Java中的协程" class="headerlink" title="纤程-Java中的协程"></a>纤程-Java中的协程</h4><p>在JVM的实现上,以Hotspot为例,协程的实现会有些额外的限制,Java调用栈跟本地调用栈是做在一起的。如果在协程中调用了本地方法,还能否正常切换协程而不影响整个线程?另外,如果协程中遇传统的线程同步措施会怎么样?譬如Kotlin提供的协程实现,一旦遭遇synchronize关键字,那挂起来的仍将是整个线程。<br>所以Java开发组就Java中协程的实现也做了很多努力,OpenJDK在2018年创建了Loom项目,这是Java的官方解决方案,并用了“纤程(Fiber)”这个名字。<br>Loom项目的意图是重新提供对用户线程的支持,但这些新功能不是为了取代当前基于操作系统的线程实现,而是会有两个并发编程模型在Java虚拟机并存,可以在程序中同时使用。新模型有意地保持了与目前线程模型相似的API设计,它们甚至可以拥有一个共同的基类,这样现有的代码就不需要为了使用纤程而进行过多改动,甚至不需要知道背后采用了哪个并发编程模型。<br>Loom团队在2018年公布的他们对于Jetty基于纤程改造后的测试结果,同样在5000QPS压力下,以容量为400的线程池的传统模式和每个请求配以一个纤程的新并发处理模式进行对比,前者的请求响应延迟在10000至20000毫秒之间,而后者的延迟普遍在200毫秒以下,目前Java中比较出名的协程库是Quasar,Quasar的实现原理是字节码注入,在字节码层面对当前被调用函数中的所有局部变量进行保存和恢复。这种不依赖Java虚拟机的线程保护虽然能够工作,但是影响性能。</p><h4 id="Java使用纤程"><a href="#Java使用纤程" class="headerlink" title="Java使用纤程"></a>Java使用纤程</h4><ol><li>引入依赖<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependemcy</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>co.paralleluniverse<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>quasar-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.7.9<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependemcy</span>></span></span><br></pre></td></tr></table></figure></li><li>使用纤程<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadTest</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception{</span><br><span class="line"> <span class="type">CountDownLatch</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">10000</span>);</span><br><span class="line"> <span class="type">StopWatch</span> <span class="variable">stopWatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StopWatch</span>();</span><br><span class="line"> stopWatch.start();</span><br><span class="line"> <span class="comment">// 线程工作池</span></span><br><span class="line"> <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newCachedThreadPool(<span class="number">2000</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 协程工作池</span></span><br><span class="line"> <span class="comment">// ExecutorService executorService = Executors.newFixedThreadPool(2000);</span></span><br><span class="line"></span><br><span class="line"> IntStream.range(<span class="number">0</span>,<span class="number">10000</span>).forEach(i->executorService.submit(()->{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">catch</span>(InterruptedException ex){}</span><br><span class="line"> count.countDown();</span><br><span class="line"> })) ;</span><br><span class="line"> count.await();</span><br><span class="line"> stopWatch.stop();</span><br><span class="line"> System.out.print(stopWatch.prettyPrint());</span><br><span class="line"> executorService.shutdownNow();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><h3 id="守护线程"><a href="#守护线程" class="headerlink" title="守护线程"></a>守护线程</h3><p>Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。但是我们一般用不上,比如垃圾回收线程就是守护线程。<br>Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。<br>这段代码打印的线程除了main线程外,其余线程均为守护线程。当JVM中线程均为守护线程时,JVM虚拟机就会退出。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OnlyMain</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">ThreadMXBean</span> <span class="variable">threadMXBean</span> <span class="operator">=</span> ManagementFactory.getThreadMXBean();</span><br><span class="line"> ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds());</span><br><span class="line"> <span class="keyword">for</span> (ThreadInfo threadInfo : threadInfos) {</span><br><span class="line"> System.out.printf(<span class="string">"[%s] %s\n"</span>,threadInfo.getThreadId(), threadInfo.getThreadName());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="线程间的通信和协调、协作"><a href="#线程间的通信和协调、协作" class="headerlink" title="线程间的通信和协调、协作"></a>线程间的通信和协调、协作</h3><p>很多的时候,孤零零的一个线程工作并没有什么太多的用处,更多的时候,我们是很多的线程一起工作,而且是这些线程间进行通信,或者配合着完成某项工作,这就离不开线程间的通信和协调、协作。</p><h3 id="管道输入输出流"><a href="#管道输入输出流" class="headerlink" title="管道输入输出流"></a>管道输入输出流</h3><p>进程间有好几种通信机制,其中包括了管道,其实Java的线程里也有类似的管道机制,用于线程间的数据传输,而传输的媒介为内存。<br>Java中的管道输入输出流主要包括了如下4种具体实现:<br>PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前面两种面向字节,而后面两种面向字符。</p><h3 id="Join方法"><a href="#Join方法" class="headerlink" title="Join方法"></a>Join方法</h3><h4 id="面试题"><a href="#面试题" class="headerlink" title="面试题"></a>面试题</h4><p>现在有T1、T2、T3三个线程,你怎么保证T2在T1执行完后执行,T3在T2执行完后执行?</p><h4 id="join"><a href="#join" class="headerlink" title="join()"></a>join()</h4><p>把指定线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。比如在线程B种调用了线程A的Join方法,直到线程A执行完毕后,才会继续执行线程B剩下的代码。</p><h3 id="synchronized-内置锁"><a href="#synchronized-内置锁" class="headerlink" title="synchronized 内置锁"></a>synchronized 内置锁</h3><p>线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,包括数据之间的共享,协同处理事情。这将会带来巨大的价值。<br>Java支持多个线程同时访问一个对象或者对象的成员变量,但是多个线程同时访问同一个变量,会导致不可预料的结果。关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,使多个线程访问同一个变量的结果正确,它又被称为内置锁机制。</p><h3 id="对象锁和类锁"><a href="#对象锁和类锁" class="headerlink" title="对象锁和类锁"></a>对象锁和类锁</h3><p>对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。</p><h3 id="volatile-最轻量的通信同步机制"><a href="#volatile-最轻量的通信同步机制" class="headerlink" title="volatile 最轻量的通信同步机制"></a>volatile 最轻量的通信同步机制</h3><p>volatile保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对于其他线程来说是立即可见的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VolatileCase</span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> <span class="type">boolean</span> ready;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不加volatile时,子线程无法感知主线程修改了ready的值,加了volatile后,子线程可以感知主线程修改了ready的值,但是volatile只能保证可见性,并不能保证多线程状态下操作的原子性。</p><h3 id="等待-通知机制"><a href="#等待-通知机制" class="headerlink" title="等待/通知机制"></a>等待/通知机制</h3><p>线程之间相互配合,完成某项工作,比如:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者是消费者,这种模式隔离了“做什么”和“怎么做”,简单的方法是让消费者线程不断地循环检查变量是否符合预期在while循环中设置不满足的条件,如果条件满足则退出循环,从而完成消费者的工作。但是却存在下面的问题:</p><ol><li>难以确保及时性</li><li>难以降低开销。如果降低睡眠的时间,比如休眠1毫秒,这样消费者能更加迅速地发现条件变化,但是却可能消耗更多的处理器资源,造成了无端的浪费。</li></ol><p>等待/通知机制则可以很好的避免,这种机制是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()方法或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify()/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方的交互工作。</p><p>notify():<br>通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。</p><h4 id="等待和通知的标准范式"><a href="#等待和通知的标准范式" class="headerlink" title="等待和通知的标准范式"></a>等待和通知的标准范式</h4><p>等待方遵循如下原则:</p><ol><li>获取对象的锁</li><li>如果条件不满足,则调用对象的wait方法,被通知后仍要检查条件</li><li>条件满足则执行对应的逻辑。</li></ol><p>通知方遵循如下原则:</p><ol><li>获取对象的锁</li><li>如果获取到对象锁,执行业务逻辑操作,然后调用对象的notify方法(通知方法)</li></ol><h3 id="方法和锁"><a href="#方法和锁" class="headerlink" title="方法和锁"></a>方法和锁</h3><p>调用yield()、sleep()、wait()、notify()等方法对锁有何影响?<br>yield()、sleep()被调用后,都不会释放当前线程所持有的锁。<br>调用wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行wait方法后面的代码。<br>调用notify()系列方法后,对锁无影响,线程只有在syn同步代码执行完后才会自然而然的释放锁,所以notify()系列方法一般都是syn同步代码的最后一行。</p><h3 id="wait-和-notify"><a href="#wait-和-notify" class="headerlink" title="wait 和 notify"></a>wait 和 notify</h3><p>为什么wait和notify方法要在同步块中调用?</p><p>原因:<br>主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IIIegalMonitorStateException异常。其实真实原因是:<br>这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现。</p><p>生产者-消费者简单示例<br>下面是一个简单的生产者-消费者例子,展示为什么wait()和notify()必须在同步块中调用:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProducerConsumerExample</span> {</span><br><span class="line"> <span class="keyword">private</span> String message;</span><br><span class="line"> <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">hasMessage</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 生产者方法</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">produce</span><span class="params">(String msg)</span> {</span><br><span class="line"> <span class="comment">// ❌ 错误:不在同步块中调用wait/notify</span></span><br><span class="line"> <span class="comment">// if (hasMessage) {</span></span><br><span class="line"> <span class="comment">// wait(); // 会抛出IllegalMonitorStateException</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ✅ 正确:在同步块中</span></span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="built_in">this</span>) {</span><br><span class="line"> <span class="comment">// 如果还有消息没被消费,就等待</span></span><br><span class="line"> <span class="keyword">while</span> (hasMessage) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> System.out.println(<span class="string">"生产者:还有消息未消费,等待中..."</span>);</span><br><span class="line"> wait(); <span class="comment">// 释放锁,等待消费者消费</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> Thread.currentThread().interrupt();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 生产消息</span></span><br><span class="line"> <span class="built_in">this</span>.message = msg;</span><br><span class="line"> hasMessage = <span class="literal">true</span>;</span><br><span class="line"> System.out.println(<span class="string">"生产者:生产了消息 - "</span> + msg);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 通知消费者</span></span><br><span class="line"> notify(); <span class="comment">// 唤醒等待的消费者线程</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 消费者方法</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">consume</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// ✅ 正确:在同步块中</span></span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="built_in">this</span>) {</span><br><span class="line"> <span class="comment">// 如果没有消息,就等待</span></span><br><span class="line"> <span class="keyword">while</span> (!hasMessage) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> System.out.println(<span class="string">"消费者:没有消息,等待中..."</span>);</span><br><span class="line"> wait(); <span class="comment">// 释放锁,等待生产者生产</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> Thread.currentThread().interrupt();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 消费消息</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">consumedMsg</span> <span class="operator">=</span> <span class="built_in">this</span>.message;</span><br><span class="line"> hasMessage = <span class="literal">false</span>;</span><br><span class="line"> System.out.println(<span class="string">"消费者:消费了消息 - "</span> + consumedMsg);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 通知生产者</span></span><br><span class="line"> notify(); <span class="comment">// 唤醒等待的生产者线程</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> consumedMsg;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 测试代码</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">ProducerConsumerExample</span> <span class="variable">example</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ProducerConsumerExample</span>();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 生产者线程</span></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i <= <span class="number">3</span>; i++) {</span><br><span class="line"> example.produce(<span class="string">"消息-"</span> + i);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>); <span class="comment">// 模拟生产时间</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> Thread.currentThread().interrupt();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 消费者线程</span></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i <= <span class="number">3</span>; i++) {</span><br><span class="line"> example.consume();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">1500</span>); <span class="comment">// 模拟消费时间</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> Thread.currentThread().interrupt();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> producer.start();</span><br><span class="line"> consumer.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果可能输出:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">生产者:生产了消息 - 消息-1</span><br><span class="line">消费者:消费了消息 - 消息-1</span><br><span class="line">生产者:生产了消息 - 消息-2</span><br><span class="line">消费者:消费了消息 - 消息-2</span><br><span class="line">生产者:生产了消息 - 消息-3</span><br><span class="line">消费者:消费了消息 - 消息-3</span><br></pre></td></tr></table></figure><p>如果不使用同步块会发生什么?</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误示例 - 竞态条件</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">wrongProduce</span><span class="params">(String msg)</span> {</span><br><span class="line"> <span class="keyword">if</span> (hasMessage) { <span class="comment">// 步骤1:检查条件</span></span><br><span class="line"> <span class="comment">// 在这里,消费者线程可能消费了消息,hasMessage变为false</span></span><br><span class="line"> <span class="comment">// 但生产者不知道这个变化,仍然会调用wait()</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> wait(); <span class="comment">// 步骤2:等待 - 但可能错过notify()</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">this</span>.message = msg; <span class="comment">// 步骤3:可能重复生产,覆盖未消费的消息</span></span><br><span class="line"> hasMessage = <span class="literal">true</span>;</span><br><span class="line"> notify();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1-基础概念"><a href="#1-基础概念" class="headerlink" title="1. 基础概念"></a>1. 基础概念</h1><h3 id="进程"><a href="#进程" class="headerlink" title="进程"</summary>
<category term="多线程、并发" scheme="https://icarus-blog.top/categories/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E3%80%81%E5%B9%B6%E5%8F%91/"/>
<category term="进程" scheme="https://icarus-blog.top/tags/%E8%BF%9B%E7%A8%8B/"/>
<category term="线程" scheme="https://icarus-blog.top/tags/%E7%BA%BF%E7%A8%8B/"/>
<category term="Java并发" scheme="https://icarus-blog.top/tags/Java%E5%B9%B6%E5%8F%91/"/>
<category term="操作系统" scheme="https://icarus-blog.top/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
</entry>
<entry>
<title>深入理解 Sentinel 限流组件推模式实现</title>
<link href="https://icarus-blog.top/2025/10/15/sentinel%E6%8E%A8%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E6%B5%81%E6%8E%A7%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96/"/>
<id>https://icarus-blog.top/2025/10/15/sentinel%E6%8E%A8%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E6%B5%81%E6%8E%A7%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96/</id>
<published>2025-10-15T23:30:00.000Z</published>
<updated>2025-12-11T06:48:10.629Z</updated>
<content type="html"><![CDATA[<p>在微服务架构中,流量控制是保障系统稳定性的核心手段,Sentinel 作为主流限流组件,其规则同步模式直接影响分布式环境下的可用性。本文将聚焦 Sentinel 推模式的实现细节,对比拉模式的局限性,并补充多种主流实现方案,为线上分布式场景提供实践参考。</p><h1 id="一、Sentinel-规则同步的两种核心模式"><a href="#一、Sentinel-规则同步的两种核心模式" class="headerlink" title="一、Sentinel 规则同步的两种核心模式"></a>一、Sentinel 规则同步的两种核心模式</h1><p>在正式讲解推模式前,需先明确 Sentinel 规则同步的两种基础模式:拉模式(Pull Mode) 与 推模式(Push Mode),二者的核心差异在于规则的主动方与持久化方式。</p><h2 id="1-1-拉模式:本地文件驱动的“被动同步”"><a href="#1-1-拉模式:本地文件驱动的“被动同步”" class="headerlink" title="1.1 拉模式:本地文件驱动的“被动同步”"></a>1.1 拉模式:本地文件驱动的“被动同步”</h2><p>拉模式是 Sentinel 最基础的规则同步方式,核心依赖本地文件存储,实现逻辑如下:</p><ul><li>客户端通过定时任务(默认间隔 3 秒)主动读取本地配置文件(如 JSON 格式);</li><li>若文件内容变更,客户端更新本地规则缓存,生效限流逻辑;</li><li>服务端(或运维人员)需手动向每个客户端节点推送文件,才能实现规则更新。</li></ul><p>局限性:</p><ul><li>无法实现“一策多节点”:规则仅对单个节点生效,若微服务部署 10 个实例,需手动更新 10 次配置文件;</li><li>无持久化保障:本地文件易丢失,且无法追溯规则变更历史;</li><li>实时性差:依赖定时任务间隔,规则更新存在延迟,不适合流量突发场景。</li></ul><h2 id="1-2-推模式:配置中心驱动的“主动同步”"><a href="#1-2-推模式:配置中心驱动的“主动同步”" class="headerlink" title="1.2 推模式:配置中心驱动的“主动同步”"></a>1.2 推模式:配置中心驱动的“主动同步”</h2><p>推模式的核心是基于统一配置中心(如 Nacos、Apollo),由配置中心主动向客户端推送规则变更,实现逻辑如下:</p><ul><li>服务端将限流规则(如流控、熔断规则)通过配置中心的 publish 接口发布;</li><li>客户端通过监听配置中心的规则变更事件,实时获取最新配置并更新本地缓存;</li><li>所有客户端节点共享同一配置源,规则变更“一次发布,全节点生效”。</li></ul><p>核心优势:</p><ul><li>规则全局统一:配置中心作为唯一数据源,解决多节点规则不一致问题;</li><li>持久化与可追溯:配置中心自带版本管理,支持规则回滚、变更记录查询;</li><li>实时性高:基于事件监听机制,规则更新延迟可控制在毫秒级;</li><li>适配分布式场景:天然支持微服务动态扩缩容,新节点启动时自动拉取最新规则。</li></ul><h1 id="二、基于-Nacos-的推模式实现(两种方案对比)"><a href="#二、基于-Nacos-的推模式实现(两种方案对比)" class="headerlink" title="二、基于 Nacos 的推模式实现(两种方案对比)"></a>二、基于 Nacos 的推模式实现(两种方案对比)</h1><p>Nacos 作为阿里开源的配置中心,是 Sentinel 推模式的主流选择,官方提供两种实现思路,需根据业务场景选择。</p><h2 id="2-1-方案一:客户端“拉取-监听”模式(简单易实现)"><a href="#2-1-方案一:客户端“拉取-监听”模式(简单易实现)" class="headerlink" title="2.1 方案一:客户端“拉取+监听”模式(简单易实现)"></a>2.1 方案一:客户端“拉取+监听”模式(简单易实现)</h2><p>这是最基础的 Nacos 推模式方案,无需修改 Sentinel 服务端代码,核心依赖 Nacos 的 ConfigService 接口,实现步骤如下:</p><h3 id="1-服务端发布规则:"><a href="#1-服务端发布规则:" class="headerlink" title="1. 服务端发布规则:"></a>1. 服务端发布规则:</h3><p>服务端将限流规则(如流控规则 FlowRule )封装为 JSON 格式,通过 Nacos 的 publishConfig(dataId, group, content) 接口,将规则发布到指定 dataId 和 group 下(如 sentinel-rules-demo )。</p><h3 id="2-客户端监听规则:"><a href="#2-客户端监听规则:" class="headerlink" title="2. 客户端监听规则:"></a>2. 客户端监听规则:</h3><p>客户端引入 Nacos 依赖,通过 addListener 接口监听目标 dataId 的配置变更:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 客户端核心代码示例</span></span><br><span class="line"><span class="type">ConfigService</span> <span class="variable">configService</span> <span class="operator">=</span> NacosFactory.createConfigService(properties);</span><br><span class="line"><span class="comment">// 监听规则变更</span></span><br><span class="line">configService.addListener(<span class="string">"sentinel-rules-demo"</span>, <span class="string">"DEFAULT_GROUP"</span>, <span class="keyword">new</span> <span class="title class_">Listener</span>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">receiveConfigInfo</span><span class="params">(String configInfo)</span> {</span><br><span class="line"> <span class="comment">// 将 JSON 字符串解析为 FlowRule 列表</span></span><br><span class="line"> List<FlowRule> rules = JSON.parseArray(configInfo, FlowRule.class);</span><br><span class="line"> <span class="comment">// 更新 Sentinel 本地规则</span></span><br><span class="line"> FlowRuleManager.loadRules(rules);</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Executor <span class="title function_">getExecutor</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"><span class="comment">// 初始拉取规则</span></span><br><span class="line"><span class="type">String</span> <span class="variable">initConfig</span> <span class="operator">=</span> configService.getConfig(<span class="string">"sentinel-rules-demo"</span>, <span class="string">"DEFAULT_GROUP"</span>, <span class="number">5000</span>);</span><br><span class="line">FlowRuleManager.loadRules(JSON.parseArray(initConfig, FlowRule.class));</span><br></pre></td></tr></table></figure><p>优势:实现简单,无需侵入 Sentinel 服务端,适合快速落地;<br>不足:客户端需额外处理规则解析、异常重试逻辑,代码冗余度较高。</p><h2 id="2-2-方案二:服务端“直接交互”模式(官方推荐)"><a href="#2-2-方案二:服务端“直接交互”模式(官方推荐)" class="headerlink" title="2.2 方案二:服务端“直接交互”模式(官方推荐)"></a>2.2 方案二:服务端“直接交互”模式(官方推荐)</h2><p>Sentinel 官方推荐的 Nacos 推模式,核心是让 Sentinel 服务端直接与 Nacos 交互,客户端仅负责“被动监听”,实现步骤如下:</p><h3 id="1-改造-Sentinel-服务端:"><a href="#1-改造-Sentinel-服务端:" class="headerlink" title="1. 改造 Sentinel 服务端:"></a>1. 改造 Sentinel 服务端:</h3><ul><li>在 Sentinel 控制台(服务端)中集成 Nacos 客户端依赖;</li><li>扩展控制台的“规则管理”模块,新增“发布到 Nacos”按钮,点击后直接调用 Nacos publishConfig 接口;</li><li>支持规则的增删改查与 Nacos 配置实时同步,无需手动编写发布逻辑。</li></ul><h3 id="2-客户端简化监听:"><a href="#2-客户端简化监听:" class="headerlink" title="2. 客户端简化监听:"></a>2. 客户端简化监听:</h3><p>客户端逻辑与方案一一致,但无需关心规则发布流程,仅需监听 Nacos 变更即可。</p><p>优势:规则管理集中化,运维人员通过 Sentinel 控制台即可操作,降低使用成本;<br>不足:需修改 Sentinel 服务端源码(如扩展 NacosConfigPublisher 类),对定制化能力要求较高。</p><h1 id="三、推模式的其他主流实现方案"><a href="#三、推模式的其他主流实现方案" class="headerlink" title="三、推模式的其他主流实现方案"></a>三、推模式的其他主流实现方案</h1><p>除了 Nacos,结合 Spring 生态、Guava 等工具,也能实现 Sentinel 推模式,适用于不同技术栈场景。</p><h2 id="3-1-基于-Spring-生态的推模式"><a href="#3-1-基于-Spring-生态的推模式" class="headerlink" title="3.1 基于 Spring 生态的推模式"></a>3.1 基于 Spring 生态的推模式</h2><p>利用 Spring 的生命周期钩子和后置处理器,可将 Sentinel 规则与 Spring 配置无缝整合,实现步骤如下:</p><h3 id="1-绑定-Spring-生命周期:"><a href="#1-绑定-Spring-生命周期:" class="headerlink" title="1. 绑定 Spring 生命周期:"></a>1. 绑定 Spring 生命周期:</h3><p>在 SpringApplicationRunListener 或 CommandLineRunner 中,初始化 Sentinel 规则数据源,确保 Spring 启动时自动拉取初始规则。</p><h3 id="2-使用后置处理器增强:"><a href="#2-使用后置处理器增强:" class="headerlink" title="2. 使用后置处理器增强:"></a>2. 使用后置处理器增强:</h3><p>自定义 BeanPostProcessor ,对 Sentinel 的 RuleDataSource 实例进行增强:</p><ul><li>为不同规则(流控、熔断、系统规则)创建独立的 RuleDataSource Bean;</li><li>在后置处理器中注入配置中心客户端,实现规则的持久化与变更监听;</li><li>示例代码:<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SentinelRulePostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ConfigService nacosConfigService;</span><br><span class="line"> <span class="comment">// 构造注入 Nacos ConfigService</span></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">SentinelRulePostProcessor</span><span class="params">(ConfigService nacosConfigService)</span> {</span><br><span class="line"> <span class="built_in">this</span>.nacosConfigService = nacosConfigService;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException {</span><br><span class="line"> <span class="comment">// 对 FlowRuleDataSource 进行增强</span></span><br><span class="line"> <span class="keyword">if</span> (bean <span class="keyword">instanceof</span> FlowRuleDataSource) {</span><br><span class="line"> <span class="type">FlowRuleDataSource</span> <span class="variable">dataSource</span> <span class="operator">=</span> (FlowRuleDataSource) bean;</span><br><span class="line"> <span class="comment">// 绑定 Nacos 监听</span></span><br><span class="line"> nacosConfigService.addListener(<span class="string">"sentinel-flow-rules"</span>, <span class="string">"DEFAULT_GROUP"</span>, </span><br><span class="line"> (configInfo) -> dataSource.loadConfig(configInfo));</span><br><span class="line"> <span class="keyword">return</span> dataSource;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> bean;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>适用场景:Spring Boot/Spring Cloud 技术栈,需与 Spring 配置体系深度整合的场景。</p><h2 id="3-2-基于-Guava-Sentinel-自带能力的推模式"><a href="#3-2-基于-Guava-Sentinel-自带能力的推模式" class="headerlink" title="3.2 基于 Guava/Sentinel 自带能力的推模式"></a>3.2 基于 Guava/Sentinel 自带能力的推模式</h2><p>若无需依赖外部配置中心,可利用 Guava 的缓存监听或 Sentinel 自带的规则持久化接口,实现轻量级推模式:</p><h3 id="1-基于-Guava-的实现:"><a href="#1-基于-Guava-的实现:" class="headerlink" title="1. 基于 Guava 的实现:"></a>1. 基于 Guava 的实现:</h3><ul><li>使用 Guava 的 LoadingCache 存储规则,设置过期时间(如 10 秒);</li><li>服务端更新规则时,直接修改 Guava 缓存;</li><li>客户端通过 CacheLoader 定时从服务端拉取缓存,实现规则同步。</li></ul><h3 id="2-基于-Sentinel-自带能力的实现:"><a href="#2-基于-Sentinel-自带能力的实现:" class="headerlink" title="2. 基于 Sentinel 自带能力的实现:"></a>2. 基于 Sentinel 自带能力的实现:</h3><p>Sentinel 提供 RuleRepository 接口,可自定义实现类:</p><ul><li>实现 saveAll 、 findAll 等方法,将规则持久化到数据库(如 MySQL)或 Redis;</li><li>服务端通过 RuleRepository 读写规则,客户端定时调用 findAll 拉取最新规则;</li><li>配合 Redis 的 Pub/Sub 机制,可实现规则变更的实时推送。</li></ul><p>适用场景:轻量级微服务,无需引入配置中心,追求低依赖的场景。</p><h1 id="四、推模式-vs-拉模式:如何选择?"><a href="#四、推模式-vs-拉模式:如何选择?" class="headerlink" title="四、推模式 vs 拉模式:如何选择?"></a>四、推模式 vs 拉模式:如何选择?</h1><p>维度 拉模式(本地文件) 推模式(配置中心)<br>适用场景 单机测试、小型服务 分布式微服务、线上环境<br>规则同步范围 单个节点 全节点统一<br>实时性 低(定时任务间隔) 高(事件监听)<br>持久化与可追溯 无 有(配置中心版本管理)<br>实现复杂度 低 中(需集成配置中心) </p><p>结论:线上分布式环境优先选择推模式(推荐 Nacos/Apollo 方案);单机测试或小型服务可使用拉模式快速验证。</p><h1 id="五、实践中的注意事项"><a href="#五、实践中的注意事项" class="headerlink" title="五、实践中的注意事项"></a>五、实践中的注意事项</h1><h2 id="1-规则格式一致性:"><a href="#1-规则格式一致性:" class="headerlink" title="1. 规则格式一致性:"></a>1. 规则格式一致性:</h2><p>无论哪种推模式,规则的 JSON 格式需严格匹配 Sentinel 实体类(如 FlowRule 的 resource 、 grade 、 count 字段不可缺失),避免解析失败。</p><h2 id="2-异常重试机制:"><a href="#2-异常重试机制:" class="headerlink" title="2. 异常重试机制:"></a>2. 异常重试机制:</h2><p>客户端监听配置中心时,需添加重试逻辑(如使用 Retryer ),防止网络抖动导致规则同步失败。</p><h2 id="3-规则版本管理:"><a href="#3-规则版本管理:" class="headerlink" title="3. 规则版本管理:"></a>3. 规则版本管理:</h2><p>配置中心需开启版本管理,避免误操作导致规则丢失,支持快速回滚到历史版本。</p><h2 id="4-客户端兜底规则:"><a href="#4-客户端兜底规则:" class="headerlink" title="4. 客户端兜底规则:"></a>4. 客户端兜底规则:</h2><p>客户端初始化时,需加载默认兜底规则(如“单机QPS不超过1000”),防止配置中心不可用时无规则可用。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>Sentinel 推模式通过统一配置中心解决了拉模式的分布式痛点,是线上环境的最优选择。本文介绍的 Nacos 两种方案、Spring 整合方案、Guava 轻量方案,覆盖了不同技术栈的需求。在实际落地时,需结合业务场景选择合适的方案,并做好异常处理与版本管理,才能充分发挥 Sentinel 的流量控制能力,保障微服务稳定运行。</p>]]></content>
<summary type="html"><p>在微服务架构中,流量控制是保障系统稳定性的核心手段,Sentinel 作为主流限流组件,其规则同步模式直接影响分布式环境下的可用性。本文将聚焦 Sentinel 推模式的实现细节,对比拉模式的局限性,并补充多种主流实现方案,为线上分布式场景提供实践参考。</p>
<h1 i</summary>
<category term="微服务" scheme="https://icarus-blog.top/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="中间件" scheme="https://icarus-blog.top/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="Sentinel" scheme="https://icarus-blog.top/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E4%B8%AD%E9%97%B4%E4%BB%B6/Sentinel/"/>
<category term="Sentinel" scheme="https://icarus-blog.top/tags/Sentinel/"/>
<category term="Nacos" scheme="https://icarus-blog.top/tags/Nacos/"/>
<category term="限流组件" scheme="https://icarus-blog.top/tags/%E9%99%90%E6%B5%81%E7%BB%84%E4%BB%B6/"/>
<category term="推模式" scheme="https://icarus-blog.top/tags/%E6%8E%A8%E6%A8%A1%E5%BC%8F/"/>
<category term="Spring生态" scheme="https://icarus-blog.top/tags/Spring%E7%94%9F%E6%80%81/"/>
<category term="分布式流量控制" scheme="https://icarus-blog.top/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6/"/>
</entry>
<entry>
<title>Tomcat的IO模型与性能调优</title>
<link href="https://icarus-blog.top/2025/10/13/Tomcat%E7%9A%84IO%E6%A8%A1%E5%9E%8B/"/>
<id>https://icarus-blog.top/2025/10/13/Tomcat%E7%9A%84IO%E6%A8%A1%E5%9E%8B/</id>
<published>2025-10-13T07:30:00.000Z</published>
<updated>2025-12-11T06:48:10.629Z</updated>
<content type="html"><![CDATA[<h1 id="1-Tomcat的I-O模型"><a href="#1-Tomcat的I-O模型" class="headerlink" title="1. Tomcat的I/O模型"></a>1. Tomcat的I/O模型</h1><h2 id="1-1-Linux-I-O模型"><a href="#1-1-Linux-I-O模型" class="headerlink" title="1.1 Linux I/O模型"></a>1.1 Linux I/O模型</h2><h3 id="I-O要解决什么问题?"><a href="#I-O要解决什么问题?" class="headerlink" title="I/O要解决什么问题?"></a>I/O要解决什么问题?</h3><p>I/O本质上是在解决在计算机内存与外部设备之间拷贝数据的过程</p><p>程序通过CPU向外部设备发出读指令,数据从外部设备拷贝至内存需要一段时间,这段时间CPU就没事情做了,程序此时有两种选择:</p><ol><li><p>让出CPU资源,CPU执行其他任务</p></li><li><p>继续使用CPU轮询数据是否拷贝完成</p></li></ol><p>采取的具体策略就是不同I/O模型要解决的问题</p><p>以网络数据读取为例分析,会涉及两个对象,一个是调用I/O操作的用户线程,另一个是操作系统内核。一个进程的地址空间分为用户空间和内核空间,基于安全上的考虑,用户程序只能访问用户空间,内核程序可以访问整个进程空间,只有内核可以直接访问各种硬件资源,比如磁盘和网卡。</p><img width="1101" height="613" alt="image" src="https://github.com/user-attachments/assets/28b24729-5077-45e1-98c4-7145582e439f" /><p>当用户线程发起I/O调用后,网络数据读取操作会经历两个步骤:</p><ul><li><p>数据准备阶段:用户线程等待内核将数据从网卡拷贝到内核空间</p></li><li><p>数据拷贝阶段:内核将数据从内核空间拷贝到用户空间(用户进程的缓冲区)</p></li></ul><img width="1080" height="574" alt="image" src="https://github.com/user-attachments/assets/4b6fcf47-be70-46ef-bdef-9a9d32236d3c" /><h3 id="Linux的I-O模型分类"><a href="#Linux的I-O模型分类" class="headerlink" title="Linux的I/O模型分类"></a>Linux的I/O模型分类</h3><ul><li><p>同步阻塞I/O(bloking I/O)</p></li><li><p>同步非阻塞I/O(non-bloking I/O)</p></li><li><p>I/O多路复用(multiplexing I/O)</p></li><li><p>信号驱动式I/O(signal-driven I/O)</p></li><li><p>异步I/O(asynchronous I/O)</p></li></ul><p>其中信号驱动式I/O在实际中并不常用:</p><img width="1232" height="906" alt="image" src="https://github.com/user-attachments/assets/8ec4c31e-38ac-4b5f-97c8-e267edf7e5b2" /><ul><li><p>阻塞或非阻塞I/O是指应用程序在发起I/O操作时,是立即返回还是等待</p></li><li><p>同步或异步是指应用程序在与内核通信时,数据从内核空间到应用空间的拷贝,是由内核主动发起还是应用程序来触发。</p></li></ul><p>BIO的缺点:每个线程只能建立一个连接,所以必须使用线程池来管理多个客户端连接,客户端连接能力有限</p><p>NIO的缺点:每次都需要轮询所有的客户连接,确认是否有读取事件,造成CPU的空转,浪费CPU资源</p><p>多路复用I/O:事件驱动模型的I/O,每当有读取事件,或者写入事件发布时,会获取事件对应的连接,执行对应的事件,极大利用了CPU资源</p><table><thead><tr><th>BIO(JioEndpoint)</th><th>同步阻塞式I/O,即Tomcat使用传统java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数小且固定架构。Tomcat8.5.x开始移除BIO</th><th></th></tr></thead><tbody><tr><td>NIO(NioEndPoint)</td><td>同步非阻塞式I/O,jdk1.4之后实现的新IO。该模式基于多路复用选择器监测连接状态再同步通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat8.0之后默认采用该模式。NIO模式使用连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯,编程比较复杂</td><td></td></tr><tr><td>AIO(Nio2EndPoint)</td><td>异步非阻塞式IO,jdk1.7后支持,与NIO不同在于不需要多路复用选择器,而是请求处理线程执行完成之后进行回调通知,继续执行后续操作。Tomcat8之后支持。一般适用于连接数较多且连接时间较长的应用。</td><td></td></tr><tr><td>APR(APrEndPoint)</td><td>(全称是Apache Portable Runtime/Apache可移植运行库),是ApacheHTTP服务器的支持库。APrEndPoint是通过JNI调用APR本地库而实现非阻塞I/O的。使用需要编译安装APR库</td><td></td></tr></tbody></table><h2 id="1-2-Tomcat-I-O模型如何选型"><a href="#1-2-Tomcat-I-O模型如何选型" class="headerlink" title="1.2 Tomcat I/O模型如何选型"></a>1.2 Tomcat I/O模型如何选型</h2><p>I/O调优实际上是连接器类型的选择,一般情况下默认都是NIO,在绝大多数情况下都是够用的,除非你的WEB应用用到了TLS加密传输,而且对性能要求极高,这个时候可以考虑APR,因为APR通过OoenSSL来处理TLS握手和加解密。OpenSSL本身用C语言实现,它还对TLS通信做了优化,所以性能比Java要高。如果你的Tomcat跑在Windows上,并且HTTP请求的数据量比较大,可以考虑NIO2,这是因为Windows从操作系统层面实现了真正意义的异步I/O,如果传输的数据量比较大,异步I/O的效果就能显现出来。如果你的Tomcat跑在Linux平台上,建议使用NIO。因为在Linux上,JavaNIO和JavaNIO2底层都是通过epoll来实现的,但是JavaNIO更加简单高效。指定IO模型只需要修改server.xml的protocol配置</p><h2 id="1-3-网络编程模型Reactor线程模型"><a href="#1-3-网络编程模型Reactor线程模型" class="headerlink" title="1.3 网络编程模型Reactor线程模型"></a>1.3 网络编程模型Reactor线程模型</h2><p>Reactor模型是网络服务器端用来处理高并发网络IO请求的一种编程模型。</p><p>该模型主要有三类处理事件:即连接事件、写事件、读事件;</p><p>三个关键角色:即reactor、acceptor、handler。</p><p>accpetor负责连接事件,handler负责读写事件,reactor负责事件监听和事件分发</p><h3 id="单Reactor单线程(redis用的就是这个):"><a href="#单Reactor单线程(redis用的就是这个):" class="headerlink" title="单Reactor单线程(redis用的就是这个):"></a>单Reactor单线程(redis用的就是这个):</h3><img width="955" height="630" alt="image" src="https://github.com/user-attachments/assets/dfa466fd-53dc-47ad-85d8-fc34e73d5df0" /><h3 id="单Reactor多线程模型"><a href="#单Reactor多线程模型" class="headerlink" title="单Reactor多线程模型"></a>单Reactor多线程模型</h3><img width="904" height="869" alt="image" src="https://github.com/user-attachments/assets/4b9e3e71-ff29-4191-a895-cee39c158cfb" /><h3 id="主从Reactor多线程"><a href="#主从Reactor多线程" class="headerlink" title="主从Reactor多线程"></a>主从Reactor多线程</h3><img width="935" height="970" alt="image" src="https://github.com/user-attachments/assets/e104c179-2062-4457-895e-e795a298d189" /><h2 id="1-4-Tomcat-NIO实现"><a href="#1-4-Tomcat-NIO实现" class="headerlink" title="1.4 Tomcat NIO实现"></a>1.4 Tomcat NIO实现</h2><p>在Tomcat中,Endpoint组件的主要工作就是处理IO,而NIOEndpoint利用Java NIO API实现了多路复用I/O模型。Tomcat的NioEndPoint是基于主从Reactor多线程模型设计的</p><p>有一个额外的线程单独处理连接事件,线程池负责处理事件</p><p>学习主从Reactor多线程模型这种实现可以去看下thrift实现</p><h1 id="2-Tomcat调优"><a href="#2-Tomcat调优" class="headerlink" title="2. Tomcat调优"></a>2. Tomcat调优</h1><h2 id="2-1-如何监控Tomcat的性能"><a href="#2-1-如何监控Tomcat的性能" class="headerlink" title="2.1 如何监控Tomcat的性能"></a>2.1 如何监控Tomcat的性能</h2><p>tomcat的关键指标有吞吐量、响应时间、错误数、线程池、CPU以及JVM内存。前三个指标是我们最关心的业务指标,Tomcat作为服务器,就是要能够又快又好地处理请求,因此吞吐量要大、响应时间要短,并且错误数要少。后面三个指标都是跟系统资源有关的,当某个资源出现瓶颈就会影响前面的业务指标,比如线程池种的线程数量不足会影响吞吐量和响应时间;但是线程数太多会耗费大量CPU,也会影响吞吐量,当内存不足时,会频繁触发GC,耗费CPU资源,最后也会反映在业务指标上来</p><h4 id="通过JConsole监控Tomcat"><a href="#通过JConsole监控Tomcat" class="headerlink" title="通过JConsole监控Tomcat"></a>通过JConsole监控Tomcat</h4><p>1)开启JMX的远程监听端口</p><p>我们可以在Tomcat的bin目录下新建一个名为setenv.sh的文件,然后输入下面内容</p><p>2)重启Tomcat,这样JMX的监听端口8011就开启了,可以通过JConsole来连接这个端口</p><p>这样就可以监控这些核心业务参数了</p><h2 id="2-2-常用调优参数"><a href="#2-2-常用调优参数" class="headerlink" title="2.2 常用调优参数"></a>2.2 常用调优参数</h2><h3 id="线程池参数"><a href="#线程池参数" class="headerlink" title="线程池参数"></a>线程池参数</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Executor</span> <span class="attr">name</span>=<span class="string">"tomcatThreadPool"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">namePrefix</span>=<span class="string">"catalina-exec-"</span> <!<span class="attr">--</span> <span class="attr">线程名前缀</span> <span class="attr">--</span>></span></span><br><span class="line"> maxThreads="500" <span class="comment"><!-- 最大线程数 --></span></span><br><span class="line"> minSpareThreads="50" <span class="comment"><!-- 最小空闲线程 --></span></span><br><span class="line"> maxIdleTime="60000" <span class="comment"><!-- 线程空闲超时时间(毫秒) --></span></span><br><span class="line"> maxQueueSize="100" <span class="comment"><!-- 任务队列大小 --></span></span><br><span class="line"> prestartminSpareThreads="true"/> <span class="comment"><!-- 启动时初始化最小空闲线程 --></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 连接器引用线程池 --></span></span><br><span class="line"><span class="tag"><<span class="name">Connector</span> <span class="attr">executor</span>=<span class="string">"tomcatThreadPool"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">port</span>=<span class="string">"8080"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">protocol</span>=<span class="string">"org.apache.coyote.http11.Http11Nio2Protocol"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">...</span>/></span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1-Tomcat的I-O模型"><a href="#1-Tomcat的I-O模型" class="headerlink" title="1. Tomcat的I&#x2F;O模型"></a>1. Tomcat的I&#x2F;O模型</h1><h2 id="1-1-L</summary>
<category term="Java Web" scheme="https://icarus-blog.top/categories/Java-Web/"/>
<category term="Tomcat" scheme="https://icarus-blog.top/categories/Java-Web/Tomcat/"/>
<category term="Tomcat IO模型" scheme="https://icarus-blog.top/tags/Tomcat-IO%E6%A8%A1%E5%9E%8B/"/>
<category term="Linux IO模型" scheme="https://icarus-blog.top/tags/Linux-IO%E6%A8%A1%E5%9E%8B/"/>
<category term="Reactor线程模型" scheme="https://icarus-blog.top/tags/Reactor%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B/"/>
<category term="Tomcat性能调优" scheme="https://icarus-blog.top/tags/Tomcat%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"/>
<category term="NIO" scheme="https://icarus-blog.top/tags/NIO/"/>
<category term="APR" scheme="https://icarus-blog.top/tags/APR/"/>
<category term="Tomcat线程池参数" scheme="https://icarus-blog.top/tags/Tomcat%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%8F%82%E6%95%B0/"/>
<category term="JConsole监控" scheme="https://icarus-blog.top/tags/JConsole%E7%9B%91%E6%8E%A7/"/>
</entry>
<entry>
<title>Tomcat类加载机制与热部署原理详解</title>
<link href="https://icarus-blog.top/2025/10/12/Tomcat%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/"/>
<id>https://icarus-blog.top/2025/10/12/Tomcat%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/</id>
<published>2025-10-12T17:07:00.000Z</published>
<updated>2025-12-11T06:48:10.629Z</updated>
<content type="html"><![CDATA[<h1 id="1-Tomcat类加载机制详解"><a href="#1-Tomcat类加载机制详解" class="headerlink" title="1.Tomcat类加载机制详解"></a>1.Tomcat类加载机制详解</h1><h2 id="1-1-JVM类加载器"><a href="#1-1-JVM类加载器" class="headerlink" title="1.1 JVM类加载器"></a>1.1 JVM类加载器</h2><p>Java中有3种类加载器,当然你也可以自定义类加载器</p><ul><li>引导类加载器(启动类加载器):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar</li><li>扩展类加载器:负责加载支撑JVM运行的JRE的lib目录下ext扩展目录中的核心jar包</li><li>应用程序类加载器(系统类加载器):负责ClassPath路径下的类包,主要就是加载你自己写的类</li><li>自定义类加载器:自己实现,负责加载自定义路径下的类包<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ClassLoaderDemo</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>{</span><br><span class="line"> <span class="comment">// BootStrapClassLoader c/c++实现,java层面是获取不到的,会输出null</span></span><br><span class="line"> System.out.println(ReentrantLock.class.getClassLoader());</span><br><span class="line"> <span class="comment">// ExtClassLoader</span></span><br><span class="line"> System.out.println(ZipInfo.class.getClassLoader());</span><br><span class="line"> <span class="comment">// AppClassLoader</span></span><br><span class="line"> System.out.println(ClassLoaderDemo.class.getClassLoader());</span><br><span class="line"> System.out.println(<span class="string">"===========JVM类加载器父子关系=============="</span>);</span><br><span class="line"> <span class="comment">// AppClassLoader </span></span><br><span class="line"> System.out.println(ClassLoader.getSystemClassLoader());</span><br><span class="line"> <span class="comment">// ExtClassLoader</span></span><br><span class="line"> System.out.println(ClassLoader.getSystemClassLoader().getParent());</span><br><span class="line"> <span class="comment">// BootStrapClassLoader</span></span><br><span class="line"> System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="1-2-双亲委派机制"><a href="#1-2-双亲委派机制" class="headerlink" title="1.2 双亲委派机制"></a>1.2 双亲委派机制</h2><p>Java中的类加载依赖双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载目录中查找并载入目标类,双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载<br><img width="1404" height="1188" alt="image" src="https://github.com/user-attachments/assets/dcd36232-ea06-418a-a266-178af135f9b0" /></p><h3 id="为什么设计双亲委派机制?"><a href="#为什么设计双亲委派机制?" class="headerlink" title="为什么设计双亲委派机制?"></a>为什么设计双亲委派机制?</h3><ul><li>沙箱安全机制:防止核心类库API被随意篡改</li><li>避免类的重复加载:父加载器已经加载了该类时,就没必要子类加载器再加载一次,保证被加载类的唯一性</li></ul><h3 id="ClassLocader-loadClass源码分析"><a href="#ClassLocader-loadClass源码分析" class="headerlink" title="ClassLocader#loadClass源码分析"></a>ClassLocader#loadClass源码分析</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ClassLoader</span> {</span><br><span class="line"> <span class="comment">// The parent class loader for delegation</span></span><br><span class="line"> <span class="comment">// Note: VM hardcoded the offset of this field, thus all new fields</span></span><br><span class="line"> <span class="comment">// must be added *after* it.</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ClassLoader parent;</span><br><span class="line"> <span class="keyword">public</span> Class<?> loadClass(String name) <span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"> <span class="keyword">return</span> loadClass(name, <span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">protected</span> Class<?> loadClass(String name, <span class="type">boolean</span> resolve)</span><br><span class="line"> <span class="keyword">throws</span> ClassNotFoundException</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">synchronized</span> (getClassLoadingLock(name)) {</span><br><span class="line"> <span class="comment">// First, check if the class has already been loaded</span></span><br><span class="line"> Class<?> c = findLoadedClass(name);</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="literal">null</span>) {</span><br><span class="line"> <span class="type">long</span> <span class="variable">t0</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (parent != <span class="literal">null</span>) {</span><br><span class="line"> c = parent.loadClass(name, <span class="literal">false</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> c = findBootstrapClassOrNull(name);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="comment">// ClassNotFoundException thrown if class not found</span></span><br><span class="line"> <span class="comment">// from the non-null parent class loader</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (c == <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// If still not found, then invoke findClass in order</span></span><br><span class="line"> <span class="comment">// to find the class.</span></span><br><span class="line"> <span class="type">long</span> <span class="variable">t1</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"> c = findClass(name);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// this is the defining class loader; record the stats</span></span><br><span class="line"> PerfCounter.getParentDelegationTime().addTime(t1 - t0);</span><br><span class="line"> PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);</span><br><span class="line"> PerfCounter.getFindClasses().increment();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (resolve) {</span><br><span class="line"> resolveClass(c);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> c;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="1-3-Tomcat为什么(如何)打破双亲委派机制?"><a href="#1-3-Tomcat为什么(如何)打破双亲委派机制?" class="headerlink" title="1.3 Tomcat为什么(如何)打破双亲委派机制?"></a>1.3 Tomcat为什么(如何)打破双亲委派机制?</h2><p>Tomcat的主要目的是充当应用服务器并处理用户的业务请求,使用场景可能会单机部署多应用。所以Tomcat打破双亲委派机制,核心是为了解决多Web应用独立运行的类隔离需求:双亲委派要求类加载器优先委托父加载器加载类,而Tomcat中多个Web应用可能依赖同一类的不同版本(如不同Spring版本),若按双亲委派,父加载器(如CommonClassLoader)加载一个版本后,所有应用都只能使用该版本,会引发类版本冲突;因此Tomcat自定义了WebAppClassLoader等加载器,让每个Web应用的类加载器优先加载自身WEB-INF/classes和WEB-INF/lib下的类,仅在自身未找到时才委托父加载器,从而实现不同应用类的独立隔离,保证多应用在同一容器中互不干扰地运行。<br>Tomcat打破双亲委派具体实现就是重写ClassLoader的两个方法:findClass和loadClass,来改变双亲委派的类加载顺序,但是直接重写这两个实现依然需要保证核心类库被篡改,所以为了保证核心类库的安全性,并没有一开始就使用系统类型加载器加载,而是先查询本地目录缓存,为了避免本地目录下的类覆盖JRE的核心类,会先尝试用JVM扩展类加载器ExtClassLoader去加载</p><h3 id="findClass方法"><a href="#findClass方法" class="headerlink" title="findClass方法"></a>findClass方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Class<?> findClass(String name) <span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" findClass("</span> + name + <span class="string">")"</span>);</span><br><span class="line"></span><br><span class="line"> checkStateForClassLoading(name);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// (1) Permission to define this class when using a SecurityManager</span></span><br><span class="line"> <span class="keyword">if</span> (securityManager != <span class="literal">null</span>) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> name.lastIndexOf(<span class="string">'.'</span>);</span><br><span class="line"> <span class="keyword">if</span> (i >= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.trace(<span class="string">" securityManager.checkPackageDefinition"</span>);</span><br><span class="line"> securityManager.checkPackageDefinition(name.substring(<span class="number">0</span>,i));</span><br><span class="line"> } <span class="keyword">catch</span> (Exception se) {</span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.trace(<span class="string">" -->Exception-->ClassNotFoundException"</span>, se);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>(name, se);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Ask our superclass to locate this class, if possible</span></span><br><span class="line"> <span class="comment">// (throws ClassNotFoundException if it is not found)</span></span><br><span class="line"> Class<?> clazz = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.trace(<span class="string">" findClassInternal("</span> + name + <span class="string">")"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (securityManager != <span class="literal">null</span>) {</span><br><span class="line"> PrivilegedAction<Class<?>> dp =</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">PrivilegedFindClassByName</span>(name);</span><br><span class="line"> clazz = AccessController.doPrivileged(dp);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 1、先在应用本地目录下查找类 </span></span><br><span class="line"> clazz = findClassInternal(name);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span>(AccessControlException ace) {</span><br><span class="line"> log.warn(<span class="string">"WebappClassLoader.findClassInternal("</span> + name</span><br><span class="line"> + <span class="string">") security exception: "</span> + ace.getMessage(), ace);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>(name, ace);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException e) {</span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.trace(<span class="string">" -->RuntimeException Rethrown"</span>, e);</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ((clazz == <span class="literal">null</span>) && hasExternalRepositories) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 2、如果在本地目录没有找到,委派父加载器去查找</span></span><br><span class="line"> clazz = <span class="built_in">super</span>.findClass(name);</span><br><span class="line"> } <span class="keyword">catch</span>(AccessControlException ace) {</span><br><span class="line"> log.warn(<span class="string">"WebappClassLoader.findClassInternal("</span> + name</span><br><span class="line"> + <span class="string">") security exception: "</span> + ace.getMessage(), ace);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>(name, ace);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException e) {</span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.trace(<span class="string">" -->RuntimeException Rethrown"</span>, e);</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 3、如果父加载器也没找到,抛出异常</span></span><br><span class="line"> <span class="keyword">if</span> (clazz == <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" --> Returning ClassNotFoundException"</span>);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>(name);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.trace(<span class="string">" --> Passing on ClassNotFoundException"</span>);</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Return the class we have located</span></span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled())</span><br><span class="line"> log.debug(<span class="string">" Returning class "</span> + clazz);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (log.isTraceEnabled()) {</span><br><span class="line"> ClassLoader cl;</span><br><span class="line"> <span class="keyword">if</span> (Globals.IS_SECURITY_ENABLED){</span><br><span class="line"> cl = AccessController.doPrivileged(</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">PrivilegedGetClassLoader</span>(clazz));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cl = clazz.getClassLoader();</span><br><span class="line"> }</span><br><span class="line"> log.debug(<span class="string">" Loaded by "</span> + cl.toString());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (clazz);</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="loadClass方法"><a href="#loadClass方法" class="headerlink" title="loadClass方法"></a>loadClass方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Class<?> loadClass(String name, <span class="type">boolean</span> resolve) <span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">synchronized</span> (getClassLoadingLock(name)) {</span><br><span class="line"> Class<?> clazz = <span class="literal">null</span>;</span><br><span class="line"> <span class="comment">// 1、从本地缓存中查找是否加载过此类</span></span><br><span class="line"> clazz = findLoadedClass0(name);</span><br><span class="line"> <span class="keyword">if</span> (clazz != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Returning class from cache"</span>);</span><br><span class="line"> <span class="keyword">if</span> (resolve)</span><br><span class="line"> resolveClass(clazz);</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2、从AppClassLoader中查找是否加载过此类</span></span><br><span class="line"> clazz = findLoadedClass(name);</span><br><span class="line"> <span class="keyword">if</span> (clazz != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Returning class from cache"</span>);</span><br><span class="line"> <span class="keyword">if</span> (resolve)</span><br><span class="line"> resolveClass(clazz);</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">String</span> <span class="variable">resourceName</span> <span class="operator">=</span> binaryNameToPath(name, <span class="literal">false</span>);</span><br><span class="line"> <span class="comment">// 3、尝试用ExtClassLoader 类加载器加载类,防止应用覆盖JRE的核心类</span></span><br><span class="line"> <span class="type">ClassLoader</span> <span class="variable">javaseLoader</span> <span class="operator">=</span> getJavaseClassLoader();</span><br><span class="line"> <span class="type">boolean</span> tryLoadingFromJavaseLoader;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> URL url;</span><br><span class="line"> <span class="keyword">if</span> (securityManager != <span class="literal">null</span>) {</span><br><span class="line"> PrivilegedAction<URL> dp = <span class="keyword">new</span> <span class="title class_">PrivilegedJavaseGetResource</span>(resourceName);</span><br><span class="line"> url = AccessController.doPrivileged(dp);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> url = javaseLoader.getResource(resourceName);</span><br><span class="line"> }</span><br><span class="line"> tryLoadingFromJavaseLoader = (url != <span class="literal">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> tryLoadingFromJavaseLoader = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">boolean</span> <span class="variable">delegateLoad</span> <span class="operator">=</span> delegate || filter(name, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4、判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类</span></span><br><span class="line"> <span class="keyword">if</span> (delegateLoad) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Delegating to parent classloader1 "</span> + parent);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> clazz = Class.forName(name, <span class="literal">false</span>, parent);</span><br><span class="line"> <span class="keyword">if</span> (clazz != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Loading class from parent"</span>);</span><br><span class="line"> <span class="keyword">if</span> (resolve)</span><br><span class="line"> resolveClass(clazz);</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="comment">// Ignore</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 5、默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载</span></span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Searching local repositories"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> clazz = findClass(name);</span><br><span class="line"> <span class="keyword">if</span> (clazz != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Loading class from local repository"</span>);</span><br><span class="line"> <span class="keyword">if</span> (resolve)</span><br><span class="line"> resolveClass(clazz);</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="comment">// Ignore</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 6、如果在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载</span></span><br><span class="line"> <span class="keyword">if</span> (!delegateLoad) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Delegating to parent classloader at end: "</span> + parent);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> clazz = Class.forName(name, <span class="literal">false</span>, parent);</span><br><span class="line"> <span class="keyword">if</span> (clazz != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (log.isDebugEnabled())</span><br><span class="line"> log.debug(<span class="string">" Loading class from parent"</span>);</span><br><span class="line"> <span class="keyword">if</span> (resolve)</span><br><span class="line"> resolveClass(clazz);</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="comment">// Ignore</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>(name);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="1-4-Tomcat如何隔离Web应用"><a href="#1-4-Tomcat如何隔离Web应用" class="headerlink" title="1.4 Tomcat如何隔离Web应用"></a>1.4 Tomcat如何隔离Web应用</h2><p>Tomcat作为Servlet容器,它负责加载我们的Servlet类,此外它还负责加载Servlet所依赖的JAR包。并且Tomcat本身也是一个Java程序,因此它需要加载自己的类和依赖的JAR包。Tomcat此时需要解决下面这三个问题:</p><ol><li>Tomcat运行了两个Web应用程序,两个Web应用中有同名的Servlet,但是功能不同,Tomcat需要同时加载和管理这两个同名的Servlet类,保证它们不会冲突。Tomcat是如何实现Web应用之间的所有类都完成隔离的?</li><li>两个Web应用都依赖同一个第三方的JAR包,比如Spring,Spring的JAR包被加载到内存后,Tomcat要保证这两个Web应用能够共享,也就是说Spring的JAR包只被加载一次,否则随着依赖第三方JAR包的增多,JVM的内存会膨胀,Tomcat是如何解决的?</li><li>跟JVM一样,我们需要隔离Tomcat本身的类和Web应用的类。Tomcat是怎么实现的?<br>也就是如何实现内部类隔离,三方JAR包资源共享,以及自身的隔离性</li></ol><h3 id="Tomcat类加载器的层次结构"><a href="#Tomcat类加载器的层次结构" class="headerlink" title="Tomcat类加载器的层次结构"></a>Tomcat类加载器的层次结构</h3><p>为了解决这些问题,Tomcat设计了类加载器的层次结构,它们的关系如下图所示:<br><img width="1668" height="2034" alt="image" src="https://github.com/user-attachments/assets/ffc48507-ed9f-47a8-9b39-76ff73eaa2be" /></p><ul><li>commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器以及本身各个Webapp访问;</li><li>catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;</li><li>sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有的Webapp可见,但是对于Tomcat容器不可见;</li><li>WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class,只对当前webapp可见,加载war包里相关的类,每个war包应用都有自己的WebAppClassLoader,实现相互隔离,加载对应各自的依赖版本</li></ul><h3 id="全盘负责委托机制"><a href="#全盘负责委托机制" class="headerlink" title="全盘负责委托机制"></a>全盘负责委托机制</h3><p>当一个ClassLoader装载一个类的时候,除非显式的使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入。<br>比如Spring作为Bean工厂,它需要创建业务类的实例,并且创建业务类实例之前需要加载这些类。Spring是通过Class.forName来加载业务类的,下面是forName的源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Class<?> forName(String className){</span><br><span class="line"> Class<?> caller = Reflection.getCallerClass();</span><br><span class="line"> <span class="keyword">return</span> forName(className,<span class="literal">true</span>,CLassLoader.getClassLoader(caller),caller);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>forName的函数中,会默认使用调用者的类加载器去加载业务类。<br>Web应用之间共享的JAR包可以交给SharedClassLoader来加载,从而避免重复加载。Spring作为共享的第三方JAR包,它本身是由SharedClassLoader来加载的,Spring又要去加载业务类,按照前面的那条规则,加载Spring的类加载器也会用来加载业务类,但是业务类再Web应用目录下,不在SharedClassLoader的加载路径下,Tomcat是如何解决这个问题的?</p><h3 id="线程上下文加载器"><a href="#线程上下文加载器" class="headerlink" title="线程上下文加载器"></a>线程上下文加载器</h3><p>于是线程上下文加载器登场了,它其实是一种类加载器传递机制。为什么叫做“线程上下文加载器”?因为这个类加载器保存在线程私有数据里,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个线程类加载器取出来使用。因此Tomcat为每个Web应用创建一个WebAppClassLoader类加载器,并在启动Web应用的线程里设置线程上下文加载器,这样Spring在启动时就将线程上下文类加载器取出来,用来加载Bean。源代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c1 = Thread.currentThread().getContextClassLoader();</span><br></pre></td></tr></table></figure><p>线程上下文加载器不仅仅可以用在Tomcat和Spring类加载器的场景里,核心框架类需要加载具体实现类时都可以用到它,比如我们熟悉的JDBC就是通过上下文类加载器来加载不同的数据库驱动的</p><h1 id="2-Tomcat热加载和热部署"><a href="#2-Tomcat热加载和热部署" class="headerlink" title="2. Tomcat热加载和热部署"></a>2. Tomcat热加载和热部署</h1><p>项目开发过程中,经常要改动Java/JSP文件,但是又不想重新启动Tomcat,有两种方式:热加载和热部署。热部署表示重新部署应用,它执行的主体是HOST。热加载表示重新加载class,它的执行主体是Context。</p><ul><li>热加载:在server.xml -> context标签中 设置 reloadable = “true”<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Context</span> <span class="attr">docBase</span>=<span class="string">"C:\project"</span> <span class="attr">path</span>=<span class="string">"/project"</span> <span class="attr">reloadable</span>=<span class="string">"true"</span>/></span></span><br></pre></td></tr></table></figure></li><li>热部署:在server.xml -> Host标签中 设置 autoDeploy=”true”<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Host</span> <span class="attr">name</span>=<span class="string">"localhost"</span> <span class="attr">appBase</span>=<span class="string">"webapps"</span> <span class="attr">unpackWARS</span>=<span class="string">"true"</span> <span class="attr">autoDeploy</span>=<span class="string">"true"</span> /></span></span><br></pre></td></tr></table></figure>它们的区别是:</li><li>热加载的实现方式是Web容器启动一个后台线程,定期检测类文件的变化,如果有变化,就重新加载类,这个过程中不会清空Session,一般用在开发环境</li><li>热部署原理类似,也是由后台线程定时检测Web应用的变化,但它会重新加载整个Web应用。这种方式会清空Session,比热加载更加彻底,一般用于生产环境</li></ul><h2 id="2-1-Tomcat开启后台线程执行周期性任务"><a href="#2-1-Tomcat开启后台线程执行周期性任务" class="headerlink" title="2.1 Tomcat开启后台线程执行周期性任务"></a>2.1 Tomcat开启后台线程执行周期性任务</h2><p>Tomcat 通过ScheduledThreadPoolExecutor(定时线程池) 管理后台周期性任务,核心是在容器初始化时(如 Service 启动阶段)创建固定数量的后台线程,绑定到 Catalina 生命周期中统一管理。典型场景包括:会话过期清理(定期扫描 HttpSession,销毁超时会话)、日志滚动(按时间 / 大小切割访问日志)、连接池维护(检测并回收空闲数据库连接)、集群节点心跳检测等。任务调度支持 “固定延迟”(如每隔 30 秒执行)或 “固定速率”(如每分钟执行一次),且线程池会自动处理任务异常,避免单个任务失败导致整个线程池崩溃,确保周期性任务稳定执行且不阻塞主线程。</p><h2 id="2-2-Tomcat热加载实现原理"><a href="#2-2-Tomcat热加载实现原理" class="headerlink" title="2.2 Tomcat热加载实现原理"></a>2.2 Tomcat热加载实现原理</h2><p>Tomcat 热加载的核心是基于类加载器的 “重新加载” 机制,仅针对 Web 应用内部的类和资源(如 WEB-INF/classes、WEB-INF/lib),无需重启整个 Tomcat 容器。具体逻辑:</p><ol><li>后台线程(如 WebAppLoader 的监控线程)定期扫描 WEB-INF/classes 和 lib 目录下文件的 “最后修改时间”,对比上次扫描记录;</li><li>若检测到文件变化(如.class 文件更新、JAR 包替换),Tomcat 会销毁当前 Web 应用的WebAppClassLoader(应用级类加载器)(同时销毁其加载的所有类、Servlet 实例、线程资源);</li><li>创建新的 WebAppClassLoader,重新加载更新后的类和资源,初始化 Servlet、Spring 容器等组件,完成 “热加载”。该机制仅适用于开发环境(如调试时修改代码),且无法处理服务器级配置(如 server.xml)的变化,因这类配置依赖 Tomcat 核心类加载器,无法单独重新加载。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 启动资源监控线程(位于WebAppLoader中)</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// 启动定时监控线程(每2秒扫描一次资源变化)</span></span><br><span class="line"> monitorThread = <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">ResourceMonitor</span>(), <span class="string">"WebappResourceMonitor"</span>);</span><br><span class="line"> monitorThread.setDaemon(<span class="literal">true</span>);</span><br><span class="line"> monitorThread.start();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 资源监控逻辑(检测类或JAR包变化)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ResourceMonitor</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="type">long</span> <span class="variable">lastModified</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 上次修改时间</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">while</span> (running) {</span><br><span class="line"> <span class="comment">// 扫描WEB-INF/classes和lib目录的最后修改时间</span></span><br><span class="line"> <span class="type">long</span> <span class="variable">currentModified</span> <span class="operator">=</span> getResourcesLastModified();</span><br><span class="line"> <span class="keyword">if</span> (currentModified > lastModified) {</span><br><span class="line"> <span class="comment">// 检测到变化,触发热加载</span></span><br><span class="line"> context.reload(); <span class="comment">// 调用Context的重新加载方法</span></span><br><span class="line"> lastModified = currentModified;</span><br><span class="line"> }</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>); <span class="comment">// 间隔2秒扫描</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. Context的reload方法(销毁旧类加载器,创建新的)</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reload</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// 销毁当前Web应用的类加载器和资源</span></span><br><span class="line"> <span class="type">WebappClassLoader</span> <span class="variable">oldLoader</span> <span class="operator">=</span> <span class="built_in">this</span>.loader;</span><br><span class="line"> oldLoader.stop(); <span class="comment">// 释放类、Servlet实例等资源</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 创建新的类加载器,重新加载资源</span></span><br><span class="line"> <span class="built_in">this</span>.loader = <span class="keyword">new</span> <span class="title class_">WebappClassLoader</span>(oldLoader.getParent());</span><br><span class="line"> <span class="built_in">this</span>.loader.start(); <span class="comment">// 加载更新后的类和配置</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><h2 id="2-3-Tomcat热部署实现原理"><a href="#2-3-Tomcat热部署实现原理" class="headerlink" title="2.3 Tomcat热部署实现原理"></a>2.3 Tomcat热部署实现原理</h2><p>Tomcat 热部署是整个 Web 应用的 “卸载 - 重新部署” 过程,支持替换应用的完整资源(包括 WAR 包、配置文件、静态资源等),可用于生产环境的版本更新(通常需短暂停止应用)。具体逻辑:</p><ol><li>触发方式(手动复制新 WAR 包、通过 Manager 应用 / API 发送部署指令);</li><li>卸载旧应用:停止应用所有 Servlet 和 Filter,关闭数据库连接池、线程池等资源,销毁对应的 WebAppClassLoader 和应用上下文(Context),删除旧应用的临时目录;</li><li>部署新应用:解压新 WAR 包(若为 WAR 格式),创建新的 Context 和 WebAppClassLoader,加载新的类、资源和配置文件,初始化 Servlet、Listener 等组件,绑定到 Connector(连接器)接收请求。</li></ol><p>与热加载的核心区别是:热部署针对 “整个应用” 的替换,可处理配置文件(如 web.xml)的变化;热加载仅针对 “类和资源” 的重新加载,依赖应用级类加载器,不涉及 Context 销毁。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 热部署触发(检测webapps目录下的WAR包变化)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HostConfig</span> <span class="keyword">implements</span> <span class="title class_">LifecycleListener</span> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lifecycleEvent</span><span class="params">(LifecycleEvent event)</span> {</span><br><span class="line"> <span class="keyword">if</span> (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {</span><br><span class="line"> checkResources(); <span class="comment">// 定期检查应用资源变化</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">checkResources</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// 扫描webapps目录下的WAR包或目录</span></span><br><span class="line"> <span class="keyword">for</span> (File app : webappsDir.listFiles()) {</span><br><span class="line"> <span class="keyword">if</span> (isModified(app)) { <span class="comment">// 检测到应用更新(如WAR包替换)</span></span><br><span class="line"> redeploy(app); <span class="comment">// 执行热部署</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 重新部署逻辑(卸载旧应用+部署新应用)</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">redeploy</span><span class="params">(File app)</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">appName</span> <span class="operator">=</span> app.getName();</span><br><span class="line"> <span class="comment">// 第一步:卸载旧应用</span></span><br><span class="line"> <span class="type">Context</span> <span class="variable">oldContext</span> <span class="operator">=</span> host.findContext(appName);</span><br><span class="line"> <span class="keyword">if</span> (oldContext != <span class="literal">null</span>) {</span><br><span class="line"> host.removeChild(oldContext); <span class="comment">// 从Host中移除旧Context</span></span><br><span class="line"> oldContext.stop(); <span class="comment">// 停止旧应用(释放连接池、线程等)</span></span><br><span class="line"> oldContext.destroy(); <span class="comment">// 销毁旧应用资源</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 第二步:部署新应用</span></span><br><span class="line"> <span class="type">Context</span> <span class="variable">newContext</span> <span class="operator">=</span> createContext(app); <span class="comment">// 创建新Context</span></span><br><span class="line"> host.addChild(newContext); <span class="comment">// 添加到Host</span></span><br><span class="line"> newContext.start(); <span class="comment">// 启动新应用(加载新类、初始化Servlet等)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1-Tomcat类加载机制详解"><a href="#1-Tomcat类加载机制详解" class="headerlink" title="1.Tomcat类加载机制详解"></a>1.Tomcat类加载机制详解</h1><h2 id="1-1-JVM类加载器"></summary>
<category term="后端技术" scheme="https://icarus-blog.top/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="中间件" scheme="https://icarus-blog.top/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="Tomcat" scheme="https://icarus-blog.top/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/%E4%B8%AD%E9%97%B4%E4%BB%B6/Tomcat/"/>
<category term="Tomcat" scheme="https://icarus-blog.top/tags/Tomcat/"/>
<category term="类加载机制" scheme="https://icarus-blog.top/tags/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/"/>
<category term="热部署" scheme="https://icarus-blog.top/tags/%E7%83%AD%E9%83%A8%E7%BD%B2/"/>
</entry>
<entry>
<title>Tomcat 整体结构以及设计源码分析</title>
<link href="https://icarus-blog.top/2025/10/10/TomCat%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84%E4%BB%A5%E5%8F%8A%E8%AE%BE%E8%AE%A1%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<id>https://icarus-blog.top/2025/10/10/TomCat%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84%E4%BB%A5%E5%8F%8A%E8%AE%BE%E8%AE%A1%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</id>
<published>2025-10-10T00:00:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h1 id="1-Tomcat是什么?"><a href="#1-Tomcat是什么?" class="headerlink" title="1. Tomcat是什么?"></a>1. Tomcat是什么?</h1><p>Tomcat 是一款<strong>开源的 Java Web 服务器 + Servlet 容器</strong>,由 Apache 软件基金会开发维护,主要用于部署和运行 Java Web 应用程序(如基于 Servlet、JSP、Spring MVC 等技术的应用)。它是 Java 生态中最流行的 Web 容器之一,兼具轻量性、稳定性和易扩展性,广泛用于开发和生产环境。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">apache-tomcat-<span class="number">10.0</span><span class="number">.0</span>/</span><br><span class="line">├── <span class="built_in">bin</span>/ <span class="comment"># 脚本与可执行文件目录</span></span><br><span class="line">├── conf/ <span class="comment"># 配置文件目录</span></span><br><span class="line">├── lib/ <span class="comment"># 核心依赖库目录</span></span><br><span class="line">├── webapps/ <span class="comment"># Web 应用部署目录</span></span><br><span class="line">├── logs/ <span class="comment"># 日志文件目录</span></span><br><span class="line">├── temp/ <span class="comment"># 临时文件目录</span></span><br><span class="line">├── work/ <span class="comment"># JSP 编译缓存目录</span></span><br><span class="line">└── LICENSE、NOTICE 等 <span class="comment"># 许可证和说明文件</span></span><br></pre></td></tr></table></figure><img width="3182" height="870" alt="image" src="https://github.com/user-attachments/assets/d66e17b1-c5e5-4c58-b54e-464dc92b3c83" /><h1 id="2-WEB应用部署的三种方式"><a href="#2-WEB应用部署的三种方式" class="headerlink" title="2. WEB应用部署的三种方式"></a>2. WEB应用部署的三种方式</h1><h3 id="2-1-自动部署(Webapps-目录部署)"><a href="#2-1-自动部署(Webapps-目录部署)" class="headerlink" title="2.1 自动部署(Webapps 目录部署)"></a>2.1 自动部署(Webapps 目录部署)</h3><p>这是最简便、最常用的部署方式,适合开发环境或简单场景,依赖 Tomcat 的自动检测机制。</p><h4 id="原理:"><a href="#原理:" class="headerlink" title="原理:"></a>原理:</h4><p>Tomcat 启动时会自动扫描 <code>webapps/</code> 目录(默认部署目录),对目录中的 Web 应用(包括 <strong>WAR 包</strong> 或 <strong>解压后的应用目录</strong>)进行部署;运行过程中,若向 <code>webapps/</code> 目录添加新的应用(如复制 WAR 包),Tomcat 也会自动检测并部署(需开启自动部署配置,默认开启)。</p><h4 id="操作步骤:"><a href="#操作步骤:" class="headerlink" title="操作步骤:"></a>操作步骤:</h4><ol><li><p>将 Web 应用打包为 WAR 包(如 <code>myapp.war</code>),或直接获取解压后的应用目录(如 <code>myapp/</code>)。</p></li><li><p>将 WAR 包或目录复制到 Tomcat 的 <code>webapps/</code> 目录下。</p></li><li><p>启动 Tomcat(<code>bin/startup.sh</code> 或 <code>startup.bat</code>),Tomcat 会自动处理:</p><ul><li><p>若为 WAR 包:自动解压为同名目录(如 <code>myapp.war</code> → <code>myapp/</code>)。</p></li><li><p>若为目录:直接识别为 Web 应用。</p></li></ul></li><li><p>访问应用:通过 <code>http://localhost:8080/应用名</code> 访问(如 <code>http://localhost:8080/myapp</code>)。</p></li></ol><h4 id="优缺点:"><a href="#优缺点:" class="headerlink" title="优缺点:"></a>优缺点:</h4><ul><li><p><strong>优点</strong>:操作简单,无需手动配置,适合开发调试或快速部署。</p></li><li><p><strong>缺点</strong>:灵活性低(应用必须放在 <code>webapps/</code> 目录);若需修改应用路径或配置,需额外操作。</p></li></ul><h3 id="2-2-配置文件部署(Context-配置部署)"><a href="#2-2-配置文件部署(Context-配置部署)" class="headerlink" title="2.2 配置文件部署(Context 配置部署)"></a>2.2 配置文件部署(Context 配置部署)</h3><p>通过手动配置 Tomcat 的 XML 配置文件,指定应用的部署路径和实际存储位置,适合需要自定义部署路径(非 <code>webapps/</code> 目录)或复杂配置的场景。</p><h4 id="原理:-1"><a href="#原理:-1" class="headerlink" title="原理:"></a>原理:</h4><p>Tomcat 通过 <strong>Context 元素</strong> 定义 Web 应用的映射关系(访问路径 → 实际目录)。Context 配置可放在两个位置:</p><ul><li>在 <code>conf/server.xml</code> 中配置(不推荐)</li></ul><p>在 <code>server.xml</code> 的 <code><Host></code> 标签内添加 <code><Context></code> 元素,指定应用的访问路径和实际位置。</p><p><strong>示例</strong>:</p><p>xml</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Server</span> <span class="attr">...</span>></span><span class="tag"><<span class="name">Service</span> <span class="attr">...</span>></span><span class="tag"><<span class="name">Engine</span> <span class="attr">...</span>></span><span class="tag"><<span class="name">Host</span> <span class="attr">name</span>=<span class="string">"localhost"</span> <span class="attr">appBase</span>=<span class="string">"webapps"</span> <span class="attr">...</span>></span><span class="comment"><!-- 配置 myapp 应用:访问路径为 /myapp,实际目录为 /opt/apps/myapp --></span><span class="tag"><<span class="name">Context</span> <span class="attr">path</span>=<span class="string">"/myapp"</span> <span class="attr">docBase</span>=<span class="string">"/opt/apps/myapp"</span> <span class="attr">reloadable</span>=<span class="string">"true"</span> /></span><span class="tag"></<span class="name">Host</span>></span><span class="tag"></<span class="name">Engine</span>></span><span class="tag"></<span class="name">Service</span>></span><span class="tag"></<span class="name">Server</span>></span></span><br></pre></td></tr></table></figure><ul><li><p><code>path</code>:应用的访问路径(如 <code>/myapp</code> 对应 <code>http://localhost:8080/myapp</code>)。</p></li><li><p><code>docBase</code>:应用的实际存储路径(可绝对路径或相对 <code>appBase</code> 的路径)。</p></li><li><p><code>reloadable</code>:是否自动重载(<code>true</code> 表示当应用类文件变化时,Tomcat 自动重启应用,适合开发环境)。</p></li></ul><ul><li>在 <code>conf/Catalina/localhost/</code> 下创建独立 XML 文件(推荐)</li></ul><p>在 <code>conf/Catalina/localhost/</code> 目录下创建以 <strong>应用访问路径</strong> 命名的 XML 文件(如 <code>myapp.xml</code>),文件内容为 <code><Context></code> 元素。</p><p><strong>示例</strong>:创建 <code>conf/Catalina/localhost/myapp.xml</code>,内容:</p><p>xml</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 访问路径为 /myapp,实际目录为 /opt/apps/myapp --></span><span class="tag"><<span class="name">Context</span> <span class="attr">docBase</span>=<span class="string">"/opt/apps/myapp"</span> <span class="attr">reloadable</span>=<span class="string">"true"</span> /></span></span><br></pre></td></tr></table></figure><ul><li><p>此时应用的访问路径由 XML 文件名决定(<code>myapp.xml</code> → 访问路径 <code>/myapp</code>)。</p></li><li><p>无需重启 Tomcat,添加 / 修改此文件后,Tomcat 会自动部署 / 更新应用(热部署)。</p></li></ul><h4 id="优缺点:-1"><a href="#优缺点:-1" class="headerlink" title="优缺点:"></a>优缺点:</h4><ul><li><p><strong>优点</strong>:灵活(应用可放在任意目录)、支持热部署(独立 XML 文件方式)、便于集中管理配置。</p></li><li><p><strong>缺点</strong>:需手动编写配置文件,对新手稍复杂。</p></li></ul><h3 id="2-3-Manager-应用部署(远程管理部署)"><a href="#2-3-Manager-应用部署(远程管理部署)" class="headerlink" title="2.3 Manager 应用部署(远程管理部署)"></a>2.3 Manager 应用部署(远程管理部署)</h3><p>通过 Tomcat 自带的 <strong>Manager 应用</strong>(网页或 API)远程部署应用,适合生产环境中无需直接操作服务器文件系统的场景(如运维人员远程部署)。</p><h4 id="前提:配置-Manager-访问权限"><a href="#前提:配置-Manager-访问权限" class="headerlink" title="前提:配置 Manager 访问权限"></a>前提:配置 Manager 访问权限</h4><ol><li><p>编辑 <code>conf/tomcat-users.xml</code>,添加具有 <code>manager-gui</code>(网页管理)或 <code>manager-script</code>(API 管理)权限的用户:</p></li><li><p>xml</p></li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">tomcat-users</span>></span><span class="comment"><!-- 允许通过网页管理部署 --></span><span class="tag"><<span class="name">user</span> <span class="attr">username</span>=<span class="string">"admin"</span> <span class="attr">password</span>=<span class="string">"123456"</span> <span class="attr">roles</span>=<span class="string">"manager-gui"</span> /></span><span class="tag"></<span class="name">tomcat-users</span>></span></span><br></pre></td></tr></table></figure><ol><li>重启 Tomcat 使配置生效。</li></ol><h4 id="操作步骤(网页方式):"><a href="#操作步骤(网页方式):" class="headerlink" title="操作步骤(网页方式):"></a>操作步骤(网页方式):</h4><ol><li><p>访问 Manager 应用:<code>http://localhost:8080/manager/html</code>,输入配置的用户名 / 密码登录。</p></li><li><p>在 “Deploy” 区域部署应用:</p><ul><li><p><strong>方式 1(上传 WAR 包)</strong>:点击 “Browse” 选择本地 WAR 包,点击 “Deploy” 上传并部署。</p></li><li><p><strong>方式 2(指定 URL)</strong>:在 “Context path” 输入访问路径(如 <code>/myapp</code>),在 “WAR or Directory URL” 输入远程 WAR 包的 URL(如 <code>http://example.com/myapp.war</code>),点击 “Deploy”。</p></li></ul></li><li><p>部署成功后,可在 “Applications” 列表中看到应用,支持启动、停止、卸载等操作。</p></li></ol><h1 id="3-Tomcat整体架构分析"><a href="#3-Tomcat整体架构分析" class="headerlink" title="3. Tomcat整体架构分析"></a>3. Tomcat整体架构分析</h1><p><strong>Tomcat</strong>本质上只<strong>需要</strong>实现两个核心功能:</p><ul><li><p>处理Socket连接,负责网络字节流Request和Response对象的转化</p></li><li><p>加载和管理Servlet,以及具体处理Request请求</p></li></ul><p>因此Tomcat设计两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情,连接器负责对外交流,容器负责内部处理</p><img width="2760" height="1400" alt="image" src="https://github.com/user-attachments/assets/cae9cc54-f3d8-4dba-8875-0deba0771b48" /><h2 id="3-1-Tom核心组件详解"><a href="#3-1-Tom核心组件详解" class="headerlink" title="3.1 Tom核心组件详解"></a>3.1 Tom核心组件详解</h2><table><thead><tr><th><strong>组件</strong></th><th><strong>作用</strong></th></tr></thead><tbody><tr><td><strong>Server</strong></td><td>整个 Tomcat 实例的顶层容器,代表一个运行的 Tomcat 服务器,可包含多个 Service。</td></tr><tr><td><strong>Service</strong></td><td>关联一个或多个 Connector 与一个 Container,负责将请求从 Connector 传递到 Container。</td></tr><tr><td><strong>Connector</strong></td><td>监听指定端口(如 8080),接收客户端 HTTP 请求,解析请求数据并传递给 Container,同时将 Container 的响应返回给客户端。支持 HTTP、HTTPS、AJP 等协议。</td></tr><tr><td><strong>Container</strong></td><td>负责处理请求的核心组件,内部采用层级结构:</td></tr><tr><td><strong>Servlet</strong></td><td>业务逻辑组件,由 Container 调用,处理具体的 HTTP 请求(如接收参数、访问数据库、生成响应)。</td></tr></tbody></table><h3 id="Server组件"><a href="#Server组件" class="headerlink" title="Server组件"></a>Server组件</h3><p>整个 Tomcat 实例的顶层容器,代表一个运行的 Tomcat 服务器,可包含多组服务( Service )。负责管理和启动各个Service,同时监听8005端口发送过来的shutdown命令</p><h3 id="Service组件"><a href="#Service组件" class="headerlink" title="Service组件"></a>Service组件</h3><p>每个service组件都包含了若干用于接收客户端消息的Connector组件和处理请求的Engine组件,Service组件还包含了若干Executor组件,每个Executor都是一个线程池,它可以为Service内部所有组件提供线程池执行任务。Tomcat内部可能有多个Service,这样的设计也是出于灵活性的考虑。通过在Tomcat内部配置多个Service,可以实现通过不同端口号来访问同一台机器上部署的不同应用</p><img width="2760" height="1400" alt="image" src="https://github.com/user-attachments/assets/e3d8b4e6-f43e-4c1d-a933-1636a6947687" /><h3 id="Connector组件"><a href="#Connector组件" class="headerlink" title="Connector组件"></a>Connector组件</h3><p>Tomcat与外部世界的连接器,监听固定端口接收外部请求,传递给Container,并将Container处理的结果返回给外部。连接器对Servlet容器屏蔽了不同的应用层协议以及IO模型,无论是HTTP还是AJP,在容器中获取到的都是一个标准的ServletRequest对象</p><h3 id="Container组件"><a href="#Container组件" class="headerlink" title="Container组件"></a>Container组件</h3><p>容器,顾名思义就是用来装载对象的器具,在Tomcat里,容器就是用了装载servlet的。tomcat通过分层架构,使得Servlet容器具有很好的灵活性。Tomcat设计了四种容器,分别是Engine、Host、Context和Wrapper。这四种容器并不是平行关系,而是父子关系。</p><ul><li><p><strong>Engine</strong>:引擎,Servlet的顶层容器,用来管理多个虚拟站点,一个Service最多只能有一个Engine</p></li><li><p><strong>Host</strong>:虚拟主机,负责web应用的部署和context的创建。可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机可以部署多个Web应用程序</p></li><li><p><strong>Context</strong>:Web应用程序上下文,包含多个Wrapper,负责Web配置的解析,管理所有的Web资源。一个Context对应一个Web应用程序</p></li><li><p><strong>Wrapper</strong>:表示一个Servlet,最底层的容器,是对Servlet的封装,负责Servlet实例的创建、执行和销毁</p></li></ul><img width="289" height="290" alt="image" src="https://github.com/user-attachments/assets/7abb6b51-c53a-4f70-b8b1-90947b6fe8d0" /><h2 id="3-2-请求定位Servlet的过程"><a href="#3-2-请求定位Servlet的过程" class="headerlink" title="3.2 请求定位Servlet的过程"></a>3.2 请求定位Servlet的过程</h2><p>网络请求先到达 Connector,也就是连接器,它负责监听端口,接收 HTTP 请求。然后,Connector 会把请求封装成 Request 对象,可能还有 Response 对象,然后传递给 Engine 处理。接下来,Engine 会根据请求的 Host 头找到对应的 Host 虚拟主机。Host 再根据请求的上下文路径(Context Path)找到对应的 Context,也就是 Web 应用。然后,Context 内部需要根据请求的 Servlet 路径(Servlet Path)来匹配对应的 Servlet。这里可能涉及到 Web 应用的 web.xml 配置或者注解定义的 Servlet 映射。</p><h1 id="4-Tomcat架构设计"><a href="#4-Tomcat架构设计" class="headerlink" title="4. Tomcat架构设计"></a>4. Tomcat架构设计</h1><h2 id="4-1-Connector高内聚低耦合设计"><a href="#4-1-Connector高内聚低耦合设计" class="headerlink" title="4.1 Connector高内聚低耦合设计"></a>4.1 Connector高内聚低耦合设计</h2><p>Tomcat连接器需要实现的功能:</p><ul><li><p>监听网络端口</p></li><li><p>接受网络连接请求</p></li><li><p>读取请求网络字节流</p></li><li><p>根据具体应用层协议解析字节流,生成统一的TomcatRequest对象</p></li><li><p>将TomcatRequest对象转化成ServletRequest对象</p></li><li><p>调用Servlet容器,得到ServletResponse对象</p></li><li><p>将ServletResponse对象转化成TomcatResponse对象</p></li><li><p>将TomcatResponse转成网络字节流</p></li><li><p>将响应字节流写回浏览器</p></li></ul><p>通过连接器需要实现的功能列表,会发现连接器需要实现3个高内聚的功能:</p><ul><li><p>网络通信</p></li><li><p>应用层协议解析</p></li><li><p>Tomcat Request/Response 与 Servlet Request/Response相互转化</p></li></ul><h3 id="ProtocolHandler"><a href="#ProtocolHandler" class="headerlink" title="ProtocolHandler"></a>ProtocolHandler</h3><blockquote><p>因此Tomcat设计者设计了3个组件来实现这3个核心功能,分别是EndPoint、Processor和Adapter</p></blockquote><ul><li><p><strong>Endpoint:</strong>负责提供字节流给Processor</p></li><li><p><strong>Processor:</strong>负责提供TomcatRequest对象给Adapter</p></li><li><p><strong>Adapter</strong>:负责提供ServletRequest对象给容器</p></li></ul><blockquote><p>组件之间通过抽象接口交互,这样的好处是封装变化。这是面向对象设计的精髓,将系统中经常变化的部分和稳定的部分隔离,有助于增加系统的复用性,并降低系统耦合度</p></blockquote><img width="4698" height="1430" alt="image" src="https://github.com/user-attachments/assets/78713cf6-af9e-4b54-9133-bf2cd6f8e859" /><p>由于IO模型和应用层协议之间可以自由组合,比如NIO+HTTP或者NIO2+NJP。Tomcat设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫做ProtocolHandler的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类,比如:Http11NioProtocol和AjpNioProtocol</p><p>除了这些变化点,系统也存在一些相对稳定的部分,因此tomcat设计了一系列抽象基类来封装这些稳定的部分,抽象基类AbstractProtocol实现类ProtocolHandler接口。每一种应用层协议有自己的抽象基类,比如AbstractAjpProtocol和AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类。</p><img width="821" height="468" alt="image" src="https://github.com/user-attachments/assets/ef9a7208-6550-4783-a507-72d1427b4e17" /><h4 id="Endpoint"><a href="#Endpoint" class="headerlink" title="Endpoint"></a>Endpoint</h4><p>endpoint是通信端点,即监听通信的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此EndPoint是用来实现TCP/IP协议的。endpoint是一个接口,对应的抽象实现类是AbstractEndpoint,其子类中,例如NioEndpoint和Nio2Endpoint中,有两个重要的子组件:Acceptor和SocketProcessor.其中Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里调用协议处理组件Processor进行处理,为了提高处理能力,SocketProcessor被提交到线程池来执行,而这个线程池叫做执行器(Executor)</p><h4 id="Processor"><a href="#Processor" class="headerlink" title="Processor"></a>Processor</h4><p>Processor用来实现HTTP/AJP协议,Processor接收来自Endpoint的Socket,读取字节流解析成TomcatRequset和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象,Processor是一个接口,定义了请求的处理等方法,它的抽象实现类AbstractProcessor对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有AJPProcessor\HTTP11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。EndPoint接收到Socket连接后,生成一个SocketProcessor任务提交到线程池去处理,SocketProcessor的Run方法会调用Processor组件去解析应用层协议,Processor通过解析生成Request对象后没回调有Adapter的Service方法。</p><img width="4950" height="1430" alt="image" src="https://github.com/user-attachments/assets/7a2eef46-6b54-4baf-abf2-0cf376bcbb12" /><h4 id="Adapter"><a href="#Adapter" class="headerlink" title="Adapter"></a><strong>Adapter</strong></h4><p>由于协议不同,客户端发过来的请求信息也不相同,Tomcat定义了自己的Request类来存放这些请求信息。ProtocolHandler接口负责解析请求并生成TomcatRequest类。但是这个Request对象不是标准的ServletRequest,也就是意味着,不能使用TomcatRequest作为参数来调用容器。Tomcat的设计者的解决方案是引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter的Service方法,传入的是TomcatRequest对象CoyoteAdapter负责将TomcatRequest和ServletRequest进行传化,再调用容器的Service方法。</p><p>设计复杂系统的思路:</p><p>首先分析需求,列出功能点,根据功能以及高内聚、低耦合的原则确定子模块,然后找出子模块中的变化点和不变点,用接口和抽象基类去封装不变点,再抽象基类中定义模板方法,让子类自行实现抽象方法,也就是具体子类去实现变化点。tomcat的父子容器组合模式设计,tomcat通过组合模式来管理这些容器。具体的实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象具有使用的一致性。</p><h2 id="4-2-Pipeline-Valve责任链模式设计"><a href="#4-2-Pipeline-Valve责任链模式设计" class="headerlink" title="4.2 Pipeline-Valve责任链模式设计"></a>4.2 Pipeline-Valve责任链模式设计</h2><p>连接器中的Adapter会调用容器的Service方法来执行Servlet,最先拿到请求的是Engine容器,Engine容器对请求做一些处理后,会把请求传给自己子容器Host继续处理,依次类推,最后这个请求会传给Wrapper容器,Wrapper会调用最终的Servlet处理,这个过程中使用了Pipeline-Valve管道责任链模式</p><p>这种设计模式是指再一个请求处理的过程中有很多处理者一次对请求进行处理,每个处理者负责做自己相应的处理,处理完成之后再调用下一个处理者继续处理。</p><img width="3620" height="2316" alt="image" src="https://github.com/user-attachments/assets/c8d0c35e-e97e-46ba-8687-e1fe0cce5e82" /><h2 id="4-3-Tomcat生命周期设计"><a href="#4-3-Tomcat生命周期设计" class="headerlink" title="4.3 Tomcat生命周期设计"></a>4.3 Tomcat生命周期设计</h2><p>通过对Tomcat架构的分析,我们知道了Tomcat都有哪些组件,以及组件之间的关系,处理http请求的流程,如果想让tomcat能够对外提供服务,我们需要创建、组装并启动Tomcat组件,在服务停止的时候,我们还需要释放资源,销毁Tomcat组件,这是一个动态的过程。Tomcat需要动态地管理这些组件的生命周期。</p><h3 id="一键式启停:LifeCycle接口"><a href="#一键式启停:LifeCycle接口" class="headerlink" title="一键式启停:LifeCycle接口"></a>一键式启停:LifeCycle接口</h3><p>Tomcat 组件层级复杂(如 <code>Server → Service → Engine → Host → Context</code>),若每个组件各自实现启停逻辑,会导致:</p><ul><li><p>组件间依赖混乱(如必须先启动 <code>Connector</code> 才能接收请求,但 <code>Connector</code> 依赖 <code>Service</code> 初始化);</p></li><li><p>无法统一控制(启动 / 停止操作需逐个处理组件,无法 “一键操作”)。</p></li></ul><p><code>LifeCycle</code> 接口通过以下方式解决问题:</p><ol><li><p><strong>统一生命周期方法</strong>:定义所有组件必须实现的初始化、启动、停止、销毁方法;</p></li><li><p><strong>状态管理</strong>:规范组件的生命周期状态(如 “未初始化”“启动中”“已启动” 等)及状态转换规则;</p></li><li><p><strong>事件监听</strong>:支持通过监听器感知组件状态变化,实现扩展逻辑(如启动前初始化资源、停止后释放连接)。</p></li></ol><h4 id="1-核心方法(定义在-org-apache-catalina-Lifecycle-接口中)"><a href="#1-核心方法(定义在-org-apache-catalina-Lifecycle-接口中)" class="headerlink" title="1. 核心方法(定义在 org.apache.catalina.Lifecycle 接口中)"></a>1. 核心方法(定义在 <code>org.apache.catalina.Lifecycle</code> 接口中)</h4><h4 id="2-生命周期状态流转(关键状态)"><a href="#2-生命周期状态流转(关键状态)" class="headerlink" title="2. 生命周期状态流转(关键状态)"></a>2. 生命周期状态流转(关键状态)</h4><p>Tomcat 定义了严格的状态转换规则(通过 <code>LifecycleState</code> 枚举),确保组件按顺序执行生命周期操作,核心状态流转如下:</p><p>plaintext</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NEW(新建未初始化)→ INITIALIZING(初始化中)→ INITIALIZED(已初始化)</span><br><span class="line">→ STARTING_PREP(启动准备)→ STARTING(启动中)→ STARTED(已启动)</span><br><span class="line">→ STOPPING_PREP(停止准备)→ STOPPING(停止中)→ STOPPED(已停止)</span><br><span class="line">→ DESTROYING(销毁中)→ DESTROYED(已销毁)</span><br></pre></td></tr></table></figure><ul><li><p>每个状态转换只能通过特定方法触发(如 <code>start()</code> 触发从 <code>INITIALIZED</code> 到 <code>STARTED</code> 的转换);</p></li><li><p>状态转换不可逆(如 <code>STARTED</code> 不能直接回到 <code>INITIALIZED</code>,必须先 <code>stop()</code> 到 <code>STOPPED</code>)。</p></li></ul><h4 id="3-“一键式启停”-的实现逻辑"><a href="#3-“一键式启停”-的实现逻辑" class="headerlink" title="3. “一键式启停” 的实现逻辑"></a>3. “一键式启停” 的实现逻辑</h4><p><code>LifeCycle</code> 接口的层级联动机制,是 “一键式启停” 的核心:<strong>顶层组件的生命周期方法会自动触发所有子组件的对应方法</strong>。</p><p>以 Tomcat 启动为例(从 <code>Server</code> 到子组件):</p><ol><li><p>用户执行 <code>startup.sh</code> 或点击启动按钮,最终调用 <strong><code>Server</code> 的 <code>start()</code> 方法</strong>;</p></li><li><p><code>Server</code> 的 <code>start()</code> 会先执行自身启动逻辑,然后遍历所有子组件(<code>Service</code>),调用每个 <code>Service</code> 的 <code>start()</code> 方法;</p></li><li><p><code>Service</code> 的 <code>start()</code> 会启动自身,再调用子组件 <code>Engine</code> 和 <code>Connector</code> 的 <code>start()</code> 方法;</p></li><li><p><code>Engine</code> 的 <code>start()</code> 会启动自身,再调用子组件 <code>Host</code> 的 <code>start()</code> 方法;</p></li><li><p>以此类推,直到最底层的 <code>Context</code>(Web 应用)和 <code>Wrapper</code>(Servlet)启动完成。</p></li></ol><p>停止过程同理:调用 <code>Server</code> 的 <code>stop()</code> 方法,会逐级触发所有子组件的 <code>stop()</code> 方法,最终实现 “一键停止”。</p><h4 id="4-3-4-监听器机制:扩展生命周期行为"><a href="#4-3-4-监听器机制:扩展生命周期行为" class="headerlink" title="4.3.4 监听器机制:扩展生命周期行为"></a>4.3.4 监听器机制:扩展生命周期行为</h4><p><code>LifeCycle</code> 接口通过 <strong><code>LifecycleListener</code> 监听器</strong>支持扩展,允许在组件状态变化时执行自定义逻辑(如日志记录、资源预热)。</p><p>示例:给 <code>Host</code> 组件添加启动监听器,在 Host 启动后打印日志:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义监听器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HostStartListener</span> <span class="keyword">implements</span> <span class="title class_">LifecycleListener</span> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lifecycleEvent</span><span class="params">(LifecycleEvent event)</span> {</span><br><span class="line"> <span class="comment">// 当事件类型为“已启动”时触发</span></span><br><span class="line"> <span class="keyword">if</span> (Lifecycle.START_EVENT.equals(event.getType())) {</span><br><span class="line"> <span class="type">Host</span> <span class="variable">host</span> <span class="operator">=</span> (Host) event.getSource();</span><br><span class="line"> System.out.println(<span class="string">"Host "</span> + host.getName() + <span class="string">" 已启动,准备处理请求"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在 Host 组件中注册监听器(通常在 server.xml 或代码中配置)</span></span><br><span class="line"><span class="type">Host</span> <span class="variable">host</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StandardHost</span>();</span><br><span class="line">host.addLifecycleListener(<span class="keyword">new</span> <span class="title class_">HostStartListener</span>());</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="1-Tomcat是什么?"><a href="#1-Tomcat是什么?" class="headerlink" title="1. Tomcat是什么?"></a>1. Tomcat是什么?</h1><p>Tomcat 是一款<strong>开源的 Java W</summary>
<category term="Java Web" scheme="https://icarus-blog.top/categories/Java-Web/"/>
<category term="中间件" scheme="https://icarus-blog.top/categories/Java-Web/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="Tomcat" scheme="https://icarus-blog.top/categories/Java-Web/%E4%B8%AD%E9%97%B4%E4%BB%B6/Tomcat/"/>
<category term="Tomcat" scheme="https://icarus-blog.top/tags/Tomcat/"/>
<category term="Tomcat 架构" scheme="https://icarus-blog.top/tags/Tomcat-%E6%9E%B6%E6%9E%84/"/>
<category term="Servlet 容器" scheme="https://icarus-blog.top/tags/Servlet-%E5%AE%B9%E5%99%A8/"/>
<category term="Pipeline-Valve 模式" scheme="https://icarus-blog.top/tags/Pipeline-Valve-%E6%A8%A1%E5%BC%8F/"/>
<category term="LifeCycle 接口" scheme="https://icarus-blog.top/tags/LifeCycle-%E6%8E%A5%E5%8F%A3/"/>
<category term="Tomcat 源码分析" scheme="https://icarus-blog.top/tags/Tomcat-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<category term="Web 应用部署" scheme="https://icarus-blog.top/tags/Web-%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2/"/>
</entry>
<entry>
<title>G1 垃圾收集器学习笔记</title>
<link href="https://icarus-blog.top/2025/09/22/G1%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/"/>
<id>https://icarus-blog.top/2025/09/22/G1%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8/</id>
<published>2025-09-22T08:00:00.000Z</published>
<updated>2025-12-11T06:48:10.627Z</updated>
<content type="html"><![CDATA[<h1 id="G1垃圾收集器学习笔记"><a href="#G1垃圾收集器学习笔记" class="headerlink" title="G1垃圾收集器学习笔记"></a>G1垃圾收集器学习笔记</h1><h2 id="2025-09-23"><a href="#2025-09-23" class="headerlink" title="2025-09-23"></a>2025-09-23</h2><h2 id="一、G1垃圾收集器概述"><a href="#一、G1垃圾收集器概述" class="headerlink" title="一、G1垃圾收集器概述"></a>一、G1垃圾收集器概述</h2><p>G1(Garbage-First)是Java 9默认的垃圾收集器,核心特性如下: </p><ul><li><strong>取消物理分代</strong>:将内存划分为2048个逻辑<code>Region</code>(默认值) </li><li><strong>动态分区管理</strong>:年轻代/老年代区域可相互转化 </li><li><strong>巨型对象处理</strong>:新增<code>Humongous</code>区存储超Region 50%的大对象</li></ul><h2 id="二、内存管理机制"><a href="#二、内存管理机制" class="headerlink" title="二、内存管理机制"></a>二、内存管理机制</h2><h3 id="1-内存分区"><a href="#1-内存分区" class="headerlink" title="1. 内存分区"></a>1. 内存分区</h3><ul><li><strong>逻辑分区</strong>:保留年轻代(Eden/Survivor)、老年代概念 </li><li><strong>物理结构</strong>: <ul><li>默认2048个Region(通过<code>-XX:G1HeapRegionSize</code>调整) </li><li>年轻代初始占比5%(动态调整,上限60%) </li><li>Eden:Survivor默认比例8:1:1</li></ul></li></ul><h3 id="2-特殊区域"><a href="#2-特殊区域" class="headerlink" title="2. 特殊区域"></a>2. 特殊区域</h3><ul><li><strong>Humongous区</strong>: <ul><li>存储超过Region 50%的大对象 </li><li>若对象超过单个Region大小,会被分割存储于多个连续Region</li></ul></li></ul><h2 id="三、垃圾回收流程"><a href="#三、垃圾回收流程" class="headerlink" title="三、垃圾回收流程"></a>三、垃圾回收流程</h2><p>G1回收分为四个阶段: </p><ol><li><strong>初始标记</strong>(Stop The World) <ul><li>标记GC Roots直接引用对象</li></ul></li><li><strong>并发标记</strong>(Concurrent) <ul><li>与工作线程并行标记存活对象</li></ul></li><li><strong>最终标记</strong>(Stop The World) <ul><li>修正并发阶段的增量引用</li></ul></li><li><strong>筛选回收</strong>(Concurrent) <ul><li>按回收价值排序Region </li><li>执行复制算法整理内存</li></ul></li></ol><h2 id="四、核心特性"><a href="#四、核心特性" class="headerlink" title="四、核心特性"></a>四、核心特性</h2><ol><li><strong>并行与并发</strong> <ul><li>多核CPU缩短停顿时间</li></ul></li><li><strong>分代抽象管理</strong> <ul><li>逻辑分代替代物理分区</li></ul></li><li><strong>空间整合</strong> <ul><li>整体标记整理 + 局部复制算法 </li><li>减少内存碎片</li></ul></li></ol><h2 id="五、垃圾收集分类"><a href="#五、垃圾收集分类" class="headerlink" title="五、垃圾收集分类"></a>五、垃圾收集分类</h2><h3 id="1-YoungGC(年轻代回收)"><a href="#1-YoungGC(年轻代回收)" class="headerlink" title="1. YoungGC(年轻代回收)"></a>1. YoungGC(年轻代回收)</h3><ul><li><strong>触发条件</strong>: <ul><li>伊甸区填满时预估回收时间: <ul><li>若预估时间 < <code>MaxGCPauseMillis</code> → 新增年轻代Region </li><li>若预估时间 ≈ <code>MaxGCPauseMillis</code> → 触发回收</li></ul></li></ul></li><li><strong>执行逻辑</strong>: <ul><li>回收所有年轻代Region </li><li>存活对象晋升至老年代/Survivor区</li></ul></li></ul><h3 id="2-MixedGC(混合回收)"><a href="#2-MixedGC(混合回收)" class="headerlink" title="2. MixedGC(混合回收)"></a>2. MixedGC(混合回收)</h3><ul><li><strong>触发条件</strong>: <ul><li>老年代占用率超过阈值(<code>-XX:InitiatingHeapOccupancyPercent</code>)</li></ul></li><li><strong>执行逻辑</strong>: <ul><li>回收所有年轻代 + 部分老年代Region(非全部老年代) </li><li>具体回收数量由回收价值决定</li></ul></li></ul><h3 id="3-Full-GC(负GC)"><a href="#3-Full-GC(负GC)" class="headerlink" title="3. Full GC(负GC)"></a>3. Full GC(负GC)</h3><ul><li><strong>触发条件</strong>: <ul><li>内存不足(如MixedGC无法完成回收)</li></ul></li><li><strong>执行逻辑</strong>: <ul><li>单线程标记-清理-压缩内存 </li><li>现代JVM(Java 11+)通过<code>-XX:+UseG1GC -XX:-UseAdaptiveSizePolicy</code>可减少Full GC</li></ul></li></ul><h2 id="六、关键参数"><a href="#六、关键参数" class="headerlink" title="六、关键参数"></a>六、关键参数</h2><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td><code>-XX:MaxGCPauseMillis</code></td><td>目标最大停顿时间(默认200ms)</td></tr><tr><td><code>-XX:G1HeapRegionSize</code></td><td>Region大小(推荐1MB-32MB)</td></tr><tr><td><code>-XX:InitiatingHeapOccupancyPercent</code></td><td>老年代触发并发回收阈值(默认45%)</td></tr></tbody></table><h2 id="七、注意事项"><a href="#七、注意事项" class="headerlink" title="七、注意事项"></a>七、注意事项</h2><ul><li>过度追求低停顿可能导致: <ul><li>垃圾堆积 → 负GC → 回收成本上升</li></ul></li><li>生产环境建议保持默认参数配置 </li><li>通过<code>-XX:+PrintGCDetails -XX:+PrintGCDateStamps</code>监控GC行为</li></ul><h2 id="八、其他相关回收器"><a href="#八、其他相关回收器" class="headerlink" title="八、其他相关回收器"></a>八、其他相关回收器</h2><ul><li><strong>ZGC</strong>(JDK 11引入的低延迟垃圾收集器): <ul><li>支持TB级内存 </li><li>停顿时间<10ms </li><li>基于Region的着色指针技术</li></ul></li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm">G1官方文档</a> </li><li><a href="https://docs.oracle.com/javase/11/gctuning/z-garbage-collector.htm">ZGC官方文档</a> </li><li>《深入理解Java虚拟机》第三版(周志明)</li></ul>]]></content>
<summary type="html"><h1 id="G1垃圾收集器学习笔记"><a href="#G1垃圾收集器学习笔记" class="headerlink" title="G1垃圾收集器学习笔记"></a>G1垃圾收集器学习笔记</h1><h2 id="2025-09-23"><a href="#2025-09</summary>
<category term="Java" scheme="https://icarus-blog.top/categories/Java/"/>
<category term="JVM" scheme="https://icarus-blog.top/categories/Java/JVM/"/>
<category term="垃圾收集器" scheme="https://icarus-blog.top/categories/Java/JVM/%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8/"/>
<category term="G1 垃圾收集器" scheme="https://icarus-blog.top/tags/G1-%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8/"/>
<category term="JVM" scheme="https://icarus-blog.top/tags/JVM/"/>
<category term="Java 虚拟机" scheme="https://icarus-blog.top/tags/Java-%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
<category term="垃圾回收机制" scheme="https://icarus-blog.top/tags/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/"/>
</entry>
<entry>
<title>Seata AT 模式与 XA 模式的区别</title>
<link href="https://icarus-blog.top/2025/09/19/SEATA%E6%BA%90%E7%A0%81%EF%BC%9AAT%E4%B8%8EXA%E6%A8%A1%E5%BC%8F%E5%AD%A6%E4%B9%A0/"/>
<id>https://icarus-blog.top/2025/09/19/SEATA%E6%BA%90%E7%A0%81%EF%BC%9AAT%E4%B8%8EXA%E6%A8%A1%E5%BC%8F%E5%AD%A6%E4%B9%A0/</id>
<published>2025-09-19T07:00:00.000Z</published>
<updated>2025-12-11T06:48:10.628Z</updated>
<content type="html"><![CDATA[<h2 id="1-模式区别"><a href="#1-模式区别" class="headerlink" title="1. 模式区别"></a>1. 模式区别</h2><ul><li><strong>AT 模式</strong>:生成的是数据源代理(DataSource Proxy),通过代理拦截 SQL 执行,无需额外配置默认模式(Seata 默认支持 AT 模式),而非“数据库动态代理”(表述更精准)。</li><li><strong>XA 模式</strong>:生成的是 XA 数据源代理(XADataSource Proxy),需在 YAML 中显式配置 <code>seata.tx-mode=XA</code>(而非仅“配置默认模式为 XA”),且依赖数据库原生 XA 协议支持(如 MySQL、Oracle 的 XA 事务能力)。</li></ul><h2 id="2-AT-模式流程"><a href="#2-AT-模式流程" class="headerlink" title="2. AT 模式流程"></a>2. AT 模式流程</h2><ul><li>第一步会申请<strong>全局事务 ID(XID)</strong>(需明确“全局”,与分支事务 ID 区分)。</li><li>基于原有分布式服务调用逻辑扩展,实现异常后的<strong>自动回滚</strong>(而非“自动补偿”)——补偿逻辑通过 Undo Log 反向生成 SQL 实现,需明确概念。</li><li><strong>事务开始前</strong>:申请 XID 并存储到 ThreadLocal(Seata 内部通过 <code>RootContext</code> 管理 XID,MDC 通常用于日志打印携带 XID,非核心存储位置,修正存储载体);往 TC(事务协调器)的全局事务表(global_table)插入记录;将数据库连接的 Auto Commit 改为 <code>false</code>(手动提交)。</li><li><strong>SQL 执行前</strong>:仅对<strong>写操作(insert/update/delete)</strong> 分析语法并生成前置镜像(查询操作不会触发镜像生成,也不会拼接 <code>for update</code>——<code>for update</code> 是用户业务 SQL 自主使用的行锁语法,AT 模式通过全局锁控制并发,修正“查询加锁”错误)。</li><li><strong>SQL 执行后</strong>:对写操作获取后置镜像(基于主键/唯一键查询,确保与前置镜像对应),此时事务未提交。</li><li><strong>后续操作</strong>:将前置/后置镜像、Undo Log 写入本地数据库的 undo_log 表(而非“Seata 的 TC 表”,TC 不存储镜像,仅管理事务状态);向 TC 注册分支事务,TC 分配分支事务 ID(Branch ID),并将分支事务与全局事务关联;同时获取全局锁(往 TC 的 lock_table 插入记录,键为“表名+主键”,而非“事务 ID 作为标识”,修正锁标识逻辑)。</li><li><strong>事务提交/回滚</strong>:<ul><li>若正常提交:TC 通知各分支删除 Undo Log,释放全局锁;</li><li>若出现异常:TC 通知各分支执行 Undo Log 回滚(反向 SQL 恢复数据),之后删除 Undo Log 并释放锁;</li><li>后置处理:清除 <code>RootContext</code> 中的 XID,释放数据库连接等资源(修正“清除 XID”的载体)。</li></ul></li></ul><h2 id="3-XID-传递"><a href="#3-XID-传递" class="headerlink" title="3. XID 传递"></a>3. XID 传递</h2><ul><li>微服务间调用时,发起方从 <code>RootContext</code>(而非仅 MDC)获取 XID,通过 HTTP 请求头(默认键为 <code>TX_XID</code>)传递给接收方;</li><li>接收方通过 Spring MVC 拦截器/Feign 拦截器解析请求头中的 <code>TX_XID</code>,并绑定到自身的 <code>RootContext</code> 中,从而实现 XID 跨服务传递(MDC 仅用于日志携带 XID,方便排查,非传递核心逻辑)。</li></ul><h2 id="4-其他要点"><a href="#4-其他要点" class="headerlink" title="4. 其他要点"></a>4. 其他要点</h2><ul><li><strong>AT 模式的全局锁</strong>:获取时往 TC 的 <code>lock_table</code> 插入记录,键为“<code>resource_id</code>(数据源标识)+表名+主键”(而非“事务 ID 作为标识”,事务 ID 用于关联全局事务,锁键才是唯一标识);插入失败时会阻塞并重试(默认重试次数可配置),避免并发写冲突。</li><li><strong>AT 模式的默认隔离级别</strong>:读已提交(Read Committed),但通过“全局锁+本地锁”的协同,可避免“脏写”,并在一定程度上缓解“不可重复读”(需补充隔离级别的实际效果)。</li><li><strong>取消全局事务绑定</strong>:若某服务不想参与全局事务,需在调用下游服务前执行 <code>RootContext.unbind()</code>(解除当前 XID 绑定),下游服务将不会注册分支事务;若后续需重新参与,需再次绑定 XID(修正“调用下游前再重新绑定”的顺序逻辑)。</li></ul>]]></content>
<summary type="html"><h2 id="1-模式区别"><a href="#1-模式区别" class="headerlink" title="1. 模式区别"></a>1. 模式区别</h2><ul>
<li><strong>AT 模式</strong>:生成的是数据源代理(DataSource Pr</summary>
<category term="分布式系统" scheme="https://icarus-blog.top/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/"/>
<category term="中间件" scheme="https://icarus-blog.top/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="Seata" scheme="https://icarus-blog.top/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/%E4%B8%AD%E9%97%B4%E4%BB%B6/Seata/"/>
<category term="Seata" scheme="https://icarus-blog.top/tags/Seata/"/>
<category term="AT模式" scheme="https://icarus-blog.top/tags/AT%E6%A8%A1%E5%BC%8F/"/>
<category term="XA模式" scheme="https://icarus-blog.top/tags/XA%E6%A8%A1%E5%BC%8F/"/>
<category term="分布式事务" scheme="https://icarus-blog.top/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/"/>
<category term="全局事务ID" scheme="https://icarus-blog.top/tags/%E5%85%A8%E5%B1%80%E4%BA%8B%E5%8A%A1ID/"/>
<category term="Undo Log" scheme="https://icarus-blog.top/tags/Undo-Log/"/>
<category term="事务协调器" scheme="https://icarus-blog.top/tags/%E4%BA%8B%E5%8A%A1%E5%8D%8F%E8%B0%83%E5%99%A8/"/>
</entry>
</feed>