-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
491 lines (260 loc) · 292 KB
/
atom.xml
File metadata and controls
491 lines (260 loc) · 292 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>YUYUYU's Blog</title>
<link href="/atom.xml" rel="self"/>
<link href="https://firecarrrr.github.io/"/>
<updated>2020-07-14T07:07:48.712Z</updated>
<id>https://firecarrrr.github.io/</id>
<author>
<name>YUYUYU</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Dubbo 集群容错与负载均衡</title>
<link href="https://firecarrrr.github.io/2020/07/13/Dubbo-%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E4%B8%8E%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/"/>
<id>https://firecarrrr.github.io/2020/07/13/Dubbo-集群容错与负载均衡/</id>
<published>2020-07-13T08:54:41.000Z</published>
<updated>2020-07-14T07:07:48.712Z</updated>
<content type="html"><![CDATA[<h1 id="Dubbo-容错策略"><a href="#Dubbo-容错策略" class="headerlink" title="Dubbo 容错策略"></a>Dubbo 容错策略</h1><p>在设计系统时,不光要考虑正常情况下,代码逻辑怎么走,还要考虑异常情况下,代码逻辑怎么走。当服务消费方调用服务提供方的服务出现错误时,Dubbo 提供了多种容错方案。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="Dubbo容错方案.png" alt="Dubbo 容错方案" title> </div> <div class="image-caption">Dubbo 容错方案</div> </figure><ul><li><strong>Failover</strong> 是 Dubbo 的默认容错模式,可以配置重试次数,通常用于读操作或者幂等的写操作。<strong>重试会导致接口延时增大,当下游服务器负载达到极限时,重试会加重下游服务器的负担。</strong></li><li><strong>Failfast</strong> 通常用在非幂等的接口调用上</li><li><strong>Failsafe</strong> 通常用在佛系调用场景,即不关心调用是否成功,并且不想抛异常影响外层调用。</li><li><strong>Failback</strong> 请求失败后,会自动记录在失败队列中,并由一个定时线程池定时重试,适用于一些异步或者最终一致性请求。</li><li><strong>Forking</strong> 适用于对实时性要求较高的调用,但会浪费更多的服务资源。可以通过 forks 设置最大并行数。</li><li><strong>Broadcast</strong> 不需要做负载均衡,通常用于服务状态更新后的广播。</li></ul><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="cluster.jpg" alt="img" title> </div> <div class="image-caption">img</div> </figure><h1 id="Dubbo-负载均衡策略"><a href="#Dubbo-负载均衡策略" class="headerlink" title="Dubbo 负载均衡策略"></a>Dubbo 负载均衡策略</h1><p>当服务提供方是集群时,为了避免大量请求一直集中在一个或者几个服务提供方机器上,从而导致这些机器的负载很高,甚至导致服务不可用,需要做一定的负载均衡策略</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="Dubbo负载均衡.png" alt="Dubbo 负载均衡" title> </div> <div class="image-caption">Dubbo 负载均衡</div> </figure><ul><li><strong>Random</strong> 默认负载均衡策略</li><li><strong>RoundRobin</strong> 轮询策略。会存在执行很慢的服务提供者堆积请求的问题,当很多新请求到达该机器后,由于之前的请求还没有处理完,会导致新的请求被堆积。新版本中使用了类似 Ngnix 的平滑轮询算法,改善了请求堆积的问题。</li><li><strong>LeastActive</strong> 最少活跃调用。在每个服务提供者里维护着一个活跃计数器,用来记录当前同时处理请求的个数,也就是并发处理任务的个数。这个值越小,说明当前服务提供者处理速度越快或者当前机器的负载越低。所以路由选择时,就选择这个活跃度最低的机器。如果活跃度相同,则随机选择一个。</li><li><strong>ConsistentHash</strong> 一致性 Hash,可以保证相同参数的请求总是发往同一个提供者。当某一台提供者宕机时,原本发往改提供者的请求,将基于虚拟节点平摊给其它提供者,这样不会引起剧烈变动。</li></ul><h2 id="RoundRobin-负载均衡的实现"><a href="#RoundRobin-负载均衡的实现" class="headerlink" title="RoundRobin 负载均衡的实现"></a>RoundRobin 负载均衡的实现</h2><p>RoundRobin 实现了平滑的轮询算法,这个算法的逻辑是:</p><ol><li>每次做负载均衡时,遍历所有可选节点(Invoker 列表)。对于每个 Invoker,让他的 current = current + weight。</li></ol><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="comment">// 考虑到并发场景下,某个 invoker 会被同时选中,current 表示节点被线程选中的权重总和</span></span><br><span class="line"><span class="comment">// 例:某个节点权重是 100,被 4 个线程同时选中,则变成 400</span></span><br><span class="line"><span class="keyword">private</span> AtomicLong current = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><ol start="2"><li>同时累加所有 Invoker 的 weight 到 totalWeight。</li><li>遍历完所有 Invoker 后,current 值最大的节点就是本次要选择的节点。把该节点的 current 值减去 totalWeight ,current = current - totalWeight。</li><li>回到 1 重复。</li></ol><h2 id="ConsistentHash-负载均衡的实现"><a href="#ConsistentHash-负载均衡的实现" class="headerlink" title="ConsistentHash 负载均衡的实现"></a>ConsistentHash 负载均衡的实现</h2><p>普通一致性 Hash 当节点较少时,可能由于散列不是很均匀,容易造成某些节点压力大(一致性 Hash 倾斜问题)。Dubbo 框架使用了优化过的 Ketama 一致性 Hash。这种算法会给每个真实节点创建多个虚拟节点,让节点环形上的分布更加均匀,后续调用也会随之更加均匀。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="consistent-hash-invoker.jpg" alt="img" title> </div> <div class="image-caption">img</div> </figure><p>上图中,相同颜色的节点,属于同一服务提供者。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜的问题。</p>]]></content>
<summary type="html">
<h1 id="Dubbo-容错策略"><a href="#Dubbo-容错策略" class="headerlink" title="Dubbo 容错策略"></a>Dubbo 容错策略</h1><p>在设计系统时,不光要考虑正常情况下,代码逻辑怎么走,还要考虑异常情况下,代码
</summary>
<category term="Distributed System" scheme="https://firecarrrr.github.io/categories/Distributed-System/"/>
<category term="Dubbo" scheme="https://firecarrrr.github.io/tags/Dubbo/"/>
</entry>
<entry>
<title>Spring 中的设计模式</title>
<link href="https://firecarrrr.github.io/2020/07/06/Spring-%E4%B8%AD%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<id>https://firecarrrr.github.io/2020/07/06/Spring-中的设计模式/</id>
<published>2020-07-06T09:26:44.000Z</published>
<updated>2020-07-06T09:26:44.558Z</updated>
<content type="html"><![CDATA[<h1 id="工厂模式"><a href="#工厂模式" class="headerlink" title="工厂模式"></a>工厂模式</h1><h2 id="简单工厂模式"><a href="#简单工厂模式" class="headerlink" title="简单工厂模式"></a>简单工厂模式</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestCaseFactory</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ITestCase <span class="title">createTestCase</span><span class="params">(String caseType)</span> </span>{</span><br><span class="line"> ITestCase <span class="keyword">case</span> = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span>(..){</span><br><span class="line"> <span class="keyword">case</span> = ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(..) {</span><br><span class="line"> <span class="keyword">case</span> = ...</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">case</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果这个对象是可复用的,可以做一下缓存。</p><p>Spring 的 BeanFactory 就是简单工厂模式的体现:</p><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">BeanFactory bf = <span class="keyword">new</span> ClassPathXmlApplicationContext(<span class="string">"spring.xml"</span>);</span><br><span class="line">User userBean = (User) bf.getBean(<span class="string">"userBean"</span>);</span><br></pre></td></tr></table></figure><h2 id="工厂方法模式"><a href="#工厂方法模式" class="headerlink" title="工厂方法模式"></a>工厂方法模式</h2><p>在简单工厂模式中,所有的逻辑判断、实例创建都是在工厂类中进行的。</p><p>如果实例的创建逻辑非常复杂,可以为不同的产品提供不同的工厂,不同的工厂生产不同的产品,每一个工厂都只对应一个相应的对象。</p><p>Spring 的 FactoryBean 就是这种思想的提现,FactoryBean 帮助实现复杂的 Bean 的实例化和初始化逻辑。调用容器的 getBean() 方法,返回的是 FactoryBean 的 getObject() 的结果。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">FactoryBean</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="function">T <span class="title">getObject</span><span class="params">()</span>;</span></span><br><span class="line"><span class="function"> Class<?> <span class="title">getObjectType</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">isSingleton</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h1><p>AOP 基于代理模式</p><h1 id="装饰器模式"><a href="#装饰器模式" class="headerlink" title="装饰器模式"></a>装饰器模式</h1><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IA</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">A</span> <span class="keyword">implements</span> <span class="title">IA</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">f</span><span class="params">()</span> </span>{ <span class="comment">// ....}</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="class"><span class="keyword">class</span> <span class="title">ADecorator</span> <span class="keyword">implements</span> <span class="title">IA</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> IA a;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ADecorator</span><span class="params">(IA a)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.a = a;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">f</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 功能增强</span></span><br><span class="line"> a.f();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">d</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 利用 A,实现 A 没有的功能</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>装饰器模式在 Java IO 中的应用:</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></pre></td><td class="code"><pre><span class="line">InputStream in = <span class="keyword">new</span> FileInputStream(<span class="string">"/user/wangzheng/test.txt"</span>);</span><br><span class="line">InputStream bin = <span class="keyword">new</span> BufferedInputStream(in);</span><br><span class="line"><span class="keyword">byte</span>[] data = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">128</span>];</span><br><span class="line"><span class="keyword">while</span> (bin.read(data) != -<span class="number">1</span>) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h1><p>适配器模式是用来做适配的,将原本不兼容的接口转换为可兼容的接口。</p><p>适配器模式有两种:</p><p>1:<strong>类适配器</strong></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></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="class"><span class="keyword">interface</span> <span class="title">ITarget</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">f1</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">f2</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">fc</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不兼容 ITarget 接口定义的类</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Adaptee</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fa</span><span class="params">()</span> </span>{ <span class="comment">//... }</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fb</span><span class="params">()</span> </span>{ <span class="comment">//... }</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fc</span><span class="params">()</span> </span>{ <span class="comment">//... } </span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 Adaptee 转化为兼容 ITarget 接口的类</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Adaptor</span> <span class="keyword">extends</span> <span class="title">Adaptee</span> <span class="keyword">implements</span> <span class="title">ITarget</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">f1</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.fa();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">f2</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// ...重新实现 f2() ...</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// fc() 不需要实现,直接继承自 Adaptee</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>类适配器是基于继承实现的。</p><p>2:<strong>对象适配器</strong></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></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="class"><span class="keyword">interface</span> <span class="title">ITarget</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">f1</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">f2</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">fc</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不兼容 ITarget 接口定义的类</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Adaptee</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fa</span><span class="params">()</span> </span>{ <span class="comment">//... }</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fb</span><span class="params">()</span> </span>{ <span class="comment">//... }</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fc</span><span class="params">()</span> </span>{ <span class="comment">//... } </span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Adaptor</span> <span class="keyword">implements</span> <span class="title">ITarget</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> Adaptee adaptee;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Adaptor</span><span class="params">(Adaptee adaptee)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.adaptee = adaptee;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">f1</span><span class="params">()</span> </span>{</span><br><span class="line"> adaptee.fa();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">f2</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 重新实现 f2() ...</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fc</span><span class="params">()</span> </span>{</span><br><span class="line"> adaptee.fc();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对象适配器是基于组合实现的。</p><h1 id="模板模式"><a href="#模板模式" class="headerlink" title="模板模式"></a>模板模式</h1><p>模板模式就是<strong>在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板模式可以让子类在不改变算法整体框架的情况下,重新定义算法中某些步骤。</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractClass</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">templateMethod</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> method1();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> method2();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">method1</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">method2</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的类中,templateMethod 方法定义了算法执行的流程,method1 和 method2 的实现推迟到了子类中。</p><p>HttpServlet 类是模板方法的一个例子,子类实现其中的 doGet() 和 doPost() 方法,HttpServlet 中的 service() 方法定义了流程的骨架。</p><h1 id="职责链模式"><a href="#职责链模式" class="headerlink" title="职责链模式"></a>职责链模式</h1>]]></content>
<summary type="html">
<h1 id="工厂模式"><a href="#工厂模式" class="headerlink" title="工厂模式"></a>工厂模式</h1><h2 id="简单工厂模式"><a href="#简单工厂模式" class="headerlink" title="简单工厂模
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
<category term="Spring" scheme="https://firecarrrr.github.io/tags/Spring/"/>
</entry>
<entry>
<title>分布式锁</title>
<link href="https://firecarrrr.github.io/2020/07/06/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/"/>
<id>https://firecarrrr.github.io/2020/07/06/分布式锁/</id>
<published>2020-07-06T09:26:03.000Z</published>
<updated>2020-07-06T09:27:44.482Z</updated>
<content type="html"><![CDATA[<h1 id="什么是分布式锁?"><a href="#什么是分布式锁?" class="headerlink" title="什么是分布式锁?"></a>什么是分布式锁?</h1><p>在分布式环境下,对共享资源的访问或者一些同步操作时需要分布式锁来协助的。</p><p>比如说假设一个场景,一个订单服务,允许一个用户一个商品只能下一单。订单服务有多个实例,两个并发的请求被负载均衡分配到了不同的实例上,这个两个实例同时创建订单,那么就会出现一个商品下了多单的情况。</p><p>这种情况,就需要在下单时加上分布式锁,防止其它实例同时下单。</p><h2 id="分布式锁服务应该具备的特点"><a href="#分布式锁服务应该具备的特点" class="headerlink" title="分布式锁服务应该具备的特点"></a>分布式锁服务应该具备的特点</h2><ul><li><strong>安全性</strong>:在任意时刻,只有一个客户端可以获得锁</li><li><strong>避免死锁</strong>:客户端最终一定可以获得锁,即使当前持有锁的客户端在释放锁之前崩溃或者网络不可达</li><li><strong>容错性</strong>:只要锁服务集群中的大部分节点存活,Client 就可以执行加锁操作</li></ul><h1 id="分布式锁服务的实现"><a href="#分布式锁服务的实现" class="headerlink" title="分布式锁服务的实现"></a>分布式锁服务的实现</h1><p>分布式锁服务,一般可以用 DB,Redis,ZooKeeper 等实现</p><h2 id="Redis-分布式锁服务"><a href="#Redis-分布式锁服务" class="headerlink" title="Redis 分布式锁服务"></a>Redis 分布式锁服务</h2><h3 id="单机方案"><a href="#单机方案" class="headerlink" title="单机方案"></a>单机方案</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SET resource_name my_random_value NX PX 30000</span><br></pre></td></tr></table></figure><p>这条命令在不存在这个 key 的情况下(NX 选项),会设置一个随机的 value 赋给这个 key,并设置一个超时时间 30000 ms</p><p>然后通过下面的算法来释放这个锁:</p><figure class="highlight lua"><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="keyword">if</span> redis.call(<span class="string">"get"</span>,KEYS[<span class="number">1</span>]) == ARGV[<span class="number">1</span>] <span class="keyword">then</span></span><br><span class="line"> <span class="keyword">return</span> redis.call(<span class="string">"del"</span>,KEYS[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>只有当 key 对应的 value 和客户端设置的随机值相等时,才去删除这个 key。这样可以防止一个客户端释放了另一个客户端申请的锁。(比如,Client A 获取了锁,然后阻塞在某个耗时操作上了,锁自动释放了。然后,B 获取了锁。A 此时醒过来要去释放锁,如果没有上述保证,是会出问题的)。</p><p>另外,这个方案还有其它问题:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="redis分布式锁.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><ul><li>假设 A 获取了锁</li><li>A 挂在了某些耗时操作上,比如一个外部的阻塞调用,或是 CPU 被别的进程吃满,或者碰上了 full gc,导致 A 花了超过平时几倍的时间</li><li>A 获取的锁超时,自动释放</li><li>B 获取锁并更新了资源</li><li>A 醒过来,也去更新了资源。于是,就是 B 的更新给冲掉了</li></ul><h3 id="RedLock-算法"><a href="#RedLock-算法" class="headerlink" title="RedLock 算法"></a>RedLock 算法</h3><p>假设有 N 个 redis 节点,这 N 个节点是独立的,没有任何主备关系和额外的协调系统。</p><p>当申请一个 key 时,客户端做以下操作:</p><ul><li>获取当前时间戳</li><li><strong>用同样的 key 和随机值</strong>,顺序的尝试从这 N 个节点上获取锁。获取锁应该设置一个超时时间,这个超时时间应该比锁自动释放的时间短很多,以防止某些节点挂掉的情况。</li><li>计算获取锁花费的时间(当前时间减去第一步的时间戳),<strong>只有当获取锁话费的时间比锁的有效时间短,并且在大多数节点都获取到锁的情况才能判断客户端获取到了锁。</strong></li><li>锁的有效时间变成了超时时间减去获取锁花费的时间</li><li><strong>如果客户端获取锁失败了,它会尝试在所以节点上解锁</strong></li></ul><h4 id="失败重试"><a href="#失败重试" class="headerlink" title="失败重试"></a>失败重试</h4><p>当客户端尝试获取锁失败的时候,应该隔一个小的 random delay 过后去重试。因为这个是 random delay,所以在一定程度上可以避免多个客户端同时尝试去获取锁的时候造成的 split brain 问题。(多个客户端同时去获取锁,假设一共 5 个节点,一个获取到 2 个,一个获取到 2 个,一个获取到 1 个,最后谁都没有获取到锁)。</p><p>客户端越快的获取多数节点上的锁,split brain 发生的时间窗口就越小,所以理想情况下客户端应该利用多线程技术,同时将 SET 请求发送到 N 个实例上。</p><p>获取锁失败的客户端立即释放所有节点上的锁是非常重要的,不然其它线程就必须等到这个 key 超时之后才能获取这个节点上的锁了。</p><p>当某个客户端出现网络分区的时候,那就只能等那个客户端设置的锁自动超时了。</p><h2 id="DB-实现分布式锁"><a href="#DB-实现分布式锁" class="headerlink" title="DB 实现分布式锁"></a>DB 实现分布式锁</h2><h3 id="基于唯一索引的实现"><a href="#基于唯一索引的实现" class="headerlink" title="基于唯一索引的实现"></a>基于唯一索引的实现</h3><p>数据库里面加一张表,设置关于资源的唯一索引。</p><p>获取锁,就是插入一条数据,如果已经有相应资源的记录,那就会抛出异常。</p><p>锁释放就是删除对应资源。</p><p>问题:</p><ul><li>加锁线程挂了,没办法解锁</li><li>非可重入锁,同一个线程没有解锁,没办法重入</li></ul><h2 id="Zookeeper-实现分布式锁"><a href="#Zookeeper-实现分布式锁" class="headerlink" title="Zookeeper 实现分布式锁"></a>Zookeeper 实现分布式锁</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="zookeeper分布式锁.png" alt="image-20200630224052012" title> </div> <div class="image-caption">image-20200630224052012</div> </figure><ul><li>创建的节点必须是临时节点</li><li>对每个资源,节点的名字必须是唯一的</li></ul>]]></content>
<summary type="html">
<h1 id="什么是分布式锁?"><a href="#什么是分布式锁?" class="headerlink" title="什么是分布式锁?"></a>什么是分布式锁?</h1><p>在分布式环境下,对共享资源的访问或者一些同步操作时需要分布式锁来协助的。</p>
<p>比如
</summary>
<category term="Distributed System" scheme="https://firecarrrr.github.io/categories/Distributed-System/"/>
</entry>
<entry>
<title>redis sentinel</title>
<link href="https://firecarrrr.github.io/2020/07/05/redis-sentinel/"/>
<id>https://firecarrrr.github.io/2020/07/05/redis-sentinel/</id>
<published>2020-07-05T09:10:36.000Z</published>
<updated>2020-07-05T09:11:39.691Z</updated>
<content type="html"><![CDATA[<h1 id="什么是-Sentinel?"><a href="#什么是-Sentinel?" class="headerlink" title="什么是 Sentinel?"></a>什么是 Sentinel?</h1><p>Sentinel(哨兵)是 redis 高可用的一个解决方案。sentinel 本质上是运行在特殊状态下的 redis 服务器。</p><p>由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器。在被监视主服务器进入下线状态时,自动将下线的主服务器属下的某个从服务器升级为主服务器,然后由新的主服务器代替已下线的主服务器处理命令请求。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="sentinel.png" alt="image-20200705143153699" title> </div> <div class="image-caption">image-20200705143153699</div> </figure><h1 id="故障转移的过程"><a href="#故障转移的过程" class="headerlink" title="故障转移的过程"></a>故障转移的过程</h1><ol><li>当 master 服务器客观下线后,leader sentinel 开始对 master 服务器进行故障转移操作。</li><li>sentinel 从所有从服务其中选举一个从服务器,被选中的从服务器被升级为新的主服务器。</li><li>sentinel 向原 master 服务器所有的从服务器发送新的复制命令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移完毕。</li><li>sentinel 还会建设已下线的 master 服务器,并在它重新上线时,将它设置为新的主服务器的从服务器。</li></ol><h2 id="故障转移日志"><a href="#故障转移日志" class="headerlink" title="故障转移日志"></a>故障转移日志</h2><p>看一下 sentinel 的日志</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="log.png" alt="image-20200705155037719" title> </div> <div class="image-caption">image-20200705155037719</div> </figure><ul><li>首先,sentinel 注意到 master 节点挂了</li><li>sentinel 集群中的节点需要就主节点挂了这件事情达成共识,因为现在只有一个节点,所以是配的 quorum 1/1</li><li>达到故障转移的条件</li><li>在从节点中进行主节点的选举</li><li>选举除了新的主节点</li><li>在选举出的节点上执行 slave of none 命令,使之成为主节点</li><li>转化到新的主节点上</li></ul><h1 id="Sentinel-基本实现原理"><a href="#Sentinel-基本实现原理" class="headerlink" title="Sentinel 基本实现原理"></a>Sentinel 基本实现原理</h1><p>sentinel 会与被监视的主服务器和其从服务器建立两个连接:</p><ul><li>命令连接</li><li>订阅连接</li></ul><h2 id="获取主服务器信息"><a href="#获取主服务器信息" class="headerlink" title="获取主服务器信息"></a>获取主服务器信息</h2><p>sentinel 默认每 10s 向监视的主服务器发送 info 命令。并通过分析 info 命令的回复来获取主服务器当前的信息。</p><p>配置 sentinel 的时候并不需要配置从服务器信息,因为这是通过 info 命令自动发现的。</p><h2 id="获取从服务器信息"><a href="#获取从服务器信息" class="headerlink" title="获取从服务器信息"></a>获取从服务器信息</h2><p>当发现有新的从服务器出现时,Sentinel 会创建连接到从服务器的命令连接和订阅连接。</p><p>通过命令连接,sentinel 也会每 10s 向从服务器发送 info 命令。</p><h2 id="向主服务器和从服务器发送信息"><a href="#向主服务器和从服务器发送信息" class="headerlink" title="向主服务器和从服务器发送信息"></a>向主服务器和从服务器发送信息</h2><p>默认情况下,Sentinel 会以每 2s 一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送一条发布命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PUBLISH _sentinel_:hello "<s.ip>,<s.port>,<s.runid>,<s.epoch>,<m.name>,<m.ip>,<m.port>,<m.epoch>"</span><br></pre></td></tr></table></figure><p>s 开头的是 sentinel 的信息</p><p>m 开头的是 master 的信息</p><h2 id="接收来自主服务器和从服务器的频道信息"><a href="#接收来自主服务器和从服务器的频道信息" class="headerlink" title="接收来自主服务器和从服务器的频道信息"></a>接收来自主服务器和从服务器的频道信息</h2><p>当 sentinel 与一个主服务器和从服务器建立订阅连接之后,sentinel 就会通过订阅连接发送一下命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SUBSCRIBE _sentinel_:hello</span><br></pre></td></tr></table></figure><p>sentinel 对这个频道的订阅会持续到 sentinel 与服务器连接断开为止。</p><p>也就是说每个与 sentinel 连接的服务器,<strong>sentinel 既通过命令连接向服务器发送消息,又通过订阅连接从服务器接收消息 <em>sentinel</em>:hello 频道的消息</strong></p><p>对于监视一个服务器的多个 sentinel 来说,一个 sentinel 发送的消息会被其他 sentinel 收到,这些消息被用于更新其他 sentinel 对发送消息 sentinel 的认识,也用于更新其他 sentinel 对被监视服务器的认识。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="ps.png" alt="image-20200705164638275" title> </div> <div class="image-caption">image-20200705164638275</div> </figure><h2 id="创建连向其他-Sentinel-的命令连接"><a href="#创建连向其他-Sentinel-的命令连接" class="headerlink" title="创建连向其他 Sentinel 的命令连接"></a>创建连向其他 Sentinel 的命令连接</h2><p>当 sentinel 通过订阅频道发现一个新的 Sentinel 时,会创建一个连向新 sentinel 的命令连接。新 sentinel 也会创建连向这个 sentinel 的命令连接,最终监视同一主服务器的多个 sentinel 将形成相互连接的网络。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="连接.png" alt="image-20200705164508764" title> </div> <div class="image-caption">image-20200705164508764</div> </figure><h2 id="检测主观下线"><a href="#检测主观下线" class="headerlink" title="检测主观下线"></a>检测主观下线</h2><p>默认情况下,Sentinel 会以每秒一次的频率向所有它创建了命令连接的实例(包括主服务器、从服务器、其它 sentinel)发送 ping 命令,以此判断实例是否在线。</p><figure class="highlight shell"><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">sentinel down-after-milliseconds <master-name> <milliseconds></span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span> Number of milliseconds the master (or any attached replica or sentinel) should</span><br><span class="line"><span class="meta">#</span> be unreachable (as in, not acceptable reply to PING, continuously, for the</span><br><span class="line"><span class="meta">#</span> specified period) in order to consider it in S_DOWN state (Subjectively</span><br><span class="line"><span class="meta">#</span> Down).</span><br></pre></td></tr></table></figure><p>通过这个设置项设置判断节点下线的 timeout ,如果超过这个时间范围都没有收到有效回复就会判断节点主观下线。</p><h2 id="检查客观下线"><a href="#检查客观下线" class="headerlink" title="检查客观下线"></a>检查客观下线</h2><p>当 sentinel 将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,会向同样监视这一主服务器的其他 sentinel 进行询问。当 sentinel 从其他 sentinel 那里接收到足够数量的已下线判断后,sentinel 将会把这个服务器判定为客观下线,并开始进行故障转移。</p><figure class="highlight shell"><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">sentinel monitor <master-name> <ip> <redis-port> <quorum></span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span> Tells Sentinel to monitor this master, and to consider it in O_DOWN</span><br><span class="line"><span class="meta">#</span> (Objectively Down) state only if at least <quorum> sentinels agree.</span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span> Note that whatever is the ODOWN quorum, a Sentinel will require to</span><br><span class="line"><span class="meta">#</span> be elected by the majority of the known Sentinels in order to</span><br><span class="line"><span class="meta">#</span> start a failover, so no failover can be performed in minority.</span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span> Replicas are auto-discovered, so you don't need to specify replicas in</span><br><span class="line"><span class="meta">#</span> any way. Sentinel itself will rewrite this configuration file adding</span><br><span class="line"><span class="meta">#</span> the replicas using additional configuration options.</span><br><span class="line"><span class="meta">#</span> Also note that the configuration file is rewritten when a</span><br><span class="line"><span class="meta">#</span> replica is promoted to master.</span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span> Note: master name should not include special characters or spaces.</span><br><span class="line"><span class="meta">#</span> The valid charset is A-z 0-9 and the three characters ".-_".</span><br></pre></td></tr></table></figure><p>这个设置用于设置客观下线需要达成共识最低的票数。</p><h2 id="选举-leader-Sentinel"><a href="#选举-leader-Sentinel" class="headerlink" title="选举 leader Sentinel"></a>选举 leader Sentinel</h2><p>当一个主服务器被判定为客观下线时,监视这个下线主服务器的各个 sentinel 会进行协调,选出一个 leader sentinel,并由 leader sentinel 对下线主服务器执行故障转移操作。</p>]]></content>
<summary type="html">
<h1 id="什么是-Sentinel?"><a href="#什么是-Sentinel?" class="headerlink" title="什么是 Sentinel?"></a>什么是 Sentinel?</h1><p>Sentinel(哨兵)是 redis 高可用的一个
</summary>
<category term="redis" scheme="https://firecarrrr.github.io/tags/redis/"/>
</entry>
<entry>
<title>并发容器</title>
<link href="https://firecarrrr.github.io/2020/07/04/%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8/"/>
<id>https://firecarrrr.github.io/2020/07/04/并发容器/</id>
<published>2020-07-04T12:01:03.000Z</published>
<updated>2020-07-13T11:54:52.304Z</updated>
<content type="html"><![CDATA[<h1 id="JUC-中的并发容器"><a href="#JUC-中的并发容器" class="headerlink" title="JUC 中的并发容器"></a>JUC 中的并发容器</h1><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="并发容器.png" alt title> </div> <div class="image-caption"></div> </figure><h2 id="List"><a href="#List" class="headerlink" title="List"></a>List</h2><p>List 只有一个实现 —— CopyOnWriteArrayList</p><h3 id="COW"><a href="#COW" class="headerlink" title="COW"></a>COW</h3><p>CopyOnWrite 是一种并发编程的设计模式,即写时复制。当发生写操作时,线程不是在原共享资源上进行增删操作,而是生成一个共享资源的副本在副本上增删。</p><p>从 CopyOnWriteArrayList 的源码中可以看出,写操作加了锁,进行数组的复制操作,读操作完全是无锁的。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Appends the specified element to the end of this list.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> e element to be appended to this list</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> {<span class="doctag">@code</span> true} (as specified by {<span class="doctag">@link</span> Collection#add})</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> ReentrantLock lock = <span class="keyword">this</span>.lock;</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Object[] elements = getArray();</span><br><span class="line"> <span class="keyword">int</span> len = elements.length;</span><br><span class="line"> Object[] newElements = Arrays.copyOf(elements, len + <span class="number">1</span>);</span><br><span class="line"> newElements[len] = e;</span><br><span class="line"> setArray(newElements);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</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="comment">/**</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@inheritDoc</span>}</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IndexOutOfBoundsException {<span class="doctag">@inheritDoc</span>}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> get(getArray(), index);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>COW 的局限性:</strong></p><ul><li>COW 仅适用于读多写很少并且数据量不大的场景,否则资源复制会产生很大开销。</li></ul><h2 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h2><p>Map 有两个实现:</p><ul><li><strong>ConcurrentHashMap</strong></li><li><strong>ConcurrentSkipListMap</strong></li></ul><p>ConcurrentHashMap 的 key 是无序的,ConcurrentSkipListMap 的 key 是有序的</p><h3 id="ConcurrentHashMap"><a href="#ConcurrentHashMap" class="headerlink" title="ConcurrentHashMap"></a>ConcurrentHashMap</h3><h4 id="HashTable-的实现有什么问题?"><a href="#HashTable-的实现有什么问题?" class="headerlink" title="HashTable 的实现有什么问题?"></a>HashTable 的实现有什么问题?</h4><p>HashTable 只是一个简单的并发容器的实现,只是在 get、put 等方法上面加上 synchronized 关键字,这是粒度非常大的锁,几乎所有并发操作都变成串行的了。</p><h4 id="ConcurrentHashMap-的实现"><a href="#ConcurrentHashMap-的实现" class="headerlink" title="ConcurrentHashMap 的实现"></a>ConcurrentHashMap 的实现</h4><p><strong>在 JDK 7 中,ConcurrentHashMap 采用分段锁机制实现:</strong></p><ul><li>分段锁,内部进行分段(Segment),里面存放 HashEntry 的数组,hash 相同的条目也是以链表的形式存放</li></ul><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="concurrent.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><p>分段数量有 concurrencyLevel 决定,默认是 16,也可以在相应的构造函数中直接指定,但必须是 2 的幂次。</p><p><strong>分段锁的一个副作用</strong>:</p><p>在计算 size 时,如果不对所有 segment 进行同步,可能因为并发的 put 造成结果不准确。但是直接锁定所有 segment 进行计算,又会是计算的成本很高。</p><p>ConcurrentHashMap 的实现是通过重试机制(RETRIES_BEFORE_LOCK,指定重试次数 2),来试图获取可靠值。如果没有监控到发生变化(通过对比 Segment.modCount),就直接返回,否则获取锁进行操作。</p><p><strong>JDK 8 中的实现:</strong></p><ul><li>锁的粒度进一步降低,锁的是桶中的头元素</li><li>利用 CAS 等操作,在特定场景下进行无锁操作</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/** Implementation for put and putIfAbsent */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(K key, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (key == <span class="keyword">null</span> || value == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="keyword">int</span> hash = spread(key.hashCode());</span><br><span class="line"> <span class="keyword">int</span> binCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V>[] tab = table;;) {</span><br><span class="line"> Node<K,V> f; <span class="keyword">int</span> n, i, fh;</span><br><span class="line"> <span class="comment">// lazy-load 初始化桶数组</span></span><br><span class="line"> <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> tab = initTable();</span><br><span class="line"> <span class="comment">// 如果对应桶数据下标位置还没有元素</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i = (n - <span class="number">1</span>) & hash)) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 利用 CAS 指令,把这个值到对应桶下标处</span></span><br><span class="line"> <span class="keyword">if</span> (casTabAt(tab, i, <span class="keyword">null</span>,</span><br><span class="line"> <span class="keyword">new</span> Node<K,V>(hash, key, value, <span class="keyword">null</span>)))</span><br><span class="line"> <span class="keyword">break</span>; <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line"> tab = helpTransfer(tab, f);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> V oldVal = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 锁定那一行</span></span><br><span class="line"> <span class="keyword">synchronized</span> (f) {</span><br><span class="line"> <span class="keyword">if</span> (tabAt(tab, i) == f) {</span><br><span class="line"> <span class="keyword">if</span> (fh >= <span class="number">0</span>) {</span><br><span class="line"> binCount = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> e = f;; ++binCount) {</span><br><span class="line"> K ek;</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((ek = e.key) == key ||</span><br><span class="line"> (ek != <span class="keyword">null</span> && key.equals(ek)))) {</span><br><span class="line"> oldVal = e.val;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line"> e.val = value;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> Node<K,V> pred = e;</span><br><span class="line"> <span class="keyword">if</span> ((e = e.next) == <span class="keyword">null</span>) {</span><br><span class="line"> pred.next = <span class="keyword">new</span> Node<K,V>(hash, key,</span><br><span class="line"> value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) {</span><br><span class="line"> Node<K,V> p;</span><br><span class="line"> binCount = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">if</span> ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,</span><br><span class="line"> value)) != <span class="keyword">null</span>) {</span><br><span class="line"> oldVal = p.val;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line"> p.val = value;</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">if</span> (binCount != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD)</span><br><span class="line"> treeifyBin(tab, i);</span><br><span class="line"> <span class="keyword">if</span> (oldVal != <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> oldVal;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> addCount(<span class="number">1L</span>, binCount);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Initializes table, using the size recorded in sizeCtl.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Node<K,V>[] initTable() {</span><br><span class="line"> Node<K,V>[] tab; <span class="keyword">int</span> sc;</span><br><span class="line"> <span class="keyword">while</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// sizeCtl 是一个 volatile 的变量</span></span><br><span class="line"> <span class="comment">// 如果 -1 表示正在被初始化,说明当前线程竞争失败,其它线程已经在初始化容器了</span></span><br><span class="line"> <span class="keyword">if</span> ((sc = sizeCtl) < <span class="number">0</span>)</span><br><span class="line"> Thread.yield(); <span class="comment">// lost initialization race; just spin</span></span><br><span class="line"> <span class="comment">// 如果 CAS 修改 ctl 成功,进入初始化逻辑</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">int</span> n = (sc > <span class="number">0</span>) ? sc : DEFAULT_CAPACITY;</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> Node<K,V>[] nt = (Node<K,V>[])<span class="keyword">new</span> Node<?,?>[n];</span><br><span class="line"> table = tab = nt;</span><br><span class="line"> sc = n - (n >>> <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> sizeCtl = sc;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> tab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="ConcurrentSkipListMap"><a href="#ConcurrentSkipListMap" class="headerlink" title="ConcurrentSkipListMap"></a>ConcurrentSkipListMap</h3><p>基于跳表的实现,跳表的插入、删除、查询操作的平均时间复杂度都是 logn</p><h2 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h2><p>Set 有两个实现:</p><ul><li><strong>CopyOnWriteArraySet</strong></li><li><strong>ConcurrentSkipListSet</strong></li></ul><h2 id="Queue"><a href="#Queue" class="headerlink" title="Queue"></a>Queue</h2><p>Queue 的实现比较多,可以通过两个维度来区分:</p><ul><li>是否阻塞(队列已满时,入队操作是否阻塞;队列为空时出队操作是否阻塞)</li><li>单端还是双端</li></ul><p>单端阻塞:</p><ul><li>ArrayBlockingQueue:基于数组实现</li><li>LinkedBlockingQueue:基于链表实现</li><li>SynchronousQueue:内部没有队列的存储,每次 put 都要等待 take,每次 take 都要等待 put,也可以用于线程同步</li><li>LinkedTransferQueue</li><li>PriorityBlockingQueue:无界优先队列</li><li>DelayQueue</li></ul><p>双端阻塞:</p><ul><li>LinkedBlockingDeque</li></ul><p>单端非阻塞:</p><ul><li>ConcurrentLinkedQueue</li></ul><p>双端非阻塞:</p><ul><li>ConcurrentLinkedDeque</li></ul><h3 id="哪些是有界的,哪些是无界的?"><a href="#哪些是有界的,哪些是无界的?" class="headerlink" title="哪些是有界的,哪些是无界的?"></a>哪些是有界的,哪些是无界的?</h3><p><strong>只有 ArrayBlockingQueue 和 LinkedBlockingQueue 是支持有界的</strong></p><p>在使用其它无界队列时,一定要充分考虑是否存在 OOM 的隐患</p><h3 id="Blocking-和-Concurrent-实现上的区别?"><a href="#Blocking-和-Concurrent-实现上的区别?" class="headerlink" title="Blocking 和 Concurrent 实现上的区别?"></a>Blocking 和 Concurrent 实现上的区别?</h3><p>Blocking 阻塞队列,利用锁机制实现。例如 LinkedBlockingQueue 的实现中:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/** Lock held by take, poll, etc */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ReentrantLock takeLock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Wait queue for waiting takes */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Condition notEmpty = takeLock.newCondition();</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Lock held by put, offer, etc */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ReentrantLock putLock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Wait queue for waiting puts */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Condition notFull = putLock.newCondition();</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Inserts the specified element at the tail of this queue, waiting if</span></span><br><span class="line"><span class="comment"> * necessary for space to become available.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> InterruptedException {<span class="doctag">@inheritDoc</span>}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> NullPointerException {<span class="doctag">@inheritDoc</span>}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">put</span><span class="params">(E e)</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">if</span> (e == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="comment">// Note: convention in all put/take/etc is to preset local var</span></span><br><span class="line"> <span class="comment">// holding count negative to indicate failure unless set.</span></span><br><span class="line"> <span class="keyword">int</span> c = -<span class="number">1</span>;</span><br><span class="line"> Node<E> node = <span class="keyword">new</span> Node<E>(e);</span><br><span class="line"> <span class="keyword">final</span> ReentrantLock putLock = <span class="keyword">this</span>.putLock;</span><br><span class="line"> <span class="keyword">final</span> AtomicInteger count = <span class="keyword">this</span>.count;</span><br><span class="line"> putLock.lockInterruptibly();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Note that count is used in wait guard even though it is</span></span><br><span class="line"><span class="comment"> * not protected by lock. This works because count can</span></span><br><span class="line"><span class="comment"> * only decrease at this point (all other puts are shut</span></span><br><span class="line"><span class="comment"> * out by lock), and we (or some other waiting put) are</span></span><br><span class="line"><span class="comment"> * signalled if it ever changes from capacity. Similarly</span></span><br><span class="line"><span class="comment"> * for all other uses of count in other wait guards.</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">while</span> (count.get() == capacity) {</span><br><span class="line"> notFull.await();</span><br><span class="line"> }</span><br><span class="line"> enqueue(node);</span><br><span class="line"> c = count.getAndIncrement();</span><br><span class="line"> <span class="comment">// 唤醒等待的写线程</span></span><br><span class="line"> <span class="keyword">if</span> (c + <span class="number">1</span> < capacity)</span><br><span class="line"> notFull.signal();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> putLock.unlock();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>)</span><br><span class="line"> signalNotEmpty();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> E x;</span><br><span class="line"> <span class="keyword">int</span> c = -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">final</span> AtomicInteger count = <span class="keyword">this</span>.count;</span><br><span class="line"> <span class="keyword">final</span> ReentrantLock takeLock = <span class="keyword">this</span>.takeLock;</span><br><span class="line"> takeLock.lockInterruptibly();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 队列空的</span></span><br><span class="line"> <span class="keyword">while</span> (count.get() == <span class="number">0</span>) {</span><br><span class="line"> notEmpty.await();</span><br><span class="line"> }</span><br><span class="line"> x = dequeue();</span><br><span class="line"> c = count.getAndDecrement();</span><br><span class="line"> <span class="comment">// 唤醒等待的读线程</span></span><br><span class="line"> <span class="keyword">if</span> (c > <span class="number">1</span>)</span><br><span class="line"> notEmpty.signal();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> takeLock.unlock();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (c == capacity)</span><br><span class="line"> signalNotFull();</span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而 ConcurrentLinkedQueue 的实现中:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Inserts the specified element at the tail of this queue.</span></span><br><span class="line"><span class="comment"> * As the queue is unbounded, this method will never return {<span class="doctag">@code</span> false}.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> {<span class="doctag">@code</span> true} (as specified by {<span class="doctag">@link</span> Queue#offer})</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> NullPointerException if the specified element is null</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">offer</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> checkNotNull(e);</span><br><span class="line"> <span class="keyword">final</span> Node<E> newNode = <span class="keyword">new</span> Node<E>(e);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Node<E> t = tail, p = t;;) {</span><br><span class="line"> Node<E> q = p.next;</span><br><span class="line"> <span class="keyword">if</span> (q == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 如果 p 是最后一个节点,利用 cas 指令在 p 的后面加上新节点</span></span><br><span class="line"> <span class="keyword">if</span> (p.casNext(<span class="keyword">null</span>, newNode)) {</span><br><span class="line"> <span class="comment">// Successful CAS is the linearization point</span></span><br><span class="line"> <span class="comment">// for e to become an element of this queue,</span></span><br><span class="line"> <span class="comment">// and for newNode to become "live".</span></span><br><span class="line"> <span class="comment">// 改变尾节点</span></span><br><span class="line"> <span class="keyword">if</span> (p != t) <span class="comment">// hop two nodes at a time</span></span><br><span class="line"> casTail(t, newNode); <span class="comment">// Failure is OK.</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Lost CAS race to another thread; re-read next</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p == q)</span><br><span class="line"> <span class="comment">// We have fallen off list. If tail is unchanged, it</span></span><br><span class="line"> <span class="comment">// will also be off-list, in which case we need to</span></span><br><span class="line"> <span class="comment">// jump to head, from which all live nodes are always</span></span><br><span class="line"> <span class="comment">// reachable. Else the new tail is a better bet.</span></span><br><span class="line"> p = (t != (t = tail)) ? t : head;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// Check for tail updates after two hops.</span></span><br><span class="line"> p = (p != t && t != (t = tail)) ? t : q;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>ConcurrentLinkedQueue 是基于 CAS 的无锁技术实现的,性能更好。</p><h2 id="fail-fast-机制"><a href="#fail-fast-机制" class="headerlink" title="fail-fast 机制"></a>fail-fast 机制</h2><p>Concurrent 类提供的是较低的遍历一致性。当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历。</p>]]></content>
<summary type="html">
<h1 id="JUC-中的并发容器"><a href="#JUC-中的并发容器" class="headerlink" title="JUC 中的并发容器"></a>JUC 中的并发容器</h1><figure class="image-bubble">
</summary>
</entry>
<entry>
<title>AQS</title>
<link href="https://firecarrrr.github.io/2020/07/03/AQS/"/>
<id>https://firecarrrr.github.io/2020/07/03/AQS/</id>
<published>2020-07-03T07:33:29.000Z</published>
<updated>2020-07-03T07:34:49.386Z</updated>
<content type="html"><![CDATA[<h1 id="什么是-AQS"><a href="#什么是-AQS" class="headerlink" title="什么是 AQS?"></a>什么是 AQS?</h1><p><strong>AbstractQueuedSynchronizer</strong> 这个类是许多同步类的基类,是一个用于构建锁和同步器的框架。AQS 解决了在实现同步器时涉及的大量细节问题。 </p><h1 id="AQS-实现"><a href="#AQS-实现" class="headerlink" title="AQS 实现"></a>AQS 实现</h1><h2 id="AQS-框架"><a href="#AQS-框架" class="headerlink" title="AQS 框架"></a>AQS 框架</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="aqs.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><h2 id="AQS-原理"><a href="#AQS-原理" class="headerlink" title="AQS 原理"></a>AQS 原理</h2><p>AQS 的核心思想是:</p><ul><li>如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;</li><li>如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。</li></ul><p>这个机制是通过 CLH 队列的变体实现的,将暂时获取不到锁的线程加入队列中。</p><p>AQS 中的队列是一个双向链表,AQS 通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="clh.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><p>AQS 使用一个 Volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取的排队工作,通过 CAS 操作完成对 State 值的修改。</p><h3 id="AQS-数据结构"><a href="#AQS-数据结构" class="headerlink" title="AQS 数据结构"></a>AQS 数据结构</h3><p>双向链表中的节点:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="node.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><p><strong>线程有两种锁模式(共享、互斥):</strong></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="comment">/** Marker to indicate a node is waiting in shared mode */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Node SHARED = <span class="keyword">new</span> Node();</span><br><span class="line"><span class="comment">/** Marker to indicate a node is waiting in exclusive mode */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Node EXCLUSIVE = <span class="keyword">null</span>;</span><br></pre></td></tr></table></figure><p><strong>节点的状态可以是:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Status field, taking on only the values:</span></span><br><span class="line"><span class="comment">* SIGNAL: The successor of this node is (or will soon be)</span></span><br><span class="line"><span class="comment">* blocked (via park), so the current node must</span></span><br><span class="line"><span class="comment">* unpark its successor when it releases or</span></span><br><span class="line"><span class="comment">* cancels. To avoid races, acquire methods must</span></span><br><span class="line"><span class="comment">* first indicate they need a signal,</span></span><br><span class="line"><span class="comment">* then retry the atomic acquire, and then,</span></span><br><span class="line"><span class="comment">* on failure, block.</span></span><br><span class="line"><span class="comment">* CANCELLED: This node is cancelled due to timeout or interrupt.</span></span><br><span class="line"><span class="comment">* Nodes never leave this state. In particular,</span></span><br><span class="line"><span class="comment">* a thread with cancelled node never again blocks.</span></span><br><span class="line"><span class="comment">* CONDITION: This node is currently on a condition queue.</span></span><br><span class="line"><span class="comment">* It will not be used as a sync queue node</span></span><br><span class="line"><span class="comment">* until transferred, at which time the status</span></span><br><span class="line"><span class="comment">* will be set to 0. (Use of this value here has</span></span><br><span class="line"><span class="comment">* nothing to do with the other uses of the</span></span><br><span class="line"><span class="comment">* field, but simplifies mechanics.)</span></span><br><span class="line"><span class="comment">* PROPAGATE: A releaseShared should be propagated to other</span></span><br><span class="line"><span class="comment">* nodes. This is set (for head node only) in</span></span><br><span class="line"><span class="comment">* doReleaseShared to ensure propagation</span></span><br><span class="line"><span class="comment">* continues, even if other operations have</span></span><br><span class="line"><span class="comment">* since intervened.</span></span><br><span class="line"><span class="comment">* 0: None of the above</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* The values are arranged numerically to simplify use.</span></span><br><span class="line"><span class="comment">* Non-negative values mean that a node doesn't need to</span></span><br><span class="line"><span class="comment">* signal. So, most code doesn't need to check for particular</span></span><br><span class="line"><span class="comment">* values, just for sign.</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* The field is initialized to 0 for normal sync nodes, and</span></span><br><span class="line"><span class="comment">* CONDITION for condition nodes. It is modified using CAS</span></span><br><span class="line"><span class="comment">* (or when possible, unconditional volatile writes).</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">volatile</span> <span class="keyword">int</span> waitStatus;</span><br></pre></td></tr></table></figure><ul><li><strong>SIGNAL:</strong>表示当前节点的下一个节点已经被阻塞或者即将被阻塞。因此当前节点释放了锁或者放弃获取锁时,如果它的 waitStatus 是 SIGNAL,它就需要唤醒它的下一个节点。</li><li><strong>CANCELED:</strong>表示 Node 代表的线程因为超时或者中断信号,取消获锁。处于这个状态的 Node 不会再阻塞。</li></ul><h3 id="同步状态-State"><a href="#同步状态-State" class="headerlink" title="同步状态 State"></a>同步状态 State</h3><p>AQS 中维护一个名为 State 的字段,用于展示当前临界资源的获锁情况。</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="comment">/**</span></span><br><span class="line"><span class="comment"> * The synchronization state.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br></pre></td></tr></table></figure><p>通过修改 State 字段表示的同步状态来实现多线程的独占模式和共享模式。</p><h1 id="ReentrantLock-的实现"><a href="#ReentrantLock-的实现" class="headerlink" title="ReentrantLock 的实现"></a>ReentrantLock 的实现</h1><h2 id="ReentrantLock-lock-过程"><a href="#ReentrantLock-lock-过程" class="headerlink" title="ReentrantLock#lock 过程"></a>ReentrantLock#lock 过程</h2><p>ReentrantLock 有一个抽象的静态内部类 Sync,Sync 继承自 AbstractQueuedSynchronizer。Sync 有两个子类,NonfairSync 和 FairSync。</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="comment">/** Synchronizer providing all implementation mechanics */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Sync sync;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Base of synchronization control for this lock. Subclassed</span></span><br><span class="line"><span class="comment"> * into fair and nonfair versions below. Uses AQS state to</span></span><br><span class="line"><span class="comment"> * represent the number of holds on the lock.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Sync</span> <span class="keyword">extends</span> <span class="title">AbstractQueuedSynchronizer</span></span></span><br></pre></td></tr></table></figure><p>ReentrantLock 有两个构造器,无参构造器默认构造非公平锁,可以带参构造器来构造公平锁。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates an instance of {<span class="doctag">@code</span> ReentrantLock}.</span></span><br><span class="line"><span class="comment"> * This is equivalent to using {<span class="doctag">@code</span> ReentrantLock(false)}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">()</span> </span>{</span><br><span class="line"> sync = <span class="keyword">new</span> NonfairSync();</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"> * Creates an instance of {<span class="doctag">@code</span> ReentrantLock} with the</span></span><br><span class="line"><span class="comment"> * given fairness policy.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> fair {<span class="doctag">@code</span> true} if this lock should use a fair ordering policy</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">(<span class="keyword">boolean</span> fair)</span> </span>{</span><br><span class="line"> sync = fair ? <span class="keyword">new</span> FairSync() : <span class="keyword">new</span> NonfairSync();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当在非公平锁模式下,调用 ReentrantLock#lock 方法时,会被委托给 NonfairSync#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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Performs lock. Try immediate barge, backing up to normal</span></span><br><span class="line"><span class="comment"> * acquire on failure.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 尝试将 state 由 0 变为 1</span></span><br><span class="line"> <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line"> <span class="comment">// 将当前线程设置为独占访问资源的线程</span></span><br><span class="line"> setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// 如果状态没有修改成功,应该尝试再去获取锁(可能当前线程已经获锁,实现可重入性)。</span></span><br><span class="line"> <span class="comment">// 如果获锁失败,应该将线程加入等待队列</span></span><br><span class="line"> acquire(<span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>NonfairSync#lock 调用 AbstractQueuedSynchronizer#compareAndSetState 以 CAS 方式,试图将 state 从 0 变为 1。</p><p>如果状态改变成功,就将当前线程设置为能够独占访问资源的线程。</p><p>如果状态改变失败,就调用 AbstractQueuedSynchronizer#acquire 方法。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Acquires in exclusive mode, ignoring interrupts. Implemented</span></span><br><span class="line"><span class="comment"> * by invoking at least once {<span class="doctag">@link</span> #tryAcquire},</span></span><br><span class="line"><span class="comment"> * returning on success. Otherwise the thread is queued, possibly</span></span><br><span class="line"><span class="comment"> * repeatedly blocking and unblocking, invoking {<span class="doctag">@link</span></span></span><br><span class="line"><span class="comment"> * #tryAcquire} until success. This method can be used</span></span><br><span class="line"><span class="comment"> * to implement method {<span class="doctag">@link</span> Lock#lock}.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> arg the acquire argument. This value is conveyed to</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> #tryAcquire} but is otherwise uninterpreted and</span></span><br><span class="line"><span class="comment"> * can represent anything you like.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </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="comment">// 返回true,否则返回 false</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">if</span> (!tryAcquire(arg) &&</span><br><span class="line"> acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line"> selfInterrupt();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>acquire 方法调用的 tryAcquire 实际上会调用 Sync#nonfairTryAcquire,tryAcquire 会再次尝试获取锁。</p><p>AbstractQueuedSynchronizer#addWaiter 会将创建节点,并将节点加入到队列中。</p><p>AbstractQueuedSynchronizer#acquireQueued 再去判断是否可以获得锁。如果不行,修改前驱节点的 waitStatus 为 SIGNAL 然后把线程挂起。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Performs non-fair tryLock. tryAcquire is implemented in</span></span><br><span class="line"><span class="comment"> * subclasses, but both need nonfair try for trylock method.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">nonfairTryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line"> <span class="comment">// 获取 state 状态</span></span><br><span class="line"> <span class="keyword">int</span> c = getState();</span><br><span class="line"> <span class="comment">// 再次尝试将状态改为 1</span></span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) {</span><br><span class="line"> setExclusiveOwnerThread(current);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</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">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) {</span><br><span class="line"> <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line"> <span class="comment">// 防止 int 越界?</span></span><br><span class="line"> <span class="keyword">if</span> (nextc < <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);</span><br><span class="line"> setState(nextc);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</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><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></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Acquires in exclusive uninterruptible mode for thread already in</span></span><br><span class="line"><span class="comment"> * queue. Used by condition wait methods as well as acquire.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> node the node</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> arg the acquire argument</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> {<span class="doctag">@code</span> true} if interrupted while waiting</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line"> <span class="comment">// 如果 node 的前驱节点是 head,并且尝试获锁成功</span></span><br><span class="line"> <span class="keyword">if</span> (p == head && tryAcquire(arg)) {</span><br><span class="line"> <span class="comment">// 把 node 节点设置为头结点</span></span><br><span class="line"> setHead(node);</span><br><span class="line"> p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line"> failed = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">return</span> interrupted;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 获锁失败,判断是否需要将当前线程挂起</span></span><br><span class="line"> <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &&</span><br><span class="line"> parkAndCheckInterrupt())</span><br><span class="line"> interrupted = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (failed)</span><br><span class="line"> cancelAcquire(node);</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><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Convenience method to park and then check if interrupted</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> {<span class="doctag">@code</span> true} if interrupted</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 线程在这里被挂起,不再执行,直到被唤醒。</span></span><br><span class="line"> LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">return</span> Thread.interrupted();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面就是整个 ReentrantLock 在非公平状态下加锁的过程。</p><ol><li>尝试用 CAS 指令 将 AQS 的 state 由 0 改为 1。</li><li>如果尝试成功,就加锁成功。</li><li>如果尝试失败,会创建节点,并将节点加入到双向链表中。</li><li>将当前线程对应节点的前驱节点的 waitStatus 设置为 SIGNAL,阻塞当前线程。</li><li>前驱节点对应线程在释放锁或者取消获锁时,唤醒后继线程。</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="加锁过程.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><h2 id="ReentrantLock-unlock-过程"><a href="#ReentrantLock-unlock-过程" class="headerlink" title="ReentrantLock#unlock 过程"></a>ReentrantLock#unlock 过程</h2><p>首先会调用 AQS#release:</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="comment"> * Releases in exclusive mode. Implemented by unblocking one or</span></span><br><span class="line"><span class="comment"> * more threads if {<span class="doctag">@link</span> #tryRelease} returns true.</span></span><br><span class="line"><span class="comment"> * This method can be used to implement method {<span class="doctag">@link</span> Lock#unlock}.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> arg the release argument. This value is conveyed to</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> #tryRelease} but is otherwise uninterpreted and</span></span><br><span class="line"><span class="comment"> * can represent anything you like.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the value returned from {<span class="doctag">@link</span> #tryRelease}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (tryRelease(arg)) {</span><br><span class="line"> Node h = head;</span><br><span class="line"> <span class="keyword">if</span> (h != <span class="keyword">null</span> && h.waitStatus != <span class="number">0</span>)</span><br><span class="line"> unparkSuccessor(h);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>又到 Sync#tryRelease,就是把 state 改成 0</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> releases)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> c = getState() - releases;</span><br><span class="line"> <span class="keyword">if</span> (Thread.currentThread() != getExclusiveOwnerThread())</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line"> <span class="keyword">boolean</span> free = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) {</span><br><span class="line"> free = <span class="keyword">true</span>;</span><br><span class="line"> setExclusiveOwnerThread(<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> setState(c);</span><br><span class="line"> <span class="keyword">return</span> free;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="公平锁和非公平锁"><a href="#公平锁和非公平锁" class="headerlink" title="公平锁和非公平锁"></a>公平锁和非公平锁</h2><p>在上面非公平锁的实现中,线程会尝试用 CAS 指令直接去改 state 去获取锁,而不管队列中是都有线程在等待。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> acquire(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Fair version of tryAcquire. Don't grant access unless</span></span><br><span class="line"><span class="comment"> * recursive call or no waiters or is first.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line"> <span class="keyword">int</span> c = getState();</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 队列中没有线程或者当前线程就是 head 后面第一个节点时,才会尝试去改状态</span></span><br><span class="line"> <span class="keyword">if</span> (!hasQueuedPredecessors() &&</span><br><span class="line"> compareAndSetState(<span class="number">0</span>, acquires)) {</span><br><span class="line"> setExclusiveOwnerThread(current);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) {</span><br><span class="line"> <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line"> <span class="keyword">if</span> (nextc < <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);</span><br><span class="line"> setState(nextc);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不会像非公平锁中,一上来就 CAS 该状态。</p>]]></content>
<summary type="html">
<h1 id="什么是-AQS"><a href="#什么是-AQS" class="headerlink" title="什么是 AQS?"></a>什么是 AQS?</h1><p><strong>AbstractQueuedSynchronizer</strong> 这个类是
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>CountDownLatch 和 CyclicBarrier</title>
<link href="https://firecarrrr.github.io/2020/07/01/CountDownLatch-%E5%92%8C-CyclicBarrier/"/>
<id>https://firecarrrr.github.io/2020/07/01/CountDownLatch-和-CyclicBarrier/</id>
<published>2020-07-01T08:09:49.000Z</published>
<updated>2020-07-01T08:09:49.384Z</updated>
<content type="html"><![CDATA[<h1 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h1><blockquote><p>A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.<br>A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown method, after which all waiting threads are released and any subsequent invocations of await return immediately. This is a one-shot phenomenon – the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier.</p></blockquote><p>CountDownLatch 是一个线程同步的工具,可以使一个或多个线程等待其它线程执行中某些操作执行完成后再执行。</p><p>CountDownLatch 的 await 方法会阻塞到 count 值被减到 0 的时候。</p><p>CountDownLatch 中的计数值,由构造方法中给定的初值初始化。<strong>CountDownLatch 只能被使用一次,count 值不能被重置。</strong></p><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">Executor executor = Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line">CountDownLatch countDownLatch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">executor.execute(() -> {</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> countDownLatch.await();</span><br><span class="line"> System.out.println(<span class="string">"World"</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">executor.execute(() -> {</span><br><span class="line"> System.out.println(<span class="string">"Hello"</span>);</span><br><span class="line"> countDownLatch.countDown();</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>两个线程,一个打印 Hello, 一个打印 World,一定要保证先打印 Hello,再打印 World。</p><h1 id="CyclicBarrier"><a href="#CyclicBarrier" class="headerlink" title="CyclicBarrier"></a>CyclicBarrier</h1><blockquote><p>A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.<br>A CyclicBarrier supports an optional Runnable command that is run once per barrier point, after the last thread in the party arrives, but before any threads are released. This barrier action is useful for updating shared-state before any of the parties continue.</p></blockquote><p>CyclicBarrier 也是一个线程同步工具,允许一组线程等待彼此到达一个共同的 barrier point 。</p><p>当 CyclicBarrier 计数被减到 0 后,所有等待线程被释放后,CyclicBarrier 能够重置到设置的初始值。</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></pre></td><td class="code"><pre><span class="line">Executor executor = Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line">CyclicBarrier cyclicBarrier = <span class="keyword">new</span> CyclicBarrier(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">executor.execute(() -> {</span><br><span class="line"> System.out.println(<span class="string">"Hello"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> cyclicBarrier.await();</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"> System.out.println(<span class="string">"World"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">executor.execute(() -> {</span><br><span class="line"> System.out.println(<span class="string">"Hello"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> cyclicBarrier.await();</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"> System.out.println(<span class="string">"World"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>两个线程,打印 Hello World,必须两个线程都打印了 Hello 过后,才能打印 World。</p><p>CyclicBarrier 还有一个构造方法:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates a new {<span class="doctag">@code</span> CyclicBarrier} that will trip when the</span></span><br><span class="line"><span class="comment"> * given number of parties (threads) are waiting upon it, and which</span></span><br><span class="line"><span class="comment"> * will execute the given barrier action when the barrier is tripped,</span></span><br><span class="line"><span class="comment"> * performed by the last thread entering the barrier.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> parties the number of threads that must invoke {<span class="doctag">@link</span> #await}</span></span><br><span class="line"><span class="comment"> * before the barrier is tripped</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> barrierAction the command to execute when the barrier is</span></span><br><span class="line"><span class="comment"> * tripped, or {<span class="doctag">@code</span> null} if there is no action</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IllegalArgumentException if {<span class="doctag">@code</span> parties} is less than 1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">CyclicBarrier</span><span class="params">(<span class="keyword">int</span> parties, Runnable barrierAction)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (parties <= <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line"> <span class="keyword">this</span>.parties = parties;</span><br><span class="line"> <span class="keyword">this</span>.count = parties;</span><br><span class="line"> <span class="keyword">this</span>.barrierCommand = barrierAction;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个方法会创建一个 CyclicBarrier,当指定数目的线程等待它是,它将会触发,当 barrier 触发时,会执行给定的 barrier 操作,由最后一个进入 barrier 的线程执行。</p>]]></content>
<summary type="html">
<h1 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h1><blockquote>
<p>A synchro
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>TCP 连接的建立和拆除</title>
<link href="https://firecarrrr.github.io/2020/06/29/TCP-%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%BB%BA%E7%AB%8B%E5%92%8C%E6%8B%86%E9%99%A4/"/>
<id>https://firecarrrr.github.io/2020/06/29/TCP-连接的建立和拆除/</id>
<published>2020-06-29T13:33:21.000Z</published>
<updated>2020-06-29T13:34:47.325Z</updated>
<content type="html"><![CDATA[<h1 id="TCP-连接建立的过程"><a href="#TCP-连接建立的过程" class="headerlink" title="TCP 连接建立的过程"></a>TCP 连接建立的过程</h1><h2 id="三次握手"><a href="#三次握手" class="headerlink" title="三次握手"></a>三次握手</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="三次握手.png" alt title> </div> <div class="image-caption"></div> </figure><ol><li>客户端向服务端发送报文,将 SYN 标志位置 1,客户端随机产生一个初始序列号 client_isn 。</li><li>服务器收到来自客户端的 SYN 报文,<strong>会为该 TCP 连接分配缓存和变量</strong>。服务器向客户端发送确认报文,SYN 标志位置 1,随机生成服务器的初始序列号 server_isn ,将首部确认号字段设置为 client_isn + 1。</li><li>收到服务器的 SYNACK 报文后,<strong>客户端为该连接分配缓存和变量</strong>。客户端向主机发送另一个报文,确认号设置为 server_isn + 1,SYN 设置为 0,序号设置为 client_isn + 1。<strong>这个报文可以携带数据。</strong></li></ol><h3 id="为什么需要三次握手?"><a href="#为什么需要三次握手?" class="headerlink" title="为什么需要三次握手?"></a>为什么需要三次握手?</h3><ul><li>第一次报文发送,服务端如果收到了客户端的报文,此时,服务端知道客户端到服务端的连接是可用的。</li><li>第二次报文发送,如果客户端收到了服务端的报文,此时,客户端知道了客户端到服务端和服务端到客户端的连接时可用的。</li><li>第三次报文发送,如果服务端收到了连接,此时,服务端就知道服务端到客户端的连接也是可用的。客户端和服务端就都能够确认这个链路双向都是可用的。</li></ul><h3 id="为什么客户端和服务端的初始序列号必须随机生成?"><a href="#为什么客户端和服务端的初始序列号必须随机生成?" class="headerlink" title="为什么客户端和服务端的初始序列号必须随机生成?"></a>为什么客户端和服务端的初始序列号必须随机生成?</h3><p>假设有三个角色 A 代表服务器,B 代表客户端,C 代表攻击者。如果以 B 的身份伪造一个请求发送给 A,A 会给 B 发送一个 SYNACK 报文,假设此时 B 被 C 搞得处于异常状态。如果这个 SYNACK 报文中的 server_isn 是固定的,那么 C 就很容易猜测出这个 server_isn,然后构造确认号发送报文给 A。</p><h2 id="四次挥手"><a href="#四次挥手" class="headerlink" title="四次挥手"></a>四次挥手</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="四次挥手.png" alt title> </div> <div class="image-caption"></div> </figure><p>参与一条 TCP 连接的两个进程中任何一个都能终止该连接。当某客户打算关闭连接:</p><ol><li>客户应用进程给服务器发送一个报文,将 FIN 标志位置 1。</li><li>服务器会送一个确认报文,ACK 标志位置 1。</li><li>服务器发送它的终止报文,FIN 标志位置 1。</li><li>客户端对服务器的终止报文进行确认,ACK 置 1。</li></ol>]]></content>
<summary type="html">
<h1 id="TCP-连接建立的过程"><a href="#TCP-连接建立的过程" class="headerlink" title="TCP 连接建立的过程"></a>TCP 连接建立的过程</h1><h2 id="三次握手"><a href="#三次握手" class="
</summary>
<category term="Basic" scheme="https://firecarrrr.github.io/categories/Basic/"/>
<category term="计算机网络" scheme="https://firecarrrr.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>数据分片与 redis 集群</title>
<link href="https://firecarrrr.github.io/2020/06/29/redis-%E9%9B%86%E7%BE%A4/"/>
<id>https://firecarrrr.github.io/2020/06/29/redis-集群/</id>
<published>2020-06-29T13:32:45.000Z</published>
<updated>2020-06-29T13:35:24.480Z</updated>
<content type="html"><![CDATA[<h1 id="数据分片"><a href="#数据分片" class="headerlink" title="数据分片"></a>数据分片</h1><p>数据分片是分布式存储中最重要的问题之一,即当存储被分散到不同的节点时,如何来将数据映射到不同的节点中?</p><h2 id="顺序分片(范围分片)"><a href="#顺序分片(范围分片)" class="headerlink" title="顺序分片(范围分片)"></a>顺序分片(范围分片)</h2><p>最直观的一种分片方式。假设数据范围是 1<del>100,范围分片就是 1</del>33 落到第一个节点,34<del>66 落到第二个节点,67</del>100 落到第三个节点。</p><p>顺序分片的有点是能做顺序访问</p><p><strong>范围分片的问题:</strong></p><ul><li>面对顺序写的时候可能存在热点</li><li>数据可能是倾斜的,比如总是倾向于访问某个范围内的数据</li></ul><h2 id="Hash-分片"><a href="#Hash-分片" class="headerlink" title="Hash 分片"></a>Hash 分片</h2><p>Hash 分片就是把数据 Hash 一下,然后对节点数量取余得到应该放哪儿。</p><p>Hash 分片最大的问题在于当节点需要拓展时,涉及到大量的数据迁移。</p><h2 id="一致性-Hash"><a href="#一致性-Hash" class="headerlink" title="一致性 Hash"></a>一致性 Hash</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="一致性hash.png" alt="image-20200628221645619" title> </div> <div class="image-caption">image-20200628221645619</div> </figure><p>假设当前有 4 个节点,把这 4 个节点放在一个环上,这个环是从 0 到 hash 函数能够生成的最大值。</p><p>每个键被 hash 后,顺时针查找最近的节点,那这个键就应该落在这个节点上。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="扩容后.png" alt="image-20200628222036769" title> </div> <div class="image-caption">image-20200628222036769</div> </figure><p>假设新增一个节点 5,位于节点 2 和节点 4 之间,可以看到此时,这个变更的影响范围就被局限在了很小的一段上。</p><h2 id="redis-虚拟槽"><a href="#redis-虚拟槽" class="headerlink" title="redis 虚拟槽"></a>redis 虚拟槽</h2><p>redis 集群将整个数据库分成了 16384 个槽(slot)。数据库中的每个键都属于 16384 个槽中的一个,集群中的每个节点可以处理 0 个或者 16384 个槽。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="槽.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><p>槽是 redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。</p><h1 id="redis-集群搭建"><a href="#redis-集群搭建" class="headerlink" title="redis 集群搭建"></a>redis 集群搭建</h1><h2 id="cluster-meet"><a href="#cluster-meet" class="headerlink" title="cluster meet"></a>cluster meet</h2><p>首先需要把配置文件里的 cluster-enabled 设置为 yes 以开启集群模式。</p><p>启动节点后,不同机器上的节点之间初跑起来的时候都是独立的,需要通过</p><blockquote><p>cluster meet ip port</p></blockquote><p>命令来使服务器之间握手连接。</p><h2 id="分配槽"><a href="#分配槽" class="headerlink" title="分配槽"></a>分配槽</h2><blockquote><p>cluster addslots [slot]</p></blockquote>]]></content>
<summary type="html">
<h1 id="数据分片"><a href="#数据分片" class="headerlink" title="数据分片"></a>数据分片</h1><p>数据分片是分布式存储中最重要的问题之一,即当存储被分散到不同的节点时,如何来将数据映射到不同的节点中?</p>
<h2 id
</summary>
<category term="redis" scheme="https://firecarrrr.github.io/tags/redis/"/>
</entry>
<entry>
<title>MySQL 慢查询</title>
<link href="https://firecarrrr.github.io/2020/06/29/MySQL-%E6%85%A2%E6%9F%A5%E8%AF%A2/"/>
<id>https://firecarrrr.github.io/2020/06/29/MySQL-慢查询/</id>
<published>2020-06-29T13:31:56.000Z</published>
<updated>2020-06-29T13:35:56.678Z</updated>
<content type="html"><![CDATA[<h1 id="获取有性能问题的-SQL"><a href="#获取有性能问题的-SQL" class="headerlink" title="获取有性能问题的 SQL"></a>获取有性能问题的 SQL</h1><h2 id="慢查询日志"><a href="#慢查询日志" class="headerlink" title="慢查询日志"></a>慢查询日志</h2><p>与慢查询日志有关的配置:</p><ul><li><strong>slow_query_log</strong>:启动停止记录慢查询日志</li><li><strong>slow_query_log_file</strong>:指定慢查询日志存储路径及文件</li><li><strong>long_query_time</strong>:指定记录慢查日志 SQL 执行时间的阈值(单位:秒)</li><li><strong>log_queries_not_using_indexes</strong>:是否记录未使用索引的 SQL(和上面那个阈值无关,只要没有用到索引就会记录)</li></ul><p><strong>慢查询日志中记录的内容:</strong></p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="slow_log.png" alt="image-20200629204739096" title> </div> <div class="image-caption">image-20200629204739096</div> </figure><ul><li>执行这条语句的用户信息</li><li>语句执行的时间</li><li>锁的时间</li><li>返回的数据的行数</li><li>扫描的数据的行数</li><li>这条 SQL 语句</li></ul><h2 id="常用慢查询分析工具"><a href="#常用慢查询分析工具" class="headerlink" title="常用慢查询分析工具"></a>常用慢查询分析工具</h2><p>如果是一个繁忙的系统,产生慢查询日志的量可能很大,可以利用一些工具对慢查询日志进行分析。</p><h4 id="mysqldumpslow"><a href="#mysqldumpslow" class="headerlink" title="mysqldumpslow"></a>mysqldumpslow</h4><p>mysqldumpslow 会合并相同的语句</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysqldumpslow -s r -t 10 slow-mysql.log</span><br></pre></td></tr></table></figure><p>-s 指定按哪种排序方式输出结果:</p><ul><li>c:总的执行次数</li><li>t:总的执行时间</li><li>l:总的锁的时间</li><li>r:总的返回的数据行数</li><li>at,al,ar:上面那些值的平均值,</li></ul><p>-t 指定 top 多少条的数据输出</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="mysqldumpslow.png" alt="image-20200629211451977" title> </div> <div class="image-caption">image-20200629211451977</div> </figure>]]></content>
<summary type="html">
<h1 id="获取有性能问题的-SQL"><a href="#获取有性能问题的-SQL" class="headerlink" title="获取有性能问题的 SQL"></a>获取有性能问题的 SQL</h1><h2 id="慢查询日志"><a href="#慢查询日志" c
</summary>
<category term="MySQL" scheme="https://firecarrrr.github.io/categories/MySQL/"/>
<category term="SQL" scheme="https://firecarrrr.github.io/tags/SQL/"/>
</entry>
<entry>
<title>RPC 原理</title>
<link href="https://firecarrrr.github.io/2020/06/23/RPC-%E5%8E%9F%E7%90%86/"/>
<id>https://firecarrrr.github.io/2020/06/23/RPC-原理/</id>
<published>2020-06-23T06:42:08.000Z</published>
<updated>2020-06-27T07:32:33.200Z</updated>
<content type="html"><![CDATA[<h1 id="什么是-RPC"><a href="#什么是-RPC" class="headerlink" title="什么是 RPC"></a>什么是 RPC</h1><p>RPC (Remote Procedure Call),远程过程调用,也就是一台机器上调用另一台机器上的函数。尽管这两个进程并不在同一个内存空间,但这个调用过程就像发生在一个进程内一样。</p><p>市面上常见的 RPC 框架包括 Dubbo、gRPC 之类的。</p><h2 id="一个-RPC-调用的例子"><a href="#一个-RPC-调用的例子" class="headerlink" title="一个 RPC 调用的例子"></a>一个 RPC 调用的例子</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 客户端</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Reference</span> <span class="comment">// dubbo注解</span></span><br><span class="line"> <span class="keyword">private</span> HelloService helloService;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> helloService.hello(<span class="string">"World"</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="meta">@Service</span> <span class="comment">// dubbo注解</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloServiceImpl</span> <span class="keyword">implements</span> <span class="title">HelloService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello "</span> + name;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面是一个 Spring 和 Dubbo 配合使用的例子。 </p><p>客户端通过 @ Reference 注解,获取了一个 HelloService 接口的对象。然后就可以直接调用 HelloService 的方法,但实际上这个调用的实现发生在远程主机。</p><p>在服务端,实现了 HelloService 接口,并使用 @Service 注解(dubbo 注解),在 Dubbo 框架中注册了这个实现类。</p><p>可以看出,在业务编码层面上,使用 dubbo 实现远程调用和在本地调用并没有多大区别,这是因为 dubbo 框架在背后完成了远程调用。</p><p>在客户端,业务代码得到的 HelloService 这个接口的实现,并不是服务端提供的真正的实现类 HelloServiceImpl 的一个实例,<strong>而是由 RPC 框架提供的一个代理类的实例,这个代理类被称作 ”桩(stub)“。</strong></p><p>作为一个代理类,桩也实现了 HelloService 接口,客户端在调用 hello 方法时,实际上调用的是这个桩的 hello 方法。在这个 hello 方法中,桩会构造一个请求,包含:</p><ul><li>请求的服务名,例如:HelloService#hello(String)</li><li>请求的所有参数,比如例子中的 name,值是 “World”</li></ul><p>服务端收到请求后,先把请求中的服务名解析出来,然后根据服务名在服务端进程中,找到服务的提供者。找到提供者后,RPC 框架使用客户端传来的参数调用服务提供者。服务端的 RPC 框架获得返回结果之后,再将结果封装成响应,返回给客户端。</p><p>客户端收到服务端的响应后,从中解析出返回值,返回给服务调用方。这样一次 RPC 调用就完成了。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="D:\Blog\blog\source\_posts\RPC-原理\demo.jpg" alt title> </div> <div class="image-caption"></div> </figure><h2 id="RPC-主要技术"><a href="#RPC-主要技术" class="headerlink" title="RPC 主要技术"></a>RPC 主要技术</h2><p>实现一个 RPC 框架,主要需要实现:</p><ol><li>高性能网络传输</li><li>序列化与反序列化</li><li>服务路由与服务发现</li></ol><p>关于服务路由与服务发现:</p><p> 这个模块要解决的是,客户端如何知道服务端的服务地址呢?在 RPC 框架中存在一个<strong>注册中心</strong>组件,服务端的业务代码在向 RPC 框架注册服务之后,RPC 框架就会把这个服务的名称和地址发布到注册中心上。客户端的桩在调用服务之前,会向注册中心请求服务端的地址。</p><h2 id="RPC-框架的一个-Demo"><a href="#RPC-框架的一个-Demo" class="headerlink" title="RPC 框架的一个 Demo"></a>RPC 框架的一个 Demo</h2><p>这个类里面定义了 RPC 里面最重要的几个方法:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * RPC框架对外提供的服务接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RpcAccessPoint</span> <span class="keyword">extends</span> <span class="title">Closeable</span></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="comment"> * <span class="doctag">@param</span> uri 远程服务地址</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> serviceClass 服务的接口类的Class</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <T> 服务接口的类型</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 远程服务引用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <T> <span class="function">T <span class="title">getRemoteService</span><span class="params">(URI uri, Class<T> serviceClass)</span></span>;</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 class="doctag">@param</span> service 实现实例</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> serviceClass 服务的接口类的Class</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <T> 服务接口的类型</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 服务地址</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <T> <span class="function">URI <span class="title">addServiceProvider</span><span class="params">(T service, Class<T> serviceClass)</span></span>;</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 class="doctag">@param</span> nameServiceUri 注册中心URI</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 注册中心引用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">default</span> NameService <span class="title">getNameService</span><span class="params">(URI nameServiceUri)</span> </span>{</span><br><span class="line"> Collection<NameService> nameServices = ServiceSupport.loadAll(NameService.class);</span><br><span class="line"> <span class="keyword">for</span> (NameService nameService : nameServices) {</span><br><span class="line"> <span class="keyword">if</span>(nameService.supportedSchemes().contains(nameServiceUri.getScheme())) {</span><br><span class="line"> nameService.connect(nameServiceUri);</span><br><span class="line"> <span class="keyword">return</span> nameService;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">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="comment"> * 服务端启动RPC框架,监听接口,开始提供远程服务。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 服务实例,用于程序停止的时候安全关闭服务。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function">Closeable <span class="title">startServer</span><span class="params">()</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="什么是-RPC"><a href="#什么是-RPC" class="headerlink" title="什么是 RPC"></a>什么是 RPC</h1><p>RPC (Remote Procedure Call),远程过程调用,也就是一台机器上调用另一台机器
</summary>
<category term="Distributed System" scheme="https://firecarrrr.github.io/categories/Distributed-System/"/>
</entry>
<entry>
<title>java String</title>
<link href="https://firecarrrr.github.io/2020/06/22/java-String/"/>
<id>https://firecarrrr.github.io/2020/06/22/java-String/</id>
<published>2020-06-22T07:20:05.000Z</published>
<updated>2020-06-22T08:21:26.384Z</updated>
<content type="html"><![CDATA[<h1 id="String-对象是如何实现的"><a href="#String-对象是如何实现的" class="headerlink" title="String 对象是如何实现的"></a>String 对象是如何实现的</h1><h2 id="JDK-7-amp-8-的实现"><a href="#JDK-7-amp-8-的实现" class="headerlink" title="JDK 7 & 8 的实现"></a>JDK 7 & 8 的实现</h2><p>在这两个版本的 JDK 中,String 本质上是一个不可变 char[]</p><h2 id="JDK-9-的实现"><a href="#JDK-9-的实现" class="headerlink" title="JDK 9 的实现"></a>JDK 9 的实现</h2><p>JDK 9 中,char[] 被改变成了 byte[]。并且新增了一个属性 coder,来表示编码格式。这是因为一个 char 字符占两个字节,在可变长编码格式中,对于只占一个字节的字符来说,相当于浪费了一个字节的空间。用 byte[] 字符和编码格式标记的组合可以提高空间利用率。</p><h2 id="String-对象的不可变性"><a href="#String-对象的不可变性" class="headerlink" title="String 对象的不可变性"></a>String 对象的不可变性</h2><p>String 这个类本身被标记为 final,表示这个类不可继承。底层的存储数组也被标记为 private final 表示不可修改。也就是说 String 对象一旦被创建成功就不能被修改了。</p><h3 id="为什么-String-对象要被设计为不可变的?"><a href="#为什么-String-对象要被设计为不可变的?" class="headerlink" title="为什么 String 对象要被设计为不可变的?"></a>为什么 String 对象要被设计为不可变的?</h3><ul><li>使得 hash 值不会变更,这使得 String 类型非常适合用来做 HashMap 的 key。</li><li>可以实现字符串常量池。</li></ul><h1 id="三种创建-String-的方式"><a href="#三种创建-String-的方式" class="headerlink" title="三种创建 String 的方式"></a>三种创建 String 的方式</h1><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">String str1 = <span class="string">"abc"</span>;</span><br><span class="line">String str2 = <span class="keyword">new</span> String(<span class="string">"abc"</span>);</span><br><span class="line">String str3 = str2.intern();</span><br></pre></td></tr></table></figure><h2 id="String-str1-“abc”"><a href="#String-str1-“abc”" class="headerlink" title="String str1 = “abc”;"></a>String str1 = “abc”;</h2><p>使用这种方式创建字符串,JVM 首先会检查该对象是否在<strong>字符串常量池中,如果在,就返回该对象的引用</strong>。否则,就会在字符串常量池中创建新的字符串。这种方式可以减少同一值的字符串对象被重复创建,节约内存。</p><h2 id="String-str2-new-String-“abc”"><a href="#String-str2-new-String-“abc”" class="headerlink" title="String str2 = new String(“abc”);"></a>String str2 = new String(“abc”);</h2><p>用这种方式创建字符串,首先在编译类文件的时候,“abc” 常量字符会被放到常量结构中。在类加载时,“abc” 将会在常量池中创建。在 new 方法被调用时,JVM 会在堆上创建一个 String 对象,String 中的数组会引用常量池中的 “abc” 对应的数组。然后 str2 会指向堆内存上这个对象的引用。</p><h2 id="String-str3-str2-intern-;"><a href="#String-str3-str2-intern-;" class="headerlink" title="String str3 = str2.intern();"></a>String str3 = str2.intern();</h2><p>如果调用 intern 方法,会先去查看字符串常量池中是否有等于该对象字符串的引用。如果有,就会返回常量池中这个字符串的引用,如果没有,就会在把这个字符串添加到常量池中。最终这个 str3 会指向常量池中的字符串引用。</p><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></pre></td><td class="code"><pre><span class="line">str1 == str2;</span><br><span class="line">str2 == str3;</span><br><span class="line">str1 == str3;</span><br></pre></td></tr></table></figure><p>答案应该是,false,false,true</p>]]></content>
<summary type="html">
<h1 id="String-对象是如何实现的"><a href="#String-对象是如何实现的" class="headerlink" title="String 对象是如何实现的"></a>String 对象是如何实现的</h1><h2 id="JDK-7-amp-8-的
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>Gap Lock</title>
<link href="https://firecarrrr.github.io/2020/06/21/Gap-Lock/"/>
<id>https://firecarrrr.github.io/2020/06/21/Gap-Lock/</id>
<published>2020-06-21T06:39:06.000Z</published>
<updated>2020-07-06T12:58:49.212Z</updated>
<content type="html"><![CDATA[<h1 id="幻读"><a href="#幻读" class="headerlink" title="幻读"></a>幻读</h1><p>幻读是指一个事务在进行过程中前后两次对同一范围数据的查询结果不一致,后一次出现了前一次查询没有查到的新行。这个新行指的是被新插入的新行。</p><p>幻读只会出现在 rr 隔离级别下的当前读场景,因为在 rc 隔离级别下,语义上规定的就是一个事务提交了就能被其它事务看到,就无所谓什么幻读了。</p><h1 id="快照读与当前读"><a href="#快照读与当前读" class="headerlink" title="快照读与当前读"></a>快照读与当前读</h1><h2 id="快照读"><a href="#快照读" class="headerlink" title="快照读"></a>快照读</h2><p>在 MySQL 的默认隔离级别 RR 下,一般的 select 语句完成的是基于 MVCC 实现的快照读,自然不会出现幻读的问题。</p><h2 id="当前读"><a href="#当前读" class="headerlink" title="当前读"></a>当前读</h2><p>当前读是指加锁的读取和 DML 操作。例如,使用下面两条查询语句可以实现当前读:</p><ul><li><strong>select … for share (for share mode)</strong> :这条语句是给 select 查询到的行加上读锁,其它的事务可以读到这些行,但是没办法修改,直到你的事务提交。(走全表扫描也是会被锁所有行的)。如果你执行这条语句的时候,还有其它未提交的事务对这些行进行了修改,那么你的查询就必须等到那些事务提交过后再执行。</li><li><strong>select … for update</strong>:这条语句给查询中遇到的行加上排它锁,也就是说如果走全表扫描,整个表的行都会被锁。其它事务如果在这些语句上执行更新操作或者 select … for share 会被阻塞。在某些隔离级别下读这些数据也会被阻塞。(RR 隔离级别会忽略 read view 上出现的任何记录上的锁)。</li></ul><h1 id="幻读的问题"><a href="#幻读的问题" class="headerlink" title="幻读的问题"></a>幻读的问题</h1><p>创建一个表:</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`t`</span> (</span><br><span class="line"> <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`c`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`d`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</span><br><span class="line"> <span class="keyword">KEY</span> <span class="string">`c`</span> (<span class="string">`c`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> t <span class="keyword">values</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>),(<span class="number">5</span>,<span class="number">5</span>,<span class="number">5</span>),</span><br><span class="line">(<span class="number">10</span>,<span class="number">10</span>,<span class="number">10</span>),(<span class="number">15</span>,<span class="number">15</span>,<span class="number">15</span>),(<span class="number">20</span>,<span class="number">20</span>,<span class="number">20</span>),(<span class="number">25</span>,<span class="number">25</span>,<span class="number">25</span>);</span><br></pre></td></tr></table></figure><p>有 id 和 c, d 三行。id 是主键,c 上加了索引。</p><p><strong>如果 for update 只在 id = 5 这一行加锁:</strong></p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="example.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><ol><li><p>T1 时刻,把所有 d = 5 的行加锁。查询结果应该是 (5,5,5)</p></li><li><p>T2 时刻,id = 0 的那一行数据,变成了 (0,5,5)</p></li><li><p>T3 时刻,查询结果应该是 (0,5,5),(5,5,5)</p></li><li><p>T4 时刻,插入了一条数据 (1,1,5),又改成了(1,5,5)</p></li><li><p>T5 时刻,查询结果是 (0,5,5), (1,5,5), (5,5,5)</p></li></ol><p>分析一下:</p><ul><li>T1 时刻,session A 给 id = 5 这一行加了锁,并没有给 id = 0 这一行加锁,所以 T2 这个 update 语句可以执行。这样就破坏了<strong>seesion A 给所有 d = 5 的行加锁的语义。</strong></li><li>同样的道理,session C 也破坏了 session A 的加锁语义。</li></ul><p>所以幻读会造成<strong>语义上的问题</strong>。</p><p>幻读还可能造成<strong>数据一致性</strong>上的问题。数据一致性包括数据库内部数据状态的一致性还包括数据和日志在逻辑上的一致性。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="example3.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><ol><li>T1 时刻,id = 5 这一行变成了 (5,5,100),这个结果最终在 T6 时刻正式提交。</li><li>T2 时刻,id = 0 这一行变成了 (0,5,5)。</li><li>T4 时刻,表中多了一行 (1,5,5)。</li></ol><p>看一下 binlog 中的内容</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* T2 时刻 session B 提交 */</span></span><br><span class="line"><span class="keyword">update</span> t <span class="keyword">set</span> d=<span class="number">5</span> <span class="keyword">where</span> <span class="keyword">id</span>=<span class="number">0</span>; <span class="comment">/*(0,0,5)*/</span></span><br><span class="line"><span class="keyword">update</span> t <span class="keyword">set</span> c=<span class="number">5</span> <span class="keyword">where</span> <span class="keyword">id</span>=<span class="number">0</span>; <span class="comment">/*(0,5,5)*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* T4 时刻 session C 提交 */</span></span><br><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> t <span class="keyword">values</span>(<span class="number">1</span>,<span class="number">1</span>,<span class="number">5</span>); <span class="comment">/*(1,1,5)*/</span></span><br><span class="line"><span class="keyword">update</span> t <span class="keyword">set</span> c=<span class="number">5</span> <span class="keyword">where</span> <span class="keyword">id</span>=<span class="number">1</span>; <span class="comment">/*(1,5,5)*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* T6 时刻 session A 提交 */</span></span><br><span class="line"><span class="keyword">update</span> t <span class="keyword">set</span> d=<span class="number">100</span> <span class="keyword">where</span> d=<span class="number">5</span>;<span class="comment">/*所有d=5的行,d改成100*/</span></span><br></pre></td></tr></table></figure><p>显然,通过这个 binlog 去恢复一个库或者克隆一个库,运行的结果都是和库里的数据不一致的。</p><p>即便是我们在 T1 时刻把扫描范围内遇到的行都加上锁,Session B 会被阻塞,但是 Session C 的插入语句,由于被插入语句还不存在,不存在的语句没法加锁,也就是说可以插入成功和修改成功,这样 binlog 里的顺序变成了 Session C -> Session A -> Session B。这样用 binlog 去恢复一个库,Session B 的执行结果没问题了,Session C 的结果还是会变成 (1,5,100)。这就是<strong>幻读导致的问题</strong>。</p><h1 id="间隙锁"><a href="#间隙锁" class="headerlink" title="间隙锁"></a>间隙锁</h1><p>产生幻读的原因是行锁只能锁住已经存在的行,而插入操作是在已经存在数据的”间隙“做的操作。</p><p>比如上面例子的表中,最开始插入了 6 条数据,在表的主键索引上就形成了 7 个间隙。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="gap.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><p>这样在执行 select * from t where d = 5 for update 的时候,就不止给数据库中已经有的 6 个记录加锁(d 上没有索引,走全表扫描),还需要同时加 7 个间隙锁。这样就能确保无法插入新的记录。</p><p>也就是说不仅给行加上了行锁,还给<strong>行两边的间隙</strong>加上了间隙锁。</p><p>间隙锁锁定的是<strong>其他事务往这个间隙中插入记录</strong>这个操作。</p><p>间隙锁和行锁合成为 <strong>next-key lock</strong></p><p>next-key-lock 是一个前开后闭的区间</p><p>我们的表 t 初始化以后,如果用 select * from t for update 要把整个表所有记录锁起来,就形成了 7 个 next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。</p><h2 id="间隙锁可能导致的问题"><a href="#间隙锁可能导致的问题" class="headerlink" title="间隙锁可能导致的问题"></a>间隙锁可能导致的问题</h2><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="gaplockpro.png" alt="img" title> </div> <div class="image-caption">img</div> </figure><ol><li>Session A 启动事务,由于 id = 9 这一行不存在,加上一个 (5, 10) 的间隙锁。</li><li>Session B 启动,同样加了一个 (5, 10) 的间隙锁。</li><li>Session B 执行 insert 语句,被 Session A 的间隙锁拦住。</li><li>Session A 执行 insert 语句,被 Session B 的间隙锁拦住。</li><li>形成死锁。</li></ol><p>间隙锁增大了锁的范围,会降低并发度。</p>]]></content>
<summary type="html">
<h1 id="幻读"><a href="#幻读" class="headerlink" title="幻读"></a>幻读</h1><p>幻读是指一个事务在进行过程中前后两次对同一范围数据的查询结果不一致,后一次出现了前一次查询没有查到的新行。这个新行指的是被新插入的新行。</
</summary>
<category term="MySQL" scheme="https://firecarrrr.github.io/categories/MySQL/"/>
</entry>
<entry>
<title>MySQL日志</title>
<link href="https://firecarrrr.github.io/2020/06/19/MySQL%E6%97%A5%E5%BF%97/"/>
<id>https://firecarrrr.github.io/2020/06/19/MySQL日志/</id>
<published>2020-06-19T12:48:35.000Z</published>
<updated>2020-06-20T09:33:53.768Z</updated>
<content type="html"><![CDATA[<h1 id="MySQL-日志"><a href="#MySQL-日志" class="headerlink" title="MySQL 日志"></a>MySQL 日志</h1><p>总结一下 MySQL (innoDB 存储引擎)的三种日志。</p><ul><li><strong>redo log</strong>:InnoDB 特有的物理日志</li><li><strong>bin log</strong>:Server 层的逻辑日志</li><li><strong>undo log</strong>:存放数据被修改前的值</li></ul><h2 id="Buffer-Pool"><a href="#Buffer-Pool" class="headerlink" title="Buffer Pool"></a>Buffer Pool</h2><p>Buffer Pool 是 InnoDB 在内存中缓存被访问表和索引的地方。Buffer Pool 可以使得经常被访问的数据直接在内存中被处理,从而加快处理速度。缓冲池以页为单位来缓存数据,在 InnoDB 中每个数据页的大小默认是 16 KB。缓冲池的实现本质上就是数据页的一个链表,通过一个变种的 LRU 算法淘汰数据页。</p><h3 id="Change-Buffer"><a href="#Change-Buffer" class="headerlink" title="Change Buffer"></a>Change Buffer</h3><p>change buffer 是 buffer pool 的一部分,用来对非唯一索引的更新操作(Insert、Update、Delete)进行缓存的数据结构。由于缓存了,就不用立马访存写盘,进行大量的随机 IO 操作。</p><p><strong>什么时候写 change buffer?</strong></p><p>更新操作作用的数据页没有在 buffer pool 中,就把更新操作缓存到 change buffer 中。</p><p><strong>什么时候 merge 数据?</strong><br>当之后发生更新操作的页因为读操作被加载到内存中后。</p><h2 id="redo-log"><a href="#redo-log" class="headerlink" title="redo log"></a>redo log</h2><p>redo log 是 InnoDB 引擎特有的日志,是一种物理日志,记录的是“在某个数据页做了什么修改”。</p><p>MySQL 有一项叫做 <strong>WAL (Write-Ahead Logging)</strong>,也就是预写日志技术。</p><p>当有记录需要更新时,InnoDB 会把更新记录写到 redo log 中,并更新内存(写读到 buffer pool 中的数据或者写 change buffer),这个更新操作就算完成了。之后系统会在适当的时间点将更新记录刷到磁盘上。</p><p><strong>如果 buffer 中的数据还没有刷到磁盘上,系统就发生了崩溃或者断电怎么办?</strong></p><p>redo log 另外一个重要功能就是能够使系统具有 <strong>crash-safe</strong> 能力。redo log 同样在内存中存在缓存,存在先写缓存后写磁盘的问题,可以通过 innodb_flush_log_at_trx_commit 参数设置写磁盘的时机。默认值1代表每次事务提交时,都会写到磁盘。</p><p>在实现上,redo log 是固定大小的,用两个指针来表明写文件的起点和终点:</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="redo-log.png" alt="redo-log.png" title> </div> <div class="image-caption">redo-log.png</div> </figure><p>如上图所示,write pos 代表当前可写的位置,checkpoint 是当前要擦除的位置。如果 write pos 追上了 checkpoint ,代表 redo log 已经写满了,这时候新的更新语句不能够再执行,需要停下来先擦掉一些数据,把 checkpoint 向前推进一些,腾出可写空间。</p><p><strong>如何推进 checkpoint ?</strong></p><p>如果 checkpoint 被触发后,会将 buffer 中的脏数据都刷到磁盘上,这样就能保证,即时删除 redo log 上的记录也不会发生数据丢失。</p><h2 id="binlog"><a href="#binlog" class="headerlink" title="binlog"></a>binlog</h2><p>binlog 是 MySQL Server 层的日志,是逻辑日志,记录的是这个语句的原始逻辑,例如”给 ID = 1 这行的字段 c 加 1“。</p><p>binlog 有三种格式:</p><ul><li>statement:记录 sql 语句</li><li>row:记录行的内容,更新前和后都有</li><li>mixed:默认用 statement,特定的时候转换为 row</li></ul><p>在数据更新时,redo log 和 binlog 都会用到,下面看一下一条更新语句的执行过程。</p><p>更新语句:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> <span class="keyword">Test</span> <span class="keyword">set</span> a = <span class="number">1</span> <span class="keyword">where</span> <span class="keyword">id</span> = <span class="number">2</span>;</span><br></pre></td></tr></table></figure><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="update.png" alt="update.png" title> </div> <div class="image-caption">update.png</div> </figure><p>在写 redo log 时采用了两阶段提交,为的是避免通过 redo log 恢复数据和通过 binlog 恢复数据出现不一致。如果先写 redo log,然后系统崩了,那么通过 binlog 恢复的数据就会少一个操作。反之,通过 redo log 恢复,就会少一个操作。</p><p>与 redo log 的循环写模式不同,binlog 采用追加写的模式。也就是说只要空间足够大,对数据库的所有操作,都可以被 binlog 记录下来。</p><p><strong>如何把数据库恢复到半个月内任意一秒?</strong></p><ul><li>定期做系统的整库备份。</li><li>找到离要恢复时间点最近的一次备份,利用备份中的 binlog ,重放到要恢复的时间点。</li></ul><p>同样可以通过参数 sync_binlog 来设置 binlog 的持久化策略。设置为1表示每次事务都持久化。</p><h2 id="undo-log"><a href="#undo-log" class="headerlink" title="undo log"></a>undo log</h2><p>undo log 即回滚日志,是 InnoDB 引擎提供的逻辑日志。当事务对数据库进行修改操作时,InnoDB 不仅会记录 redo log,还会生成 undo log;如果事务执行失败或者调用 rollback,导致事务回滚时,就可以利用 undo log 中的信息将数据回滚到修改之前的样子。</p><p>undo log 主要实现两个功能:</p><ul><li>事务回滚</li><li>MVCC</li></ul>]]></content>
<summary type="html">
<h1 id="MySQL-日志"><a href="#MySQL-日志" class="headerlink" title="MySQL 日志"></a>MySQL 日志</h1><p>总结一下 MySQL (innoDB 存储引擎)的三种日志。</p>
<ul>
<li><s
</summary>
<category term="MySQL" scheme="https://firecarrrr.github.io/categories/MySQL/"/>
</entry>
<entry>
<title>Spring 事务的传播行为</title>
<link href="https://firecarrrr.github.io/2020/06/18/Transaction/"/>
<id>https://firecarrrr.github.io/2020/06/18/Transaction/</id>
<published>2020-06-18T14:14:06.000Z</published>
<updated>2020-06-19T03:55:49.532Z</updated>
<content type="html"><![CDATA[<h1 id="Spring-事务传播行为"><a href="#Spring-事务传播行为" class="headerlink" title="Spring 事务传播行为"></a>Spring 事务传播行为</h1><h2 id="什么是-Spring-事务传播行为?"><a href="#什么是-Spring-事务传播行为?" class="headerlink" title="什么是 Spring 事务传播行为?"></a>什么是 Spring 事务传播行为?</h2><p>Spring 事务传播行为是指被声明为事务的方法,嵌套到另一个方法时,事务是如何传播的。</p><h2 id="Spring-事务的传播行为有哪些?"><a href="#Spring-事务的传播行为有哪些?" class="headerlink" title="Spring 事务的传播行为有哪些?"></a>Spring 事务的传播行为有哪些?</h2><p>通过实验的方式来验证一下 Spring 事务的传播行为。</p><p>将就用一下之前项目中的用户表:</p><figure class="highlight sql"><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">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`user`</span> (</span><br><span class="line"> <span class="string">`id`</span> <span class="built_in">int</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT,</span><br><span class="line"> <span class="string">`nickname`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`password`</span> <span class="built_in">varchar</span>(<span class="number">32</span>) <span class="built_in">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'两次 MD5'</span>,</span><br><span class="line"> <span class="string">`salt`</span> <span class="built_in">varchar</span>(<span class="number">10</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`head`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`register_time`</span> datetime <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`last_login_time`</span> datetime <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`login_count`</span> <span class="built_in">int</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="string">`mobile`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8mb4_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</span><br><span class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> AUTO_INCREMENT=<span class="number">2</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8mb4 <span class="keyword">COLLATE</span>=utf8mb4_bin;</span><br></pre></td></tr></table></figure><ol><li><strong>PROPAGATION_REQUIRED</strong></li></ol><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="meta">@Override</span></span><br><span class="line"><span class="meta">@Transactional</span>(propagation = Propagation.REQUIRED)</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addUser</span><span class="params">()</span> </span>{</span><br><span class="line"> User user = <span class="keyword">new</span> User();</span><br><span class="line"> user.setNickname(<span class="string">"小王"</span>);</span><br><span class="line"> userMapper.insert(user);</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="meta">@Transactional</span>(propagation = Propagation.REQUIRED)</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addUserException</span><span class="params">()</span> </span>{</span><br><span class="line"> User user = <span class="keyword">new</span> User();</span><br><span class="line"> user.setNickname(<span class="string">"小李"</span>);</span><br><span class="line"> userMapper.insert(user);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException();</span><br><span class="line">}</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addUserTest</span><span class="params">()</span></span>{</span><br><span class="line"> propaTestService.addUser();</span><br><span class="line"> propaTestService.addUserException();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果:</p><ul><li>小王插入成功,小李插入失败</li></ul><p>也就是说,两个内部方法各自执行了自己的事务。第二个方法的回滚没有对第一个方法造成影响。</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addUserTest</span><span class="params">()</span></span>{</span><br><span class="line"> propaTestService.addUser();</span><br><span class="line"> propaTestService.addUserException();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果:</p><ul><li>小王和小李都插入失败</li></ul><p>也就是说,两个内部方法处于一个事务之中,方法2的执行抛出异常造成了两个方法的回滚。</p><p>总结一下 REQUIRED 的事务传播行为:</p><p><strong>如果当前方法有事务,就用当前方法的事务。如果没有,就用自身的事务</strong></p><ol start="2"><li><strong>PROPAGATION_SUPPORTS</strong></li></ol><p>外围方法没有事务的情况:</p><ul><li>小王和小李都插入成功</li></ul><p>外围方法有事务的情况:</p><ul><li>小王和小李都插入失败</li></ul><p>SUPPORTS 的事务传播行为:</p><p><strong>事务可有可无,如果当前方法有事务,就以当前外部事务执行,如果没有事务,就以非事务的方式执行</strong></p><ol start="3"><li><strong>PROPAGATION_MANDATORY</strong></li></ol><p>外围方法没有事务的情况:</p><ul><li>插入失败,并且抛出异常 </li></ul><blockquote><p>org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’</p></blockquote><p>外围方法有事务的情况:</p><ul><li>都插入失败</li></ul><p>MANDATORY 的事务传播行为:</p><p><strong>事务是必须的,外围方法没有事务就会抛异常,有事务就以当前事务执行</strong></p><ol start="4"><li><strong>PROPAGATION_REQUIRES_NEW</strong></li></ol><p>外围方法没有事务的情况:</p><ul><li>小王插入成功,小李插入失败</li></ul><p>外围方法有事务的情况:</p><ul><li>小王插入成功,小李插入失败</li></ul><p>REQUIRES_NEW 的传播行为:</p><p><strong>无论外围方法有没有事务,都会新开内部方法自己的事务,且事务之间互不影响</strong></p><ol start="5"><li><strong>PROPAGATION_NOT_SUPPORTED</strong></li></ol><p>外围方法没有事务的情况:</p><ul><li>小王、小李都插入成功</li></ul><p>外围方法有事务的情况:</p><ul><li>小王、小李都插入成功</li></ul><p>NOT_SUPPORT 的传播行为:</p><p><strong>不支持事务,以非事务的方式执行</strong></p><ol start="6"><li><strong>PROPAGATION_NEVER</strong></li></ol><p>外围没有事务:</p><ul><li>都插入成功</li></ul><p>外围有事务:</p><ul><li>抛异常</li></ul><p>NEVER 的传播行为:</p><p><strong>不支持事务,有事务就抛异常</strong></p><ol start="7"><li><strong>PROPAGATION_NESTED</strong></li></ol><p>外围没有事务:</p><ul><li>小王插入成功,小李插入失败</li></ul><p>外围有事务:</p><ul><li>都插入失败</li></ul><p>现在看来和 REQUIRED 的行为很像,来看第三种情况:</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="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addUserTest</span><span class="params">()</span></span>{</span><br><span class="line"> propaTestService.addUser();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> propaTestService.addUserException();</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.out.println(<span class="string">"回滚"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种情况下,外部方法中把内部方法抛出的异常 catch 了,这样得到的结果是:</p><ul><li>小王插入成功,小李插入失败</li></ul><p>NESTED 的传播行为:</p><p><strong>如果外围方法有事务,内部方法会启动子事务,如果外部事务没有感知到子事务抛出的异常,可以只回滚子事务</strong></p>]]></content>
<summary type="html">
<h1 id="Spring-事务传播行为"><a href="#Spring-事务传播行为" class="headerlink" title="Spring 事务传播行为"></a>Spring 事务传播行为</h1><h2 id="什么是-Spring-事务传播行为?"><
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>Dynamic Proxy</title>
<link href="https://firecarrrr.github.io/2020/05/07/Dynamic-Proxy/"/>
<id>https://firecarrrr.github.io/2020/05/07/Dynamic-Proxy/</id>
<published>2020-05-07T10:14:40.000Z</published>
<updated>2020-05-07T14:42:37.738Z</updated>
<content type="html"><![CDATA[<h1 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h1><p><strong>代理模式(Proxy Design Pattern):</strong>在不改变原始类的情况下,通过代理类来给原始类添加功能。被代理类中实现的是主要的业务逻辑,代理类在主要业务逻辑的基础上加上了一些额外的与主业务逻辑关系不大的功能,比如说做一些统计、日志等。</p><p>代理模式可以通过<strong>静态</strong>也可以通过<strong>动态</strong>的方式实现。</p><h2 id="静态代理"><a href="#静态代理" class="headerlink" title="静态代理"></a>静态代理</h2><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></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="class"><span class="keyword">interface</span> <span class="title">IUserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">login</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">register</span><span class="params">()</span></span>;</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><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="comment">// 业务类实现登录和注册逻辑</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> <span class="keyword">implements</span> <span class="title">IUserController</span></span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">login</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"login"</span>);</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"register"</span>);</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><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="comment">// 代理类继承同一个接口,将具体登录行为委托给业务类实现,在业务逻辑上加上自身代理的逻辑</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserControllerProxy</span> <span class="keyword">implements</span> <span class="title">IUserController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> UserController userController;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UserControllerProxy</span><span class="params">(UserController userController)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.userController = userController;</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">login</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// doSomething1()</span></span><br><span class="line"> userController.login();</span><br><span class="line"> <span class="comment">// doSomething2()</span></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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// doSomething1()</span></span><br><span class="line"> userController.register();</span><br><span class="line"> <span class="comment">// doSomething2()</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>静态构建代理类在复杂的业务场景下会使类的数量大幅增加,引入大量额外工作,代码重复较多,不够简洁。动态代理可以用来解决这个问题。</p><h2 id="动态代理"><a href="#动态代理" class="headerlink" title="动态代理"></a>动态代理</h2><p>动态代理的实现主要有两种 JDK 的实现和 cglib 的实现。</p><h3 id="JDK-动态代理"><a href="#JDK-动态代理" class="headerlink" title="JDK 动态代理"></a>JDK 动态代理</h3><p><strong>JDK 实现中有一个接口:</strong></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">java.lang.reflect.InvocationHandler</span><br></pre></td></tr></table></figure><p>Java doc 里面这样描述这个接口:</p><blockquote><p>InvocationHandler is the interface implemented by the invocation handler of a proxy instance.<br>Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.</p></blockquote><p>InvacationHandler 接口被代理实例的 invocation handler 实现。每一个代理实例都有一个关联的 invocation handler。当代理对象上有方法被调用时,将对方法进行编码,并分派到调用程序的 invoke 方法。</p><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="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> Throwable</span>;</span><br></pre></td></tr></table></figure><blockquote><p>Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.</p></blockquote><p>处理代理实例上的方法调用,并返回结果。当代理对象发生方法调用时,这个方法会被与代理对象关联的 invocation handler 调用。</p><p>proxy – 发生方法调用的实例</p><p>method – 发生调用的方法</p><p>args – 方法调用的参数</p><p><strong>JDK 实现还有一个关键的类:</strong></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">java.lang.reflect.Proxy</span><br></pre></td></tr></table></figure><blockquote><p>Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Class<?> getProxyClass(ClassLoader loader,</span><br><span class="line"> Class<?>... interfaces)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">newProxyInstance</span><span class="params">(ClassLoader loader,</span></span></span><br><span class="line"><span class="function"><span class="params"> Class<?>[] interfaces,</span></span></span><br><span class="line"><span class="function"><span class="params"> InvocationHandler h)</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> InvocationHandler <span class="title">getInvocationHandler</span><span class="params">(Object proxy)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IllegalArgumentException</span></span><br></pre></td></tr></table></figure><p>上面这三个方法分别获取代理类、代理实例、和关联的 Invocation Handler</p><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><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="class"><span class="keyword">class</span> <span class="title">UserControllerDynamicProxy</span> <span class="keyword">implements</span> <span class="title">InvocationHandler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Object target;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UserControllerDynamicProxy</span><span class="params">(Object target)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.target = target;</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="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> before();</span><br><span class="line"> Object result = method.invoke(target, args);</span><br><span class="line"> after();</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">before</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"do something before method invoke"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">after</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"do something after method invoke"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建要被代理的对象</span></span><br><span class="line"> UserController userController = <span class="keyword">new</span> UserController();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建 invocationHandler</span></span><br><span class="line"> UserControllerDynamicProxy userControllerDynamicProxy = <span class="keyword">new</span> UserControllerDynamicProxy(userController);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建代理对象,代理对象是 IUserController 类型,但不是 UserController 类型(面向接口)</span></span><br><span class="line"> IUserController controller = (IUserController)Proxy.newProxyInstance(userController.getClass().getClassLoader(),</span><br><span class="line"> userController.getClass().getInterfaces(), userControllerDynamicProxy);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调用代理对象上的方法,引发 invoke 调用</span></span><br><span class="line"> controller.login();</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">do</span> something before method invoke</span><br><span class="line">login</span><br><span class="line"><span class="keyword">do</span> something after method invoke</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> DynamicProxy.IUserController;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.InvocationHandler;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Proxy;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.UndeclaredThrowableException;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 代理类继承了 Proxy,实现了 IUserController 接口</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">UserControllerProxy</span> <span class="keyword">extends</span> <span class="title">Proxy</span> <span class="keyword">implements</span> <span class="title">IUserController</span> </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> Method m1;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m2;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m4;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m0;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Method m3;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UserControllerProxy</span><span class="params">(InvocationHandler var1)</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(var1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object var1)</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> (Boolean)<span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m1, <span class="keyword">new</span> Object[]{var1});</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var3) {</span><br><span class="line"> <span class="keyword">throw</span> var3;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var4) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var4);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">toString</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> (String)<span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m2, (Object[])<span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调用 invocationHandler 的 invoke() 方法</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">login</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m4, (Object[])<span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> (Integer)<span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m0, (Object[])<span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">()</span> <span class="keyword">throws</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">super</span>.h.invoke(<span class="keyword">this</span>, m3, (Object[])<span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException | Error var2) {</span><br><span class="line"> <span class="keyword">throw</span> var2;</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UndeclaredThrowableException(var3);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> m1 = Class.forName(<span class="string">"java.lang.Object"</span>).getMethod(<span class="string">"equals"</span>, Class.forName(<span class="string">"java.lang.Object"</span>));</span><br><span class="line"> m2 = Class.forName(<span class="string">"java.lang.Object"</span>).getMethod(<span class="string">"toString"</span>);</span><br><span class="line"> m4 = Class.forName(<span class="string">"DynamicProxy.IUserController"</span>).getMethod(<span class="string">"login"</span>);</span><br><span class="line"> m0 = Class.forName(<span class="string">"java.lang.Object"</span>).getMethod(<span class="string">"hashCode"</span>);</span><br><span class="line"> m3 = Class.forName(<span class="string">"DynamicProxy.IUserController"</span>).getMethod(<span class="string">"register"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchMethodException var2) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchMethodError(var2.getMessage());</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException var3) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoClassDefFoundError(var3.getMessage());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看起来就和静态代理区别不大,就是自动生成了代理类。也可以看出 JDK 动态代理是<strong>面向接口</strong>的,这个代理对象与被代理对象实现了相同的接口。如果被代理对象没有实现接口,那么就不能用 JDK 的动态代理来实现。</p><h3 id="CGLIB-动态代理"><a href="#CGLIB-动态代理" class="headerlink" title="CGLIB 动态代理"></a>CGLIB 动态代理</h3><p>如果被代理对象没有实现接口,那么可以考虑使用 cglib,cglib 采用<strong>创建目标类子类</strong>的方式来实现动态代理。</p><h3 id="JDK-动态代理与-cglib-的比较"><a href="#JDK-动态代理与-cglib-的比较" class="headerlink" title="JDK 动态代理与 cglib 的比较"></a>JDK 动态代理与 cglib 的比较</h3><p>实现上:</p><ul><li>JDK 动态代理使用反射方式实现,必须要有接口的业务类才能使用。</li><li>cglib 基于 ASM(动态字节码操作框架)实现,通过生成业务类的子类来实现。</li></ul><p>优缺点:</p><ul><li>JDK 达到最小依赖,可能比 cglib 稳定。平滑的版本升级。代码实现简单。</li><li>cglib 无需实现接口,达到无侵入。性能较高。</li></ul>]]></content>
<summary type="html">
<h1 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h1><p><strong>代理模式(Proxy Design Pattern):</strong>在不改变原始类的情况下,通过代理类来
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>Spring Bean 生命周期</title>
<link href="https://firecarrrr.github.io/2020/05/04/SpringBean/"/>
<id>https://firecarrrr.github.io/2020/05/04/SpringBean/</id>
<published>2020-05-04T06:26:59.000Z</published>
<updated>2020-05-06T13:04:25.687Z</updated>
<content type="html"><![CDATA[<h1 id="Spring-Bean"><a href="#Spring-Bean" class="headerlink" title="Spring Bean"></a>Spring Bean</h1><p>Spring Bean 是指可以被实例化、组装,并通过 Spring IoC 容器管理的对象。</p><h2 id="BeanFactory-和-ApplicationContext"><a href="#BeanFactory-和-ApplicationContext" class="headerlink" title="BeanFactory 和 ApplicationContext"></a>BeanFactory 和 ApplicationContext</h2><ul><li><strong>BeanFactory:</strong> 基础类型的 IoC 容器,提供完整的 IoC 服务支持。</li><li><strong>ApplicationContext:</strong> ApplicationContext 在 BeanFactory 的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory 的所有支持,ApplicationContext 还提供了其它高级特性,比如事件发布、国际化等。</li></ul><h2 id="Spring-Bean-生命周期"><a href="#Spring-Bean-生命周期" class="headerlink" title="Spring Bean 生命周期"></a>Spring Bean 生命周期</h2><h3 id="配置-Spring-Bean-元信息"><a href="#配置-Spring-Bean-元信息" class="headerlink" title="配置 Spring Bean 元信息"></a>配置 Spring Bean 元信息</h3><p>Spring Bean 元信息是指那些描述 Bean 的基本信息。 BeanDefinition 是 Spring 中定义 Bean 配置元信息的接口。BeanDefinition 元信息包括:</p><ul><li><p>Name:Bean的名称或者 ID</p></li><li><p>Class:Bean 的全类名</p></li><li><p>Scope:作用域</p></li><li><p>Constructor arguments:构造器参数</p></li><li><p>Properties:属性</p></li><li><p>Autowiring mode:自动装配模式</p></li><li><p>lazy-initialization mode:延迟初始化模式(延迟和非延迟)</p></li><li><p>Initialization method:初始化回调方法名称</p></li><li><p>Destruction method:销毁回调方法名称</p></li></ul><p>定义 Spring Bean 元信息的方式有三大类:</p><ul><li>面向资源:XML, Properties, Guava</li><li>面向注解</li><li>面向 API</li></ul><p>对于面向资源和面向注解的方式,Spring 利用相应的解析器将其解析为 BeanDefinition。</p><h3 id="注册-Spring-Bean"><a href="#注册-Spring-Bean" class="headerlink" title="注册 Spring Bean"></a>注册 Spring Bean</h3><p>配置好的 BeanDefinition 需要注册到 IoC 容器中。</p><p>BeanDefinitionRegistry 是定义 Bean 注册行为的接口,DefaultListableBeanFactory 是它的一个实现。注册 BeanDefinition 时,是将 BeanName 和 BeanDefinition 保存到一个 ConcurrentHashMap 中,BeanName 以注册顺序被保存到一个 List 中。</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="comment">/** Map of bean definition objects, keyed by bean name. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map<String, BeanDefinition> beanDefinitionMap = <span class="keyword">new</span> ConcurrentHashMap<>(<span class="number">256</span>);</span><br><span class="line"><span class="comment">/** List of bean definition names, in registration order. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> List<String> beanDefinitionNames = <span class="keyword">new</span> ArrayList<>(<span class="number">256</span>);</span><br></pre></td></tr></table></figure><h3 id="加载-Spring-Bean-Class"><a href="#加载-Spring-Bean-Class" class="headerlink" title="加载 Spring Bean Class"></a>加载 Spring Bean Class</h3><p>AbstractBeanDefinition 中有一个属性时 beanClass,当 Definition 对应的 Class 还没有被加载时,beanClass 是一个 String 记录 Bean Class 的全类名,当 Bean Class 被 ClassLoader 被加载后就是 Bean Class 的 Class 对象。</p><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="meta">@Nullable</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> Object beanClass;</span><br></pre></td></tr></table></figure><h3 id="Spring-Bean-的实例化"><a href="#Spring-Bean-的实例化" class="headerlink" title="Spring Bean 的实例化"></a>Spring Bean 的实例化</h3><p><strong>BeanPostProcessor:</strong>BeanPostProcessor 是一个回调接口,有两个方法,允许在初始化前和后对 Bean 进行修改。BeanPostProcessor 有一些子接口,添加了在生命周期其它阶段对 Bean 的拦截操作,比如 InstantiationAwareBeanPostProcessor , DestructionAwareBeanPostProcessor 等。BeanPostProcessor 和 BeanFactory 之间是 N : 1 的关系。BeanPostProcessor 通过配置 Bean 的方式注册到 Spring 容器中。</p><p><strong>实例化前操作:</strong>InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法可以在实例化之前生成一个代理对象,之后就不会执行 BeanDefinition 中定义的实例化操作。下面这个例子中,拦截了 User 对象。</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="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">postProcessBeforeInstantiation</span><span class="params">(Class<?> beanClass, String beanName)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="keyword">if</span>(ObjectUtils.nullSafeEquals(<span class="string">"user"</span>, beanName) && User.class.equals(beanClass)) {</span><br><span class="line"> <span class="comment">// 把配置完成的 User 替换掉</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> User();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 保持 Spring IoC 容器的实例化操作</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>实例化:</strong>这里就是执行 Bean 构造器的,可能是执行无参构造器,也可能是执行有参构造器。</p><p><strong>实例化后操作:</strong>InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 方法可以在 Spring Bean 实例化后,属性被填充前被调用。如果方法返回 false,该 Bean 实例将不允许属性赋值,也就不会执行配置元信息中定义的赋值操作。如果返回 true,就会保持 Spring IoC 容器的赋值操作。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">postProcessAfterInstantiation</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="keyword">if</span>(ObjectUtils.nullSafeEquals(<span class="string">"user"</span>, beanName) && User.class.equals(bean.getClass())) {</span><br><span class="line"> User user = (User) bean;</span><br><span class="line"> user.setId(<span class="number">2L</span>);</span><br><span class="line"> user.setName(<span class="string">"hello"</span>);</span><br><span class="line"> <span class="comment">// user 对象不允许属性赋值填入 就是不填入配置元信息中的值</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 保持 Spring IoC 容器的属性赋值操作</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Spring-Bean-属性赋值"><a href="#Spring-Bean-属性赋值" class="headerlink" title="Spring Bean 属性赋值"></a>Spring Bean 属性赋值</h3><p><strong>赋值前阶段:</strong>InstantiationAwareBeanPostProcessor#postProcessProperties 是 Spring Bean 赋值前的回调,可以修改赋值参数-值元数据。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> PropertyValues <span class="title">postProcessProperties</span><span class="params">(PropertyValues pvs, Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="keyword">if</span>(ObjectUtils.nullSafeEquals(<span class="string">"user"</span>, beanName) && User.class.equals(bean.getClass())) {</span><br><span class="line"> MutablePropertyValues propertyValues;</span><br><span class="line"> <span class="keyword">if</span>(pvs <span class="keyword">instanceof</span> MutablePropertyValues){</span><br><span class="line"> propertyValues = (MutablePropertyValues) pvs;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> propertyValues = <span class="keyword">new</span> MutablePropertyValues();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(propertyValues.contains(<span class="string">"city"</span>)){</span><br><span class="line"> propertyValues.removePropertyValue(<span class="string">"city"</span>);</span><br><span class="line"> }</span><br><span class="line"> propertyValues.addPropertyValue(<span class="string">"city"</span>, <span class="string">"chengdu"</span>);</span><br><span class="line"> <span class="keyword">return</span> propertyValues;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>Bean 属性赋值:</strong></p><p><strong>Spring Aware 接口回调:</strong>Aware 接口是一个标记接口,用于 Spring 容器回调以注入相应的组件。Aware 的子接口包括,对 Aware 接口的回调发生在 Bean 属性赋值之后:</p><ul><li>BeanNameAware</li><li>BeanClassLoaderAware</li><li>BeanFactoryAware</li><li>EnvironmentAware</li><li>EmbeddedValueResolverAware</li><li>ResourceLoaderAware</li><li>ApplicationEventPublisherAware</li><li>MessageSourceAware</li><li>ApplicationContextAware</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserHolder</span> <span class="keyword">implements</span> <span class="title">BeanNameAware</span>, <span class="title">BeanClassLoaderAware</span>, <span class="title">BeanFactoryAware</span>, <span class="title">EnvironmentAware</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> User user;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> ClassLoader classLoader;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> BeanFactory beanFactory;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String beanName;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Environment environment;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UserHolder</span><span class="params">(User user)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.user = user;</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBeanClassLoader</span><span class="params">(ClassLoader classLoader)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.classLoader = classLoader;</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBeanFactory</span><span class="params">(BeanFactory beanFactory)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="keyword">this</span>.beanFactory = beanFactory;</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBeanName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.beanName = name;</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setEnvironment</span><span class="params">(Environment environment)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.environment = environment;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Spring-Bean-初始化"><a href="#Spring-Bean-初始化" class="headerlink" title="Spring Bean 初始化"></a>Spring Bean 初始化</h3><p><strong>初始化前:</strong>BeanPostProcessor#postProcessBeforeInitialization 可以在 Bean 初始化前做一些操作。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">postProcessBeforeInitialization</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="keyword">if</span> (ObjectUtils.nullSafeEquals(<span class="string">"user"</span>, beanName) && User.class.equals(bean.getClass())) {</span><br><span class="line"> User user = (User) bean;</span><br><span class="line"> user.setName(<span class="string">"FireCar"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> bean;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>初始化:</strong></p><ul><li>@PostConstruct 注解</li><li>实现 InitializingBean 接口的 afterPropertiesSet() 方法</li><li>自定义初始化方法 (init-method)</li></ul><p>在 Spring 容器中执行的顺序也是上面这个顺序。</p><p><strong>初始化后回调:</strong>BeanPostProcessor#postProcessAfterInitialization 在 Bean 初始化后做一些操作。</p><p><strong>初始化完成阶段:</strong>实现 SmartInitializingSingleton#afterSingletonsInstantiated 方法</p><h3 id="Spring-Bean-销毁"><a href="#Spring-Bean-销毁" class="headerlink" title="Spring Bean 销毁"></a>Spring Bean 销毁</h3><p><strong>销毁前回调:</strong>DestructionAwareBeanPostProcessor#postProcessBeforeDestruction</p><p><strong>销毁:</strong></p><ul><li>@PreDestory</li><li>实现 DisposableBean 的 destroy() 方法</li><li>自定义实现 (destroy-method)</li></ul>]]></content>
<summary type="html">
<h1 id="Spring-Bean"><a href="#Spring-Bean" class="headerlink" title="Spring Bean"></a>Spring Bean</h1><p>Spring Bean 是指可以被实例化、组装,并通过 Spring
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>HTTPS与TLS</title>
<link href="https://firecarrrr.github.io/2020/04/22/TLS/"/>
<id>https://firecarrrr.github.io/2020/04/22/TLS/</id>
<published>2020-04-22T13:01:08.000Z</published>
<updated>2020-04-22T15:18:21.653Z</updated>
<content type="html"><![CDATA[<h1 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h1><p>HTTP 是基于明文传输的,不具有安全性。很容易被攻击者截获、更改。HTTPS 协议是基于 SSL/TLS 协议上的安全的传输协议。</p><h2 id="如何定义安全?"><a href="#如何定义安全?" class="headerlink" title="如何定义安全?"></a>如何定义安全?</h2><ul><li><strong>机密性</strong>:数据是被加密的</li><li><strong>完整性</strong>:数据是不可篡改的</li><li><strong>身份认证</strong>:能够确定通信方的身份</li></ul><h3 id="机密性的实现"><a href="#机密性的实现" class="headerlink" title="机密性的实现"></a>机密性的实现</h3><h4 id="对称加密"><a href="#对称加密" class="headerlink" title="对称加密"></a>对称加密</h4><p>加密和解密使用同一个密钥</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="对称加密.png" alt="对称加密.png" title> </div> <div class="image-caption">对称加密.png</div> </figure><p>对称加密的问题在于无法解决<strong>密钥交换</strong>的问题,就是说怎么安全的将密钥传递给对方?</p><p>常见的对称加密算法:AES, ChaCha20等</p><h4 id="非对称加密"><a href="#非对称加密" class="headerlink" title="非对称加密"></a><strong>非对称加密</strong></h4><p>存在<strong>公钥(公开)</strong>和<strong>私钥(严格保密)</strong>两个密钥</p><p>加密解密过程具有单向性:公钥加密只能用私钥解密,反之亦然</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="非对称加密.png" alt="非对称加密.png" title> </div> <div class="image-caption">非对称加密.png</div> </figure><p>常用的非对称加密算法:RSA, ECC等</p><h4 id="混合加密"><a href="#混合加密" class="headerlink" title="混合加密"></a>混合加密</h4><p>对称加密无法解决密钥交换问题,非对称加密很慢</p><p>融合两种加密方式优点的解决方案是混合加密</p><ol><li>通信开始时,先用非对称加密解决密钥交换问题</li><li>拿到密钥后,双方使用对称加密进行通信</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="混合加密.png" alt="混合加密.png" title> </div> <div class="image-caption">混合加密.png</div> </figure><h3 id="完整性的实现"><a href="#完整性的实现" class="headerlink" title="完整性的实现"></a>完整性的实现</h3><h4 id="摘要算法"><a href="#摘要算法" class="headerlink" title="摘要算法"></a>摘要算法</h4><p>就是用一个 hash 函数,把数据压缩成一个固定长度、独一无二的摘要字符串</p><p>常用的摘要算法:MD5、SHA-1、SHA-2(TLS 推荐使用)</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="摘要算法.png" alt="摘要算法.png" title> </div> <div class="image-caption">摘要算法.png</div> </figure><p>通信一方在发送消息时,也发送消息摘要;通信另一方解密后,再算一次摘要,进行比对。一样就是完整、未经篡改的数据。</p><h3 id="身份认证的实现"><a href="#身份认证的实现" class="headerlink" title="身份认证的实现"></a>身份认证的实现</h3><h4 id="数字签名"><a href="#数字签名" class="headerlink" title="数字签名"></a>数字签名</h4><p>利用<strong>私钥</strong>加密<strong>摘要</strong>就能实现数字签名</p><p>因为被私钥加密的摘要只能被对应的公钥解密,这就验证了消息发送者的身份</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="数字签名.png" alt="数字签名.png" title> </div> <div class="image-caption">数字签名.png</div> </figure><h4 id="数字证书和-CA"><a href="#数字证书和-CA" class="headerlink" title="数字证书和 CA"></a>数字证书和 CA</h4><p>如何判断公钥的来源?如何防止黑客伪造公钥?</p><p>CA(Certificate Authority): 证书认证机构,作为一个第三方,用自己的私钥来给公钥签名。把公钥和相关信息一起加密达成一个包,形成了<strong>数字证书</strong>。</p><p>客户端的”证书管理器“中有”受信任的根证书颁发机构“列表。客户端会根据这张表,查看解开数字证书的公钥是否在列表之中。如果在就用它解开数字证书,如果数字证书中记录的网址与正在浏览的网址不一致,就说明证书可能被冒用,浏览器会发出警告。</p><h1 id="TLS"><a href="#TLS" class="headerlink" title="TLS"></a>TLS</h1><p>SSL 即安全套阶层(Secure Socket Layer),在 OSI 模型中处于第五层(会话层)。SSL 发展到 v3 时已经被证明了是一个非常好的安全通信协议,于是 IETF 把它改名为 TLS(Transport Layer Security),正式标准化,版本号从 1.0 开始算起,实际上 TLS1.0 就是 SSLv3.1。</p><h2 id="抓包-TLS-1-2-连接过程"><a href="#抓包-TLS-1-2-连接过程" class="headerlink" title="抓包 TLS 1.2 连接过程"></a>抓包 TLS 1.2 连接过程</h2><p>下面抓包百度,看看连接建立过程。开始用 Chrome 访问,Chrome 似乎做了一些神奇的优化?一些包抓不到。用 FireFox 就好了。</p><ol><li>TCP 连接建立后,浏览器首先会发送一个 Client Hello 消息。消息包括版本号、支持的密码套件、还有一个随机数。</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="ClientHello.jpg" alt="ClientHello.jpg" title> </div> <div class="image-caption">ClientHello.jpg</div> </figure><ol start="2"><li>服务器收到 Client Hello 后,返回一个 Server Hello。会返回版本号、从密码套件中选择一个、一个随机数。</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="ServerHello.jpg" alt="ServerHello.jpg" title> </div> <div class="image-caption">ServerHello.jpg</div> </figure><p>可以看出百度选择了用 ECDHE 做密钥交换算法。ECDHE 是 ECC 的一个子算法,基于椭圆曲线离散对数。</p><ol start="3"><li>服务器把证书发送给客户端。客户端可以解析证书,取出服务器的公钥。</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="certificate.jpg" alt="certificate.jpg" title> </div> <div class="image-caption">certificate.jpg</div> </figure><ol start="4"><li>服务端发送 Server Key Exchange 消息,发送椭圆曲线的公钥(Server Params),再加上自己的私钥签名。客户端可以验证这个消息来自于服务器。</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="SKexchange.jpg" alt="SKexchange.jpg" title> </div> <div class="image-caption">SKexchange.jpg</div> </figure><ol start="5"><li>发送 Server Hello Done 消息。</li></ol><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="ServerHelloDone.png" alt="ServerHelloDone.png" title> </div> <div class="image-caption">ServerHelloDone.png</div> </figure><ol start="6"><li>客户端生成一个椭圆曲线公钥(Client Params),用 Client Key Exchange 消息发送给服务器。 </li></ol><p><img src="CKE.png" alt="CKE.png"></p><ol start="7"><li><p>客户端和服务器都拿到了 Client Params、Server Params,用 ECDHE 算法,算出 Pre-Master。黑客即便拿到了前面两个参数也算不出 Pre-Master(why?)。客户端和服务器用 Client Random、Server Random、Pre-Master 生成用于加密会话的主密钥 Master Secret,由于 Pre-Master 是保密的,Master Secret 也是保密的。</p></li><li><p>客户端发送 Change Cipher Spec。再发送一个 Finished 消息,把之前所有发送的数据做个摘要,再加密发送给对方做个验证。服务器同样发送 Change Cipher Spec 和 finish。双方都验证加密、解密 OK,握手结束。后面就收发被加密的 HTTP 请求和响应。</p><p><img src="finish.png" alt="finish.png"></p><p><strong>整体流程:</strong></p><p><img src="%E6%95%B4%E4%BD%93%E6%B5%81%E7%A8%8B.png" alt="整体流程.png"></p><p>上面这个过程其实是<strong>单向认证</strong>的握手过程,只认证了服务器的身份(通过服务器证书),而没有认证客户端的身份。因为单向认证过后已经建立了安全通信,可以通过账号、密码来确认用户身份。</p><p>银行的 U 盾就是给用户颁发客户端证书,实现双向认证。</p></li></ol>]]></content>
<summary type="html">
<h1 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h1><p>HTTP 是基于明文传输的,不具有安全性。很容易被攻击者截获、更改。HTTPS 协议是基于 SSL/TLS 协议上的
</summary>
<category term="HTTPS" scheme="https://firecarrrr.github.io/tags/HTTPS/"/>
</entry>
<entry>
<title>Reactor</title>
<link href="https://firecarrrr.github.io/2020/04/15/Reactor/"/>
<id>https://firecarrrr.github.io/2020/04/15/Reactor/</id>
<published>2020-04-15T10:51:16.000Z</published>
<updated>2020-04-15T14:21:53.481Z</updated>
<content type="html"><![CDATA[<h1 id="Reactor-模式"><a href="#Reactor-模式" class="headerlink" title="Reactor 模式"></a>Reactor 模式</h1><p> NIO 这部分一直迷迷糊糊的,想借学 Reactor 这部分的时候来谈一下。</p><h2 id="IO"><a href="#IO" class="headerlink" title="IO"></a>IO</h2><p>所谓I/O就是内存和外部设备之间拷贝数据的过程。以网络 I/O为例,也就是用户程序从网卡读取数据和向网卡写数据的过程。这个过程可以被分为两个步骤:</p><ul><li>内核从网卡把数据拷贝到内核缓存</li><li>数据从内核读取到用户程序地址空间</li></ul><p>各种 I/O 模型的区别在于:实现这两个步骤的方式不一样</p><h2 id="同步阻塞-I-O-与-TPC"><a href="#同步阻塞-I-O-与-TPC" class="headerlink" title="同步阻塞 I/O 与 TPC"></a>同步阻塞 I/O 与 TPC</h2><p>TPC 也就是 Thread Per Connection,就是说服务器在处理客户端请求的时候,为每一个客户端分配一个线程去处理。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="tpc.png" alt="tpc.png" title> </div> <div class="image-caption">tpc.png</div> </figure><p>这里之所以需要为每一个请求分配单独的线程是因为 read、write 这些调用都是同步阻塞的。如果单线程,一阻塞,系统就没办法响应新的请求了。</p><p>TPC 的问题在于,创建线程和线程的上下文切换都是需要代价的。面对海量连接,这种模型是无能为力的。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BIOServer</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ConnectionHandler</span> <span class="keyword">extends</span> <span class="title">Thread</span></span>{</span><br><span class="line"> <span class="keyword">private</span> Socket socket;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ConnectionHandler</span><span class="params">(Socket socket)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.socket = socket;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (!Thread.currentThread().isInterrupted() && !socket.isClosed()){</span><br><span class="line"> <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 class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">// 线程池</span></span><br><span class="line"> ExecutorService executor = Executors.newFixedThreadPool(<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> ServerSocket serverSocket = <span class="keyword">new</span> ServerSocket();</span><br><span class="line"> serverSocket.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">9999</span>));</span><br><span class="line"> <span class="comment">// 主线程循环等待新连接到来</span></span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>){</span><br><span class="line"> Socket socket = serverSocket.accept();</span><br><span class="line"> <span class="comment">// 把任务提交给线程池</span></span><br><span class="line"> executor.submit(<span class="keyword">new</span> ConnectionHandler(socket));</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">catch</span> (IOException 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></pre></td></tr></table></figure><h2 id="Reactor-模式-1"><a href="#Reactor-模式-1" class="headerlink" title="Reactor 模式"></a>Reactor 模式</h2><p>TPC 之所以需要创建那么多线程,是因为在执行那些 I/O 操作的时候,并不知道内核是否已经把数据准备好,只能傻等。</p><p>NIO 能够解决这个问题,只需要在 Selector 上注册我们感兴趣的事件(有新连接到来、读就绪、写就绪等)。NIO 能同时监听多个连接,当某条连接上就绪时,操作系统就会通知线程,从阻塞态返回,开始处理。</p><p>Reactor 模式就是利用 NIO 的这种 I/O 多路复用的特性来实现的更高性能的单机高并发模型。Reactor 模式又叫做 Dispatcher 模式,即 I/O 多路复用统一监听事件,收到事件后 Dispatch 给某个线程。</p><p>Reactor 的核心组件包括 Reactor 和处理资源池,典型的配置方案包括:</p><ul><li>单 Reactor / 单线程</li><li>单 Reactor / 多线程</li><li>多 Reactor / 多线程</li></ul><h3 id="单-Reactor-单线程"><a href="#单-Reactor-单线程" class="headerlink" title="单 Reactor / 单线程"></a>单 Reactor / 单线程</h3><p>Redis 就是典型的单 Reactor / 单线程模型</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="oneReactorOneThread.png" alt="单Reactor/单线程.png" title> </div> <div class="image-caption">单Reactor/单线程.png</div> </figure><p>客户端发来请求,Reactor 对象通过 Select 监听到连接事件。收到事件后,如果是建立连接的事件,则交给 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 处理后续时间。如果不是连接建立时间则调用 Acceptor 中建立的 Handler 来进行响应。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OneThreadReactor</span> <span class="keyword">extends</span> <span class="title">Thread</span></span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 存储连接和 Handler 之间的关系</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> HashMap<SocketChannel, ChannelHandler> handlerHashMap = <span class="keyword">new</span> HashMap<>();</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>(Selector selector = Selector.open();</span><br><span class="line"> ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()){</span><br><span class="line"> serverSocketChannel.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">9999</span>));</span><br><span class="line"> <span class="comment">// 设置 channel 为非阻塞</span></span><br><span class="line"> serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">// 注册到 selector, 设置关注状态</span></span><br><span class="line"> serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"> <span class="keyword">while</span>(<span class="keyword">true</span>) {</span><br><span class="line"> <span class="comment">// 阻塞等待就绪的 channel</span></span><br><span class="line"> selector.select();</span><br><span class="line"> <span class="comment">// 获取所有就绪的 channel</span></span><br><span class="line"> Set<SelectionKey> selectionKeys = selector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> iterator = selectionKeys.iterator();</span><br><span class="line"> <span class="keyword">while</span> (iterator.hasNext()) {</span><br><span class="line"> SelectionKey key = iterator.next();</span><br><span class="line"> <span class="comment">// 接受连接并注册 handler</span></span><br><span class="line"> <span class="keyword">if</span>(key.isAcceptable()) {</span><br><span class="line"> registerHandler((ServerSocketChannel) key.channel(), selector);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 调用连接对应 Handler 处理读事件</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(key.isReadable()) {</span><br><span class="line"> getHandler((SocketChannel) key.channel()).channelReadable();</span><br><span class="line"> }</span><br><span class="line"> iterator.remove();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (IOException 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="comment">/**</span></span><br><span class="line"><span class="comment"> * 接受连接并绑定处理器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> serverSocketChannel</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerHandler</span><span class="params">(ServerSocketChannel serverSocketChannel, Selector selector)</span></span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> SocketChannel socketChannel = serverSocketChannel.accept();</span><br><span class="line"> socketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> socketChannel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"> handlerHashMap.put(socketChannel, <span class="keyword">new</span> ChannelHandler(socketChannel));</span><br><span class="line"> } <span class="keyword">catch</span> (IOException 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="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取绑定的 handler</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> socketChannel</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> ChannelHandler <span class="title">getHandler</span><span class="params">(SocketChannel socketChannel)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> handlerHashMap.get(socketChannel);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> OneThreadReactor server = <span class="keyword">new</span> OneThreadReactor();</span><br><span class="line"> server.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>单 Reactor / 单线程模型没有线程的切换,确实效率非常高,但是也有它的局限性。第一是不能利用多核能力。第二是应用场景有限,只适合处理速度非常快的场景,如果处理逻辑里有访问数据库等耗时操作,整个服务器就卡住了。</p><h3 id="单-Reactor-多线程"><a href="#单-Reactor-多线程" class="headerlink" title="单 Reactor / 多线程"></a>单 Reactor / 多线程</h3><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="oneReactorMuiltyThread.png" alt="单Reactor/多线程.png" title> </div> <div class="image-caption">单Reactor/多线程.png</div> </figure><p>和单 Reactor / 单线程的区别是,Handler 不会负责业务处理,Handler 通过 read 读取到数据后,会发送给 Processor 进行业务处理。业务处理完成之后,会将结果发给主线程中的 send 将相应结果返回给 client。</p><h3 id="多-Reactor-多线程"><a href="#多-Reactor-多线程" class="headerlink" title="多 Reactor / 多线程"></a>多 Reactor / 多线程</h3><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="muiltyRactor.png" alt="多Reactor/多线程.png" title> </div> <div class="image-caption">多Reactor/多线程.png</div> </figure><p>父线程只监听连接的建立事件,当出现连接建立事件后 Acceptor 接受连接,并将新连接分配给某个子线程。子线程将监听连接,并创建一个对应的 Handler。当事件发生后,子线程调用 Handler 来做相应。</p><p>Ngnix(具体实现上有差异)、Netty 就是采用这种模型。 </p>]]></content>
<summary type="html">
<h1 id="Reactor-模式"><a href="#Reactor-模式" class="headerlink" title="Reactor 模式"></a>Reactor 模式</h1><p> NIO 这部分一直迷迷糊糊的,想借学 Reactor 这部分的时候来谈一下
</summary>
<category term="Java" scheme="https://firecarrrr.github.io/categories/Java/"/>
</entry>
<entry>
<title>MySQL事务隔离和MVCC</title>
<link href="https://firecarrrr.github.io/2019/08/04/mysql-transaction/"/>
<id>https://firecarrrr.github.io/2019/08/04/mysql-transaction/</id>
<published>2019-08-04T08:25:20.000Z</published>
<updated>2019-08-24T14:44:13.468Z</updated>
<content type="html"><![CDATA[<h1 id="MySQL事务隔离和MVCC"><a href="#MySQL事务隔离和MVCC" class="headerlink" title="MySQL事务隔离和MVCC"></a>MySQL事务隔离和MVCC</h1><p>同样,把最近看的事务和锁的部分总结一下,这部分的确东西蛮多的。</p><h2 id="MySQL事务"><a href="#MySQL事务" class="headerlink" title="MySQL事务"></a>MySQL事务</h2><p>其实事务这东西说白了就是一组操作的集合。我们希望数据库系统能够保证这一组操作数据一致且操作独立。数据一致就是说事务在提交的时候保证事务内的所有操作都能成功完成,并且永久生效。操作独立是说多个同时执行的事务之间应该是相互独立,互不影响的。</p><p>用标准一点的说法,还就是老生常谈的ACID,感觉ACID更像是目标吧,真实的系统不一定能实现:</p><ul><li><p><strong>原子性(atomicity):</strong>这个最好理解,就是说一个事务的所有操作是不可分隔的最小工作单元,要么全部成功,要么全部失败。</p></li><li><p><strong>一致性(consistency):</strong>数据库总是从一个一致性状态转换到另一个一致性状态。在任何时刻,包括数据库正常提供服务的时候,数据库从异常中恢复过来的时候,数据都是一致的,保证不会读到中间状态的数据。每本数据库书里都会举的转账的例子,A转200块给B的这个转账的事务执行后,A的钱少了200,B的钱就一定会多200。数据库不会因为这个事务的执行,出现逻辑上不一致的状况。</p></li><li><p><strong>隔离性(isolation):</strong>所有事情当出现在并发的场景下都会变得复杂。通常来说,一个事务所做的修改在最终提交之前,对其他事务是不可见的。在真实地数据库系统中,不尽然是这样的,可以通过设置不同的<strong>隔离级别(isolation level)</strong>来调整不同事务操作对彼此的可见性。</p></li><li><p><strong>持久性(durability):</strong>一旦事务提交,其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。</p></li></ul><p>并不是所有存储引擎都支持事务,比如MyISAM就不支持事务。</p><h3 id="隔离性与隔离级别"><a href="#隔离性与隔离级别" class="headerlink" title="隔离性与隔离级别"></a>隔离性与隔离级别</h3><p>当数据库中有多个事务同时执行的时候,如果不加控制,就会出现各种各样的问题:</p><ul><li><p><strong>脏读(dirty read):</strong>当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中的时候,另一个事务也访问了这个数据<strong>(脏数据)</strong>。</p></li><li><p><strong>不可重复读(non-repeatable read):</strong>是指在一个事务中,多次读同一个数据。两次读取之间,另一个事务修改了数据,造成第一个事务两次读取数据不一致的情况。</p></li><li><p><strong>幻读(phantom read):</strong>当保证了可重复读后,事务进行过程中查询的结果都是事务开始时的状态。但是如果另一个事务提交了数据,本事务再更新时,就可能会出现错误,就好像之前读到的数据是“鬼影”一样的幻觉似的。比如,第一个事务对一个表中的数据进行了修改或读取,这种修改或读取涉及到表中所有的数据行。同时,第二个事务向表中插入了一行新数据,就会发生第一个事务发现有行没有被修改的情况。</p></li></ul><p>为了解决这些问题,就有了<strong>隔离级别</strong>的概念,SQL的标准隔离级别有:</p><ul><li><p><strong>读未提交(read uncommitted):</strong>一个事务还没提交时,它做的变更就能被其它事务看到(这不就相当于没隔离吗),所有的问题都可能发生。</p></li><li><p><strong>读提交(read committed):</strong>一个事务提交之后,它做的变更才能被其它事务看到。读提交可以解决脏读的问题。</p></li><li><p><strong>可重复读(repeatable read):</strong>一个事务在执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。可重复读解决了脏读和不可重复读的问题但是没有解决幻读的问题。可重复读是MySQL的<strong>默认事务隔离级别</strong>。</p></li><li><p><strong>串行化(serializable):</strong>强制让事务串行执行,最高的隔离级别。</p></li></ul><p>随着隔离级别的提升,出现并发问题的可能性在减小,可是并发性能也在降低。很多时候都是个权衡的问题。</p><h2 id="多版本并发控制——MVCC"><a href="#多版本并发控制——MVCC" class="headerlink" title="多版本并发控制——MVCC"></a>多版本并发控制——MVCC</h2><p>MVCC是一种提高并发的技术。在最早的数据库系统中,只有读读之间可以并发,读写,写读,写写都是要阻塞的。引入MVCC之后,只有写写之间是相互阻塞的,其它三种操作都可以并行,大幅提高了InnoDB的并发度。在多事务环境下,对数据读写在不加读写锁的情况下实现互补干扰,<strong>从而实现了数据库的隔离性</strong>。</p><h3 id="MVCC的实现"><a href="#MVCC的实现" class="headerlink" title="MVCC的实现"></a>MVCC的实现</h3><h4 id="Undo-Log"><a href="#Undo-Log" class="headerlink" title="Undo Log"></a>Undo Log</h4><p>undo log主要用于存放数据被修改前的值。undo log分为两类,一种是INSERT_UNDO,记录插入的唯一键值;一种是UPDATE_UNDO,包括UPDATE及DELETE操作,记录修改的唯一键值以及old column记录。undo log主要由两个作用:</p><ul><li>事务回滚</li><li>MVCC多版本控制</li></ul><p>InnoDB以聚簇索引的方式存储数据,MySQL默认为每个索引添加了4个隐藏的字段。</p><p>其中</p><ul><li><strong>DB_ROLL_PTR</strong>:是undo log的指针,用于记录之前的历史数据在undo log中的位置。undo log中的历史数据行中的<code>DB_ROLL_PTR</code>指向的是上一次修改的行的undo log。这样多次更新后,回滚指针会把不同的版本的记录串在一起。</li><li><strong>DB_ROW_ID</strong>:是如果没有定义主键和合适的键做主键的时候,MySQL自己生成的一个隐藏的主键。</li><li><strong>DB_TRX_ID</strong>:是最近更改改行数据的事务ID。</li><li><strong>DELETE_BIT</strong>:是索引删除标志,如果DB删除了一条数据,是优先通知索引将这个标志位设置为1,然后通过清除线程去异步删除真实的数据</li></ul><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="mvcc3.png" alt="索引隐藏字段.png" title> </div> <div class="image-caption">索引隐藏字段.png</div> </figure><p>整个innoDB的MVCC机制都是通过<code>DB_ROLL_PTR</code>和<code>DB_TRX_ID</code>这两个字段实现的。</p><h4 id="Read-View"><a href="#Read-View" class="headerlink" title="Read View"></a>Read View</h4><p>由于undo log的存在,数据的多个版本得以保存。这就给事务隔离的实现创造了条件,对于RR和RC隔离级别的事务,对数据进行访问之前都需要对数据的可见性进行判断,也就是说需要判断当前事务是否能看到这一行数据,看到的应该是哪个版本的数据。这些功能的实现依赖于Read View对象。</p><p>首先,当一个事务开始的时候,会将当前数据库中正在活跃的所有事务(执行begin,但是还没有commit的事务)保存到一个叫做<code>trx_sys</code>的事务链表中,事务链表中保存的都是未提交的事务,当事务提交之后会从中删除。</p><figure class="image-bubble"> <div class="img-lightbox"> <div class="overlay"></div> <img src="trx_sys.png" alt="活跃事务链表.png" title> </div> <div class="image-caption">活跃事务链表.png</div> </figure><p>Read View的初始化相当于给当前的<code>trx_sys</code> 打一个快照。</p><ul><li>活跃事务链表(<code>trx_sys</code>)中事务id最大的值被赋值给<code>m_low_limit_id</code>。</li><li>活跃事务链表中第一个值(也就是事务id最小)被赋值给<code>m_up_limit_id</code>。</li><li><code>m_ids</code> 为事务链表。</li></ul><figure class="highlight c++"><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="comment">// readview 初始化</span></span><br><span class="line"><span class="comment">// m_low_limit_id = trx_sys->max_trx_id; </span></span><br><span class="line"><span class="comment">// m_up_limit_id = !m_ids.empty() ? m_ids.front() : m_low_limit_id;</span></span><br><span class="line">ReadView::ReadView()</span><br><span class="line"> :</span><br><span class="line"> m_low_limit_id(),</span><br><span class="line"> m_up_limit_id(),</span><br><span class="line"> m_creator_trx_id(),</span><br><span class="line"> m_ids(),</span><br><span class="line"> m_low_limit_no()</span><br><span class="line">{</span><br><span class="line"> ut_d(::<span class="built_in">memset</span>(&m_view_list, <span class="number">0x0</span>, <span class="keyword">sizeof</span>(m_view_list)));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过这个Read View,事务就可以根据查询到的所有记录的<code>DB_TRX_ID</code>来匹配是否能看见该记录。</p><ul><li>当检索到的数据的事务ID(数据事务ID < <code>m_up_limit_id</code>) 小于事务链表中的最小值表示这个数据在当前事务开启前就已经被其他事务修改过了,所以是可见的。</li><li>当检索到的数据的事务ID(数据事务ID = <code>m_creator_trx_id</code>) 表示是当前事务自己修改的数据。</li><li>当检索到的数据的事务ID(数据事务ID >= <code>m_low_limit_id</code>) 大于事务链表中的最大值表示这个数据在当前事务开启之前又被其他的事务修改过,那么就是不可见的。</li><li>如果事务ID落在(<code>m_up_limit_id</code>,<code>m_low_limit_id</code>),需要在活跃读写事务数组查找事务ID是否存在,如果存在,记录对于当前read view是不可见的。</li></ul><p><strong>如果记录对于view不可见,需要通过记录的<code>DB_ROLL_PTR</code>指针遍历history list构造当前view可见版本数据</strong>。</p><p>上面基本上是对于聚簇索引的情况,对于二级索引,在二级索引页中存储了更新当前页的最大事务ID,<strong>如果该事务ID大于read view中的<code>m_up_limit_id</code>,那么就需要回聚簇索引判断记录可见性。</strong></p><p>RR隔离级别(除了Gap锁之外)和RC隔离级别的差别是创建read view的时机不同。<strong>RR隔离级别是在事务开始时刻,确切地说是第一个读操作创建read view的;RC隔离级别是在语句开始时刻创建read view的。</strong></p><p><strong>undo log什么时候会被删除呢?</strong></p><p>当该undo log关联的事务没有出现在其他事务的read view中时(事务已提交,且没有其他事务依赖当前事务),那么InnoDB引擎的后台清除线程(purge线程)会进行遍历删除undo log操作。</p><p>因此长事务会导致数据库中存在大量的undo log,占用大量的存储空间。所以<strong>应该尽量避免长事务</strong>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>一个月一篇的速度真的是醉了。。。未完待续</p>]]></content>
<summary type="html">
<h1 id="MySQL事务隔离和MVCC"><a href="#MySQL事务隔离和MVCC" class="headerlink" title="MySQL事务隔离和MVCC"></a>MySQL事务隔离和MVCC</h1><p>同样,把最近看的事务和锁的部分总结一下,这部
</summary>
<category term="MySQL" scheme="https://firecarrrr.github.io/categories/MySQL/"/>
</entry>
</feed>