-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
602 lines (320 loc) · 464 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>梧桐和风的博客</title>
<link href="/atom.xml" rel="self"/>
<link href="https://blog.wthfeng.com/"/>
<updated>2019-01-06T08:29:47.100Z</updated>
<id>https://blog.wthfeng.com/</id>
<author>
<name>Tonghe Wang</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>redis学习笔记-持久化</title>
<link href="https://blog.wthfeng.com/2019-01-06-Redis%E6%8C%81%E4%B9%85%E5%8C%96/"/>
<id>https://blog.wthfeng.com/2019-01-06-Redis持久化/</id>
<published>2019-01-06T08:13:00.000Z</published>
<updated>2019-01-06T08:29:47.100Z</updated>
<content type="html"><![CDATA[<h1 id="redis学习笔记-持久化"><a href="#redis学习笔记-持久化" class="headerlink" title="redis学习笔记-持久化"></a>redis学习笔记-持久化</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>redis持久化有两种方式:RDB和AOF。分别对应着全量复制和增量复制。深刻理解各自的实现方式及适用场景对redis的使用和运维十分重要。下面就分别介绍。</p><h2 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a>RDB持久化</h2><p>RDB持久化即将当前Redis实例中的数据快照全部保存的磁盘的过程。可手动触发,也可根据配置自动触发。</p><h3 id="手动触发"><a href="#手动触发" class="headerlink" title="手动触发"></a>手动触发</h3><p>手动触发有两个命令可以选择: <code>save</code>和<code>bgsave</code>。两者区别在于<code>save</code>是阻塞的,复制完成前会阻塞客户端命令执行,目前已经废弃。<code>bgsave</code>是非阻塞的。执行过程中redis进程会fork出一个子进程负责复制任务,而主进程依然响应客户端请求命令。对应<code>bgsave</code>命令,阻塞只存在于<code>fork</code>过程中。大大减小了阻塞的时间。</p><p>如图,上次rdb同步时间在17:20</p><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChatf23994fda146eef357cdc94da5abc642.png" alt></p><p>执行命令<code>bgsave</code>后, 返回 <code>Background saving started</code> ,</p><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChat5bb07d90c280fa760e9bb9c6e85fddef.png" alt></p><p>查看rdb文件更新时间,已变为当前时间。</p><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChatecd20b2ed4f6cf03e5eceeede1b4fd70.png" alt></p><h3 id="自动触发"><a href="#自动触发" class="headerlink" title="自动触发"></a>自动触发</h3><p>若使用自动触发,需配置save参数。格式 <code>save m n</code>。查看redis配置文件可看到相关配置。</p><figure class="highlight plain"><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">save 900 1</span><br><span class="line">save 300 10</span><br><span class="line">save 60 10000</span><br></pre></td></tr></table></figure><p>配置 <code>save m n</code> 含义为当数据在m秒内发生n次修改,自动执行<code>bgsave</code>命令进行RDB持久化。如<code>save900 1</code> 表示在900秒(15分钟内)数据有至少1次修改,则触发RDB。多个配置情况下,只要符合任一条件即可。</p><p>具体在实现上,redis服务器会记录自上次RDB备份后,有多少次修改。每100ms检查一次,看是否有符合条件的save配置,有则再次执行DRB.</p><p>另外,当在客户端执行关闭服务器命令<code>shutdown</code>,若redis没有开启AOF,则自动执行<code>bgsave</code>进行备份。</p><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChat610ff13a7239eaa47d6a4a4c18797699.png" alt></p><h3 id="RDB流程"><a href="#RDB流程" class="headerlink" title="RDB流程"></a>RDB流程</h3><p>由于save的持久化命令会发送阻塞,所以目前基本是用bgsave命令触发的RDB,关于bgsave的流程如下图所示。</p><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChat8c79461dc66d79c5ca0dba0097296469.png" alt></p><p>简要流程如下</p><ol><li>redis进程(即图中主进程)收到<code>bgsave</code>命令后,调用fork命令创建子进程。期间主进程是阻塞的(图中1,2)。</li><li>主进程创建子进程完成后,返回<code>Background saving started</code>,不再阻塞,照常响应客户端命令。(图中3)</li><li>子进程开启RDB复制任务,根据<strong>主进程内存快照</strong>进行备份。(图4)</li><li>子进程完成后通知主进程,RDB任务完成。(图5)</li></ol><p><strong>简要来说是父进程fork出了一个子进程用于持久化任务,父进程仍响应客户端请求,耗时的RDB让子进程来做</strong>。这样的确可以大大减少阻塞时间,可有一个问题是,主进程还在接受请求,子进程怎样进行复制?难道子进程再复制一份主进程的内存用于复制,这样内存岂不是要翻倍?肯定不能这样。要是子进程共享父进程的内存,那怎样保证边接受请求边复制呢?</p><p>事实上,fork操作使用写时复制(copy-on-write)技术实现。创建的子进程共享父进程地址空间,当只有需要写入时才会复制地址空间。也就是说,redis的子进程使用共享的内存快照完成RDB备份,而主进程当收到写请求后,会将涉及到的内存页复制出一份副本,在此副本进行修改。当子进程RDB完成后,通知主进程更换副本,RDB就此完成。</p><h3 id="RDB参数设置及注意点"><a href="#RDB参数设置及注意点" class="headerlink" title="RDB参数设置及注意点"></a>RDB参数设置及注意点</h3><ol><li>RDB文件路径由<code>dir</code>设置,文件名由<code>dbfilename</code>指定。RDB文件为二进制文件,可开启压缩处理,压缩后文件体积可大大缩小。使用<code>rdbcompression</code>参数指定,默认为<code>yes</code>。</li><li>RDB文件表示某时刻的redis内存快照,文件也相对较小,适用于备份,全量复制等场景。不适合实时持久化。</li></ol><p>以下是redis配置文件有关rdb的默认配置</p><figure class="highlight plain"><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">## 是否开启rdb文件压缩</span><br><span class="line">rdbcompression yes</span><br><span class="line"></span><br><span class="line">## rdb文件效验</span><br><span class="line">rdbchecksum yes</span><br><span class="line"></span><br><span class="line">## rdb文件名</span><br><span class="line">dbfilename redis-dump-127.0.0.1-6379.rdb</span><br><span class="line"></span><br><span class="line">## 备份文件储存目录</span><br><span class="line">dir /usr/local/var/db/redis/</span><br></pre></td></tr></table></figure><h2 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a>AOF持久化</h2><p>AOF主要用于解决redis实时持久化的问题。方式为实时将redis所有写命令记录下来,用于以后恢复及备份。</p><h3 id="开启AOF"><a href="#开启AOF" class="headerlink" title="开启AOF"></a>开启AOF</h3><p>aof默认是关闭的,使用<code>appendonly yes</code>开启。用<code>appendfilename</code>设置aof文件名。同rdb一样,<code>dir</code>表示aof储存目录。</p><p>在配置文件设好后重启redis,即开启了aof功能。可使用<code>info persistence</code>可查看redis持久化的参数,如下</p><blockquote><p>aof_enabled:1</p></blockquote><p>表示已开启aof功能。</p><h3 id="AOF流程"><a href="#AOF流程" class="headerlink" title="AOF流程"></a>AOF流程</h3><p>AOF写入日志的直接就是文本格式。如<code>set hello world</code>这个命令,在<code>appendonly.aof</code>文件中储存的就是</p><figure class="highlight plain"><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">*3</span><br><span class="line">$3</span><br><span class="line">SET</span><br><span class="line">$5</span><br><span class="line">hello</span><br><span class="line">$5</span><br><span class="line">world</span><br></pre></td></tr></table></figure><p>这就是Redis客户端与服务器通信的的序列化协议(RESP),每行用<code>\r\n</code>分割,*n表示参数个数,$n表示字节数,十分简单明了。AOF直接使用协议格式可让redis直接识别。无需特殊处理,避免二次处理的开销,也方便修改。</p><p>AOF的流程如下</p><ol><li>每当遇到写命令时,执行完命令后,会将此命令再写入一个由AOF维护的缓存区(aof_buf)。</li><li>AOF缓冲区根据指定策略将数据刷到硬盘保存落地。</li></ol><p>用流程图表示就是</p><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChate6360e05fec4996a204bc5d12950e818.png" alt></p><p>为什么不直接将命令写入磁盘很容易理解,这里需关注的是以什么策略从缓冲区写到磁盘。redis提供了3种策略。</p><table><thead><tr><th>策略</th><th>说明</th></tr></thead><tbody><tr><td>always</td><td>每次命令写入缓冲区后都同步刷新到硬盘(使用fsync命令刷新磁盘),此方式最安全,但也是最慢的,因为每次写数据都要同步硬盘</td></tr><tr><td>everysec</td><td>每次写命令调用系统命令write操作,write只会将数据写入系统缓冲区,然后由专门的线程每秒同步刷盘</td></tr><tr><td>no</td><td>每次写命令调用系统命令write操作写入系统缓冲区,具体刷盘时间由操作系统决定,性能是最高的,但数据安全性不能保证</td></tr></tbody></table><p>通常,我们选择<code>everysec</code>作为刷盘策略,也是redis默认配置,可以兼顾性能和数据安全。</p><h3 id="AOF追加阻塞"><a href="#AOF追加阻塞" class="headerlink" title="AOF追加阻塞"></a>AOF追加阻塞</h3><p>一般我们使用<code>everysec</code>的同步刷盘策略。此时会有一个专门的线程每秒执行一次刷盘操作。为保证数据安全性,redis主进程会比对上次刷盘时间与当前时间的差值,如果大于2秒,则阻塞以等待刷盘完成。</p><p>也就是说,如果硬盘性能很差,<code>fsync</code>执行太慢,会造成redis阻塞。以保证硬盘的数据不被真实内存数据落的太远(最大2秒的数据差)。</p><h3 id="AOF重写机制"><a href="#AOF重写机制" class="headerlink" title="AOF重写机制"></a>AOF重写机制</h3><p>刚才提到,AOF直接使用redis的序列化协议进行备份,一段时间后,aof文件会很大。为解决此问题,可配置aof重写机制对原aof文件进行瘦身。</p><p>AOF重写的实质即把redis进程的内存数据转为写命令重新生成一份aof文件,以替代原aof文件。</p><p>重写后比原aof体积小的原因有以下几点</p><ol><li>过期数据不再写入</li><li>命令可合并(<code>hset k1 f1 v1</code>,<code>hset k1 f2 v2</code>可合并<code>hmset k1 f1 v1 f2 v2</code>)</li><li>重写直接使用内存数据生成,一些无效命令就不会再写入了。(如<code>set hello a, set hello b</code>等)</li></ol><h3 id="触发方式"><a href="#触发方式" class="headerlink" title="触发方式"></a>触发方式</h3><p>AOF可有手动触发和自动触发。</p><p>手动触发使用<code>bgrewriteaof</code>命令触发。</p><p>自动触发有关参数及含义如下</p><figure class="highlight plain"><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">## aof重写时此时aof文件与上次aof文件大小百分比</span><br><span class="line">auto-aof-rewrite-percentage 100</span><br><span class="line"></span><br><span class="line">## aof重写时文件最小大小,默认64M</span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br></pre></td></tr></table></figure><p>如按默认配置,则发生重写的条件为aof文件大于64M且此时aof文件比上次重写时大100%,即是上次的2倍。</p><h3 id="重写流程"><a href="#重写流程" class="headerlink" title="重写流程"></a>重写流程</h3><p><img src="http://img.wthfeng.com/img/wthfeng/redis/WeChat4d69e335c54790abe72e6aab1a167bef.png" alt></p><ol><li>redis主进程收到aof重写命令(bgwriteaof),fork出子进程用于执行aof重写流程(图中1,2)。</li><li>主进程继续接收客户端请求,将请求写入aof_buf缓冲区。保证原有aof流程正常工作(图中3.1.1和3.1.2)。</li><li>同时,主进程还会将写命令写入aof_rewrite_aof(aof重写缓冲区),用于补偿aof重写期间丢失的命令(图中3.2)。</li><li>子进程与此同时进行aof重写过程(图中3.3)</li><li>子进程完成aof重写后信号通知主进程(图中4)</li><li>主进程收到子进程完成信号后,将aof_rewrite_aof的命令写入新aof文件(图中5)</li><li>新aof文件原子替换旧aof文件,aof重写流程结束(图中6)。</li></ol><p>aof重写虽然比较复杂,但对比图,也能很容易明白其中流程。这这里有个疑问是为什么主进程在子进程重写时为什么要维护两个缓冲区?只维护一个不行吗?毕竟原有aof文件等到新aof文件生成后也是要替换的。</p><p>仔细想想就可知,有必要维护两个缓冲区。主要是需要维护原有aof逻辑不变,以防aof重写失败导致数据丢失。另一点两个缓冲区目的也不同,不宜混淆。</p><p>注意:</p><blockquote><p>为防止资源过于竞争,同一时刻只能进行一个AOF重写任务,当进行重写时发现有RDB任务时,也会等RDB完成后进行。</p></blockquote><h2 id="RDB与AOF"><a href="#RDB与AOF" class="headerlink" title="RDB与AOF"></a>RDB与AOF</h2><h3 id="加载顺序"><a href="#加载顺序" class="headerlink" title="加载顺序"></a>加载顺序</h3><p>在重启时,redis会首先判断是否开启aof,若开启aof且aof文件存在,则加载aof文件进行数据恢复以启动。否则判断rdb文件存在并加载rdb以启动。</p><h3 id="性能开销"><a href="#性能开销" class="headerlink" title="性能开销"></a>性能开销</h3><p>在进行RDB备份或是AOF重写时,都需要fork出子进程运行任务。此期间会大量消耗CPU,通常子进程对单核CPU的利用率达到90%,因此在有RDB或需AOF重写任务的redis,不要做绑定单核CPU操作,这会导致父子进程对CPU产生竞争。</p><h3 id="RDB与AOF比较"><a href="#RDB与AOF比较" class="headerlink" title="RDB与AOF比较"></a>RDB与AOF比较</h3><ol><li>RDB是全量复制的持久化方式,一次性生成内存快照,产生二进制文件。文件读取速度快,生成较慢。适用于数据冷备。</li><li>AOF通过追加生成持久化文件,体积较大,用于数据实时备份。</li><li>redis阻塞的场景在fork子进程和AOF的追加阻塞阶段。</li></ol><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol><li><a href="https://book.douban.com/subject/26971561/" target="_blank" rel="noopener">Redis开发与运维</a></li><li><a href="https://book.douban.com/subject/25900156/" target="_blank" rel="noopener">Redis设计与实现</a></li><li><a href="https://www.cnblogs.com/wuchanming/p/4495479.html" target="_blank" rel="noopener">Linux进程管理——fork()和写时复制</a></li></ol>]]></content>
<summary type="html">
<h1 id="redis学习笔记-持久化"><a href="#redis学习笔记-持久化" class="headerlink" title="redis学习笔记-持久化"></a>redis学习笔记-持久化</h1><h2 id="前言"><a href="#前言" cla
</summary>
<category term="redis" scheme="https://blog.wthfeng.com/categories/redis/"/>
<category term="redis" scheme="https://blog.wthfeng.com/tags/redis/"/>
<category term="cache" scheme="https://blog.wthfeng.com/tags/cache/"/>
<category term="db" scheme="https://blog.wthfeng.com/tags/db/"/>
</entry>
<entry>
<title>flume简要说明</title>
<link href="https://blog.wthfeng.com/2018-11-18-flume%E7%AE%80%E8%A6%81%E8%AF%B4%E6%98%8E/"/>
<id>https://blog.wthfeng.com/2018-11-18-flume简要说明/</id>
<published>2018-11-18T12:45:00.000Z</published>
<updated>2018-11-18T12:45:40.076Z</updated>
<content type="html"><![CDATA[<h1 id="Flume简要说明笔记"><a href="#Flume简要说明笔记" class="headerlink" title="Flume简要说明笔记"></a>Flume简要说明笔记</h1><p>## 前言</p><p>flume是Apache旗下的开源日志收集、传输框架(由Cloudera于2009年捐赠)。可支持收集来自文本、HDFS、HBase等多种数据源的日志记录,并可传输到多种数据源。由于其稳定的性能及表现,目前已被广泛采用。</p><h2 id="主体介绍"><a href="#主体介绍" class="headerlink" title="主体介绍"></a>主体介绍</h2><p>Event(事件)是Flume的基本传输单元,可理解为传输的数据如日志等。</p><p>Agent是Flume的核心运行逻辑。一个Agent就是一个完整的数据收集工具(表现为一个JVM)。包含3个主要组件:Source、Channel、Sink。如下图数据流程模型所示,数据从外部流入Flume(Agent),分别经过3个组件流转后被送到目的数据源。下面是其介绍:</p><h3 id="Source"><a href="#Source" class="headerlink" title="Source"></a>Source</h3><blockquote><p>数据收集端,负责将数据封装成Event,后传输给Channel。</p></blockquote><p>Flume提供了各种source的实现,包括Avro Source、 Exce Source、Spooling Directory Source、 NetCat Source、 Syslog Source、 Syslog TCP Source、Syslog UDP Source、 HTTP Source、 HDFS Source等,下面列举一些常用的Source</p><table><thead><tr><th>Source</th><th>说明</th><th>必填属性示例</th></tr></thead><tbody><tr><td>Avro Source</td><td>支持Avro协议的数据源</td><td>type=avro,bind=127.0.0.1,port=7777</td></tr><tr><td>Exce Source</td><td>通过运行unix命令收集数据源,如 <code>tail -F</code>等</td><td>type=exec,command=xxx</td></tr><tr><td>Spooling Directory Source</td><td>监听指定目录的文件变化收集日志,这里特别需注意: <strong>一旦文件移到flume指定监视目录里,文件就不能再变化了,否则Flume会失败退出</strong></td><td>type=spooldir,spoolDir=path</td></tr><tr><td>NetCat Source</td><td>监听tcp端口数据</td><td>type=netcat,bind=127.0.0.1,port=7777</td></tr></tbody></table><h3 id="Channel"><a href="#Channel" class="headerlink" title="Channel"></a>Channel</h3><blockquote><p>数据传输队列,连接Source和Sink,可理解为缓冲区。channel可将数据暂存在内存或是磁盘中,直到Sink处理完将该数据再删除。</p></blockquote><p>channel可分为Memory Channel(内存channel)、FileChannel(文件Channel)等。顾名思义,内存Channel将数据存于内存,FileChannel存于磁盘文件中。还有Spillable Memory Channel,内存满了向磁盘存储,此channel正处于试验性质,不建议生产使用。见<a href="http://flume.apache.org/FlumeUserGuide.html#spillable-memory-channel" target="_blank" rel="noopener">Flume Spillable Memory Channel</a></p><h3 id="Sink"><a href="#Sink" class="headerlink" title="Sink"></a>Sink</h3><blockquote><p>从Channel中取出事件,再将其发送出去,是Flume的出口位置。如可将数据发往HDFS、DB、Kafka等。</p></blockquote><p>下列是支持的输出源:</p><ul><li>DHFS Sink</li><li>Logger Sink</li><li>Avro Sink</li><li>Thrift Sink</li><li>Null Sink</li><li>HBase Sink</li><li>ElasticSearch Sink</li><li>Custom Sink :自定义sink</li></ul><p>Flume数据流程如下所示</p><p><img src="https://img-blog.csdnimg.cn/20181118202757248.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=,size_16,color_FFFFFF,t_70" alt></p><h2 id="Flume配置项"><a href="#Flume配置项" class="headerlink" title="Flume配置项"></a>Flume配置项</h2><p>选用exec方式监听日志的方式,收集日志后送到kafka中。配置如下:</p><figure class="highlight plain"><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><br><span class="line"># 配置项,定义源,通道以及sink</span><br><span class="line">agent.sources = mysource</span><br><span class="line">agent.channels = mychannel</span><br><span class="line">agent.sinks = mysink</span><br><span class="line"></span><br><span class="line"># 定义数据源</span><br><span class="line"># 数据源类型,unix的二进制命令</span><br><span class="line">agent.sources.mysource.type = exec</span><br><span class="line"># 执行命令</span><br><span class="line">agent.sources.mysource.command = tail -F /usr/local/log/spark/streaming/spark-stream.log</span><br><span class="line"># 数据源接收通道</span><br><span class="line">agent.sources.mysource.channels = mychannel</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">agent.channels.mychannel.type = memory</span><br><span class="line"># 在channel中最大存储的容量</span><br><span class="line">agent.channels.mychannel.capacity = 1000</span><br><span class="line"># 从通道中每次事务获取的最大数</span><br><span class="line">agent.channels.mychannel.transactionCapacity = 100</span><br><span class="line"></span><br><span class="line"># 定义数据下沉出口</span><br><span class="line"># 定义kafka类型</span><br><span class="line">agent.sinks.mysink.type = org.apache.flume.sink.kafka.KafkaSink</span><br><span class="line"># 要连接的kafka服务器</span><br><span class="line">agent.sinks.mysink.kafka.bootstrap.servers = 127.0.0.1:9092</span><br><span class="line"># kafka 主题</span><br><span class="line">agent.sinks.mysink.kafka.topic = test</span><br><span class="line"># 上游channel 名称</span><br><span class="line">agent.sinks.mysink.channel = mychannel</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="Flume简要说明笔记"><a href="#Flume简要说明笔记" class="headerlink" title="Flume简要说明笔记"></a>Flume简要说明笔记</h1><p>## 前言</p>
<p>flume是Apache旗下的开源日志收集
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="flume" scheme="https://blog.wthfeng.com/tags/flume/"/>
<category term="kafka" scheme="https://blog.wthfeng.com/tags/kafka/"/>
</entry>
<entry>
<title>netty架构浅析</title>
<link href="https://blog.wthfeng.com/2018-09-17-netty%E6%A6%82%E8%A6%81%E8%AE%B2%E8%A7%A3/"/>
<id>https://blog.wthfeng.com/2018-09-17-netty概要讲解/</id>
<published>2018-09-17T09:45:00.000Z</published>
<updated>2018-10-28T05:18:10.315Z</updated>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>netty是使用java编写的高性能IO框架,旨在为高并发场景提供支持。netty可提供多种IO模型的支持,如OIO,NIO等。一般来说,非阻塞IO更适合于大规模高并发场景,我们使用netty主要也因为其封装了原生NIO,规避了其中复杂易出错的细节,更加易用、通用。</p><h2 id="从示例讲起"><a href="#从示例讲起" class="headerlink" title="从示例讲起"></a>从示例讲起</h2><p>netty既然是以java NIO为基础构建的(当然添加了大量特性),那就不能不了解java NIO的处理方式。NIO实现非阻塞的关键在于Selector(选择器)以及通道。下面先复习一下nio的示例,然后再对比netty。</p><p>java nio 示例</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> ServerSocketChannel ssc = ServerSocketChannel.open();</span><br><span class="line"> Selector selector = Selector.open();</span><br><span class="line"> ssc.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> ssc.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">8080</span>));</span><br><span class="line"> <span class="comment">// ①将服务器的channel注册到选择器</span></span><br><span class="line"> ssc.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="keyword">try</span> {</span><br><span class="line"> <span class="comment">//阻塞,至少一个连接到来时才会继续</span></span><br><span class="line"> selector.select();</span><br><span class="line"> Set<SelectionKey> selectionKeys = selector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> it = selectionKeys.iterator();</span><br><span class="line"> <span class="keyword">while</span> (it.hasNext()) {</span><br><span class="line"> SelectionKey key = it.next();</span><br><span class="line"> it.remove();</span><br><span class="line"> <span class="comment">// 连接进入</span></span><br><span class="line"> <span class="keyword">if</span> (key.isAcceptable()) {</span><br><span class="line"> ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();</span><br><span class="line"> <span class="comment">// ② 服务器接受连接,创建客户端的channel,然后注册到选择器(Selector)</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"> } <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) {</span><br><span class="line"> <span class="comment">// ③ 客户端的channel</span></span><br><span class="line"> SocketChannel sc = (SocketChannel) key.channel();</span><br><span class="line"> ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> <span class="keyword">int</span> count = sc.read(byteBuffer);</span><br><span class="line"> <span class="keyword">if</span> (count < <span class="number">0</span>) {</span><br><span class="line"> key.cancel();</span><br><span class="line"> sc.close();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> byteBuffer.flip(); <span class="comment">//切换到读模式</span></span><br><span class="line"> String msg = Charset.forName(<span class="string">"UTF-8"</span>).decode(byteBuffer).toString();</span><br><span class="line"> System.out.println(<span class="string">"received from: "</span> + msg);</span><br><span class="line"> sc.write(ByteBuffer.wrap(msg.getBytes(CharsetUtil.UTF_8)));</span><br><span class="line"> }</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></pre></td></tr></table></figure><p>示例很简单,就是该服务器接受来自客户端的连接,并打印客户端的信息。解释如下:</p><ol><li>selector为选择器,即可以把要关注的事件注册到这里来。待该事件发生时,可给与通知。如将表示与服务器相连的serverSocketChannel注册,等有新连接过来后(accept事件),会通知该channel。</li><li>①处即为向选择器注册服务器channel及需关注的accept事件。</li><li>②处为向选择器注册接收的客户端channel,及关注的read事件</li><li>③处为客户端channel的read事件,处理read事件</li><li>从上面我们看出,selector注册了两种channel。一种是服务器channel,一种是客户端channel。前者只有一个,后者却很多,来一个请求便创建一个。且后者是前者在②处创建出来的。这两种channel有种父子关系的特征,后面netty就是用了这种概念表示。</li></ol><p>下面看看netty的示例。学之前以为netty的非阻塞是以nio为基础创建的,应该差不多。看过来发现,果然,一点也不一样。</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="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NettyServer</span> </span>{</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 class="keyword">throws</span> Exception </span>{</span><br><span class="line"> EventLoopGroup group = <span class="keyword">new</span> NioEventLoopGroup();</span><br><span class="line"> <span class="keyword">final</span> ByteBuf buf = Unpooled.copiedBuffer(<span class="string">"Hi!\r\n"</span>, Charset.forName(<span class="string">"UTF-8"</span>));</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//服务端的引导类</span></span><br><span class="line"> ServerBootstrap serverBootstrap = <span class="keyword">new</span> ServerBootstrap();</span><br><span class="line"> <span class="comment">// 设置线程组</span></span><br><span class="line"> serverBootstrap.group(group)</span><br><span class="line"> <span class="comment">// 设置非阻塞channel</span></span><br><span class="line"> .channel(NioServerSocketChannel.class)</span><br><span class="line"> <span class="comment">// 设置绑定本地的端口</span></span><br><span class="line"> .localAddress(<span class="keyword">new</span> InetSocketAddress(<span class="number">8080</span>))</span><br><span class="line"> <span class="comment">// 设置</span></span><br><span class="line"> .childHandler(<span class="keyword">new</span> ChannelInitializer<SocketChannel>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> ch.pipeline().addLast(<span class="keyword">new</span> ChannelInboundHandlerAdapter() {</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">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> ByteBuf byteBuf = (ByteBuf) msg;</span><br><span class="line"> String text = CharsetUtil.UTF_8.decode(byteBuf.nioBuffer()).toString();</span><br><span class="line"> System.out.println(<span class="string">"接受到的消息:"</span> + text);</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"> ChannelFuture f = serverBootstrap.bind().sync();</span><br><span class="line"> f.channel().closeFuture().sync();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> group.shutdownGracefully().sync();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>解释一下</p><ol><li>netty和java原生nio实现方式相当不一样,它将nio和oio的实现方式做了统一,所以,上面非阻塞式的代码,只需改动一点即可实现oio的方式。</li><li>从概念上来讲,Bootstrap(引导器)的说法在java nio上是没有的,它相当于一个用于集成引导配置的容器。有ServerBootstrap(用于服务器)和BootStrap(用于客户端)。</li><li>EventLoopGroup和EventLoop很重要。EventLoopGroup用于管理多个EventLoop,而EventLoop关联一个线程。同时EventLoop又充当选择器(Selector)的角色。用于选取已注册的准备好的事件。</li><li>还有一个点是childHandler,用于设置处理接收而来的客户端channel。而handler,则用与设置服务器Channel。</li></ol><h2 id="netty流程浅析"><a href="#netty流程浅析" class="headerlink" title="netty流程浅析"></a>netty流程浅析</h2><p>你可以以理解java nio的方式理解netty。ServerBootstrap作为服务端的引导类,作用为串联配置,启动服务器。EventLoop是netty中的重要部件,有java nio中的选择器的功能,可以选择就绪的channel,且自身关联一个Thread。看下图</p><p><img src="https://img-blog.csdn.net/20180917173924542?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><p>这图是从<a href="https://book.douban.com/subject/27038538/" target="_blank" rel="noopener">《netty实战》</a>中找的,可以简单概括出EventLoopGroup、EventLoop以及Channel的关系。</p><p>EventLoopGroup可在创建时指定EventLoop的个数,如图中为3个。同时,EventLoopGroup负责为每个新创建的Channel(客户端Channel)分配一个EventLoop。一般采用顺序循环的方式分配。如此,客户端连接一多,每个EventLoop就会负责多个Channel。EventLoop本身还关联着一个Thread。负责处理Channel的读或写等事件。每个Channel的整个生命周期的事件均由其关联的EventLoop的线程处理,这样可避免多线程环境下数据同步等问题。</p><p>对比java nio的选择器模型,可以发现一些相似之处。这里的selector同样负责多个channel的事件处理。</p><p><img src="https://img-blog.csdn.net/20180917173946566?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><p>当channel的某个事件准备好后,就可以根据业务需要处理这些数据了(或读或写等)。netty的处理流程对应的是一个处理链。ChannelPipeline。处理链上可添加若干个单个处理逻辑:ChannelHandler。这种处理方式使得处理逻辑简单清晰(如可将处理编解码的handler和序列化以及处理业务逻辑的代码分离开)。并且当需要改变处理流程时(如出站数据需要进行加密),只需动态添加(或移除)一个ChannelHandler即可。</p><p><img src="https://img-blog.csdn.net/20180917174003304?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><p>图中直观显示了ChannelPipeline和ChannelHandler的关系。上面示例中,设置childHandler即可设置一个ChannelHandler。</p><h2 id="启动流程"><a href="#启动流程" class="headerlink" title="启动流程"></a>启动流程</h2><p>ServerBootstrap作为server端的引导器,是串联整个流程的关键。前面也说过,netty的引导器分2种,服务端的(ServerBootStrap)和客户端的(Bootstrap)。其类继承关系如图</p><p><img src="https://img-blog.csdn.net/20180917174029322?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><p><img src="https://img-blog.csdn.net/20180917174040701?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><p>可见两者均继承了AbstractBootstrap,这里只分析ServerBootStrap。</p><p>ServerBootStrap的group方法用于设置EventLoopGroup。上面示例中类似于这样设置的。</p><blockquote><p>group(new NioEventLoopGroup())</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></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> ServerBootstrap <span class="title">group</span><span class="params">(EventLoopGroup group)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> group(group, group);</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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Set the {<span class="doctag">@link</span> EventLoopGroup} for the parent (acceptor) and the child (client). These</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> EventLoopGroup}'s are used to handle all the events and IO for {<span class="doctag">@link</span> ServerChannel} and</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> Channel}'s.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ServerBootstrap <span class="title">group</span><span class="params">(EventLoopGroup parentGroup, EventLoopGroup childGroup)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.group(parentGroup);</span><br><span class="line"> <span class="keyword">if</span> (childGroup == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">"childGroup"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.childGroup != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"childGroup set already"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.childGroup = childGroup;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><blockquote><p>这里需解释下parentGroup和childGroup的含义。parentGroup用于处理ServerSocketChannel对应的事件(也就是accept()事件),而childGroup用于处理客户端channel的读写等的事件。前面提过这两种channel有一种父子对应的关系,所以netty就这样做的命名。</p></blockquote><p>从源码可以看出,如果只设置一个group,则parentGroup和childGroup共用一个group。</p><p>目前来说,一般在引导器中主动设置两个EventLoopGroup,即</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">EventLoopGroup parentGroup = <span class="keyword">new</span> NioEventLoopGroup(<span class="number">1</span>);</span><br><span class="line">EventLoopGroup workerGroup = <span class="keyword">new</span> NioEventLoopGroup();</span><br><span class="line">ServerBootstrap b = <span class="keyword">new</span> ServerBootstrap();</span><br><span class="line">b.group(parentGroup, workerGroup);</span><br></pre></td></tr></table></figure><p>看一下<code>NioEventLoopGroup</code>类的构造器方法</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><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(<span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">(<span class="keyword">int</span> nThreads)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(nThreads, (Executor) <span class="keyword">null</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可知,传递的数字参数为线程数,跟踪代码知道,若不设线程数(无参),则最终为核心数的2倍。</p><figure class="highlight plain"><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">protected MultithreadEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,</span><br><span class="line"> Object... args) {</span><br><span class="line"> super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, chooserFactory, args);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(</span><br><span class="line"> "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));</span><br></pre></td></tr></table></figure><p>ServerBootstrap最关键的方法是<code>bind</code>方法。也是开启netty服务器的方法。其具体实现在父类<code>AbstractBootstrap</code>。<br>调用链为 doBind()-> initAndRegister()->init()。init()依靠子类实现。这也是模板方法的应用。看看init()方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">init</span><span class="params">(Channel channel)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 设置属性option及attr,略过</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取pipeline </span></span><br><span class="line"> ChannelPipeline p = channel.pipeline();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> EventLoopGroup currentChildGroup = childGroup;</span><br><span class="line"> <span class="keyword">final</span> ChannelHandler currentChildHandler = childHandler;</span><br><span class="line"> <span class="keyword">final</span> Entry<ChannelOption<?>, Object>[] currentChildOptions;</span><br><span class="line"> <span class="keyword">final</span> Entry<AttributeKey<?>, Object>[] currentChildAttrs;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 设置属性,略过</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 添加处理逻辑</span></span><br><span class="line"> p.addLast(<span class="keyword">new</span> ChannelInitializer<Channel>() {</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">initChannel</span><span class="params">(<span class="keyword">final</span> Channel ch)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">final</span> ChannelPipeline pipeline = ch.pipeline();</span><br><span class="line"> ChannelHandler handler = config.handler();</span><br><span class="line"> <span class="keyword">if</span> (handler != <span class="keyword">null</span>) {</span><br><span class="line"> pipeline.addLast(handler);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ch.eventLoop().execute(<span class="keyword">new</span> Runnable() {</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"> pipeline.addLast(<span class="keyword">new</span> ServerBootstrapAcceptor(</span><br><span class="line"> ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>init(channel)的参数channel要说明一下。其来源于NioServerSocketChannel,经反射得到的。</p><blockquote><p>serverBootstrap.group(group).channel(NioServerSocketChannel.class) </p></blockquote><p>也即这个channel是与服务器相关联的channel,这些代码为设置服务端channel的pipeline和handler。看看<code>ServerBootstrapAcceptor</code>。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ServerBootstrapAcceptor</span> <span class="keyword">extends</span> <span class="title">ChannelInboundHandlerAdapter</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//省略其他字段及方法</span></span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Channel child = (Channel) msg;</span><br><span class="line"></span><br><span class="line"> child.pipeline().addLast(childHandler);</span><br><span class="line"></span><br><span class="line"> setChannelOptions(child, childOptions, logger);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Entry<AttributeKey<?>, Object> e: childAttrs) {</span><br><span class="line"> child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> childGroup.register(child).addListener(<span class="keyword">new</span> ChannelFutureListener() {</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">operationComplete</span><span class="params">(ChannelFuture future)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">if</span> (!future.isSuccess()) {</span><br><span class="line"> forceClose(child, future.cause());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> forceClose(child, t);</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>ServerBootstrapAcceptor</code>继承了<code>ChannelInboundHandlerAdapter</code>。用于负责接收客户端的连接。当连接过来后注册到<code>childGroup</code>中。</p><blockquote><p>handler与childHandler的区别在于前者处理服务端handler,如接收新客户端连接;后者处理客户端连接,如客户端读写等事件。</p></blockquote><blockquote><p>文本为简要介绍netty流程,后续尝试逐步分析。若有问题还请指正。</p></blockquote><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://segmentfault.com/a/1190000007403873" target="_blank" rel="noopener">Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop(一)</a></li><li><a href="https://blog.csdn.net/bdmh/article/details/49945765" target="_blank" rel="noopener">Netty:EventLoopGroup</a></li><li><a href="https://my.oschina.net/lifany/blog/519600" target="_blank" rel="noopener">Netty 源码分析(三):服务器端的初始化和注册过程</a></li><li><a href="https://book.douban.com/subject/27038538/" target="_blank" rel="noopener">netty实战</a></li><li><a href="https://my.oschina.net/lifany/blog/517275" target="_blank" rel="noopener">Netty 源码解析(二):对 Netty 中一些重要接口和类的介绍</a></li></ol>]]></content>
<summary type="html">
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>netty是使用java编写的高性能IO框架,旨在为高并发场景提供支持。netty可提供多种IO模型的支持,如OIO,NIO等。一般来说,非
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="io" scheme="https://blog.wthfeng.com/tags/io/"/>
<category term="nio" scheme="https://blog.wthfeng.com/tags/nio/"/>
<category term="netty" scheme="https://blog.wthfeng.com/tags/netty/"/>
</entry>
<entry>
<title>java I/O体系总结(三) java NIO</title>
<link href="https://blog.wthfeng.com/2018-09-13-java%20IO%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%89%EF%BC%89java%20NIO/"/>
<id>https://blog.wthfeng.com/2018-09-13-java IO总结(三)java NIO/</id>
<published>2018-09-13T13:12:00.000Z</published>
<updated>2018-10-28T05:25:13.879Z</updated>
<content type="html"><![CDATA[<h2 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h2><table><thead><tr><th></th><th></th><th>IO</th><th>NIO</th></tr></thead><tbody><tr><td></td><td>特点</td><td>面向流</td><td>面向缓冲</td><td></td></tr><tr><td></td><td>是否阻塞</td><td>阻塞IO</td><td>非阻塞IO</td></tr><tr><td></td><td></td><td>无</td><td>选择器</td></tr></tbody></table><p>java 新IO主要部分:Buffer(缓冲区)、Channel(通道)、Selectors(选择器)</p><p>Java NIO的非阻塞模式,如使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。一个单独的线程可以管理多个输入和输出通道(channel)。</p><p>NIO中,所以操作都以缓冲区进行的。</p><p>Channel 表示通道,与流有一些类似,用于在字节缓冲区和位于通道另一侧的实体(文件或套接字)之间有效的传输数据。需注意的是,通道只接受ByteBuffer作为参数。</p><p>缓冲区是通道内部用来发送和接收数据的端点。</p><h3 id="Channel与流的区别:"><a href="#Channel与流的区别:" class="headerlink" title="Channel与流的区别:"></a>Channel与流的区别:</h3><ol><li>通道(Channel)既可以读取数据也可以写入数据,而流是单向的(如InputStream是输入流,OutputStream是输出流)</li><li>通道(Channel)不能直接访问数据,只能通过缓冲(Buffer)去访问。</li><li>通道只在字节缓冲区操作(因为操作系统都是以字节的形式实现底层I/O接口的)</li><li>流,就像水流一样,单向,流过去了就不会回来;而通道如其名,双向,可来可去,可读可写。</li></ol><p>通道</p><table><thead><tr><th>channel类别</th><th>说明</th></tr></thead><tbody><tr><td>FileChannel</td><td>文件通道</td></tr><tr><td>DatagramChannel</td><td>UDP通道,用于通过UDP读取网络中的数据通道</td></tr><tr><td>SocketChannel</td><td>TCP通道,用于通过TCP读取网络数据</td></tr><tr><td>ServerSocketChannel</td><td>监听新进来的TCP连接,对每个链接都创建一个SocketChannel</td></tr></tbody></table><p>以上4种channel大致可分为文件通道和套接字通道。文件通道指的是FileChannel,套接字通道则有三个,分别是SocketChannel、ServerSocketChannel和DatagramChannel。</p><h3 id="获取Channel的方法"><a href="#获取Channel的方法" class="headerlink" title="获取Channel的方法"></a>获取Channel的方法</h3><ol><li>通过getChannel()方法获取。<br>FileInputStream/FileOutputStream、Socket、DatagramSocket等类都有此方法。</li><li>静态open方法;如FileChannel.open()</li><li>Files.newByteChannel</li></ol><h2 id="FileChannel"><a href="#FileChannel" class="headerlink" title="FileChannel"></a>FileChannel</h2><p>先说说文件通道吧,</p><p>看其继承图</p><p><img src="http://img.wthfeng.com/img/wthfeng/java/io/20180913212203202.jpeg" alt="这里写图片描述"></p><p>FileChannel可通过FileInputStream或FileOutputStream或RandomAccessFile对象上调用getChannel()方法来获取。</p><p>得到的FileChannel拥有和file对象相同的访问权限。</p><p>FileChannel是线程安全的,多个进程可在同一实例上并发调用。</p><p>需注意的是,文件通道是阻塞的。FileChannel不能切换到非阻塞模式。而套接字通道都可以。</p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * FileChannel 读取</span><br><span class="line"> * @throws Exception</span><br><span class="line"> */</span><br><span class="line"> @Test</span><br><span class="line"> public void testChannel() throws Exception {</span><br><span class="line"></span><br><span class="line"> File file = new File("/Users/wangtonghe/local/tmp/hello.txt");</span><br><span class="line"></span><br><span class="line"> FileInputStream fileInputStream = new FileInputStream(file);</span><br><span class="line"> // 获取channel</span><br><span class="line"> FileChannel fileChannel = fileInputStream.getChannel(); </span><br><span class="line"> // 分配Buffer</span><br><span class="line"> ByteBuffer byteBuffer = ByteBuffer.allocate(40);</span><br><span class="line"> // 将文件内容读取出来</span><br><span class="line"> fileChannel.read(byteBuffer);</span><br><span class="line"> // 将channel设为可读状态</span><br><span class="line"> byteBuffer.flip();</span><br><span class="line"> while (byteBuffer.hasRemaining()) {</span><br><span class="line"> System.out.print((char) byteBuffer.get());</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testChannel2</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> File file = <span class="keyword">new</span> File(<span class="string">"/Users/wangtonghe/local/tmp/hello.txt"</span>);</span><br><span class="line"> FileOutputStream fileOutputStream = <span class="keyword">new</span> FileOutputStream(file);</span><br><span class="line"> FileChannel fileChannel = fileOutputStream.getChannel();</span><br><span class="line"> ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">20</span>);</span><br><span class="line"> String str = <span class="string">"asdfghjk\n"</span>;</span><br><span class="line"> byteBuffer.put(str.getBytes());</span><br><span class="line"> byteBuffer.flip();</span><br><span class="line"> fileChannel.write(byteBuffer);</span><br><span class="line"> byteBuffer.clear();</span><br><span class="line"> fileOutputStream.close();</span><br><span class="line"> fileChannel.close();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h3><p>Buffer有以下4个主要属性,用以控制读写状态。</p><table><thead><tr><th>属性</th><th>作用</th></tr></thead><tbody><tr><td>capacity</td><td>容量,指缓冲区能够容纳的数据元素的最大数量,这一容量在缓冲区创建时被设定,并且永远不能被改变</td></tr><tr><td>limit</td><td>上界,缓冲区中现存元素的边界。即不可读或不可写的位置</td></tr><tr><td>position</td><td>指示位置,缓冲区读取或写入的下一个位置。位置会自动由相应的get()和put()函数更新</td></tr><tr><td>mark</td><td>标记,指一个备忘位置,调用mark()来设定mark=position,调用reset()来设定postion=mark,标记未设定前是未定</td></tr></tbody></table><h4 id="flip方法"><a href="#flip方法" class="headerlink" title="flip方法"></a>flip方法</h4><p>很重要的方法,将Buffer从可写状态变为可读状态。</p><h4 id="直接缓冲区"><a href="#直接缓冲区" class="headerlink" title="直接缓冲区"></a>直接缓冲区</h4><p>直接缓冲区,避免了缓冲区在I/O上的复制。直接缓冲区使用的内存是直接调用操作系统分配的,绕过了JVM的堆栈结构。</p><p>可通过调用ByteBuffer.allocateDirect()分配。</p><h2 id="Socket通道"><a href="#Socket通道" class="headerlink" title="Socket通道"></a>Socket通道</h2><p>有关Socket的Channel主要有3个:ServerSocketChannel、SocketChannel、DatagramChannel。ServerSocketChannel表示服务器端的Socket通道,而SocketChannel表示客户端的Socket通道。DatagramChannel表示数据报(UDP)的通道。</p><p>Socket通道均支持非阻塞式连接。在介绍非阻塞前,首先看看阻塞式的Socket是怎样的</p><h3 id="阻塞式Socket"><a href="#阻塞式Socket" class="headerlink" title="阻塞式Socket"></a>阻塞式Socket</h3><p>这里讨论的阻塞非阻塞针对服务器端。阻塞式处理一般采用多线程的方式。使用ServerSocket(服务器端Socket)和Socket(客户端Socket)。步骤如下:</p><ol><li>调用ServerSocket的accept()方法,等待客户端连接,如没有连接此方法会一直阻塞,若有,返回与客户端通信的socket</li><li>根据1.中返回的socket获取IntputStream及OutputStream,以便与客户端进行通信。</li><li>通信完毕,关闭连接。</li><li>有新连接到来,重复2</li></ol><p>要了解非阻塞式IO,就要先了解阻塞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><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"> <span class="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 class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建服务器端</span></span><br><span class="line"> ServerSocket server = <span class="keyword">new</span> ServerSocket(<span class="number">8000</span>);</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="comment">// 在这阻塞,直到下一个请求到来</span></span><br><span class="line"> <span class="keyword">try</span> (Socket socket = server.accept()) {</span><br><span class="line"> <span class="comment">// 没有分线程处理,即请求串行执行,当前请求必须处理完毕才能处理下一个,</span></span><br><span class="line"> <span class="comment">// 若某个请求处理很慢,将直接影响后续所有请求</span></span><br><span class="line"> Reader reader = <span class="keyword">new</span> InputStreamReader(socket.getInputStream());</span><br><span class="line"> <span class="keyword">int</span> c;</span><br><span class="line"> <span class="keyword">while</span> ((c = reader.read()) != -<span class="number">1</span>) {</span><br><span class="line"> System.out.print((<span class="keyword">char</span>) c);</span><br><span class="line"> }</span><br><span class="line"> reader.close();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException ex) {</span><br><span class="line"> ex.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>是accept()方法阻塞才被认为是阻塞IO吗?可是,accept()方法是在等待客户端连接,没有人连接也就不用处理什么业务啊。显然关键不在这里。我们在学习IO流的时候就说过,流是同步的,当程序在读、写一段数据时,要等待该数据流可读或可写,也就是读、写过程有IO等待及操作的时间。也即</p><blockquote><p>IO读写时间=IO等待阻塞时间+操作时间</p></blockquote><p>而IO等待是不需要CPU的,且相对耗时较长,而操作时间则很快,属于CPU时间级别。非阻塞的原理就在怎样避免IO阻塞时间,让CPU把时间都花在操作时间上。</p><blockquote><p>需要说明的是,首先上面示例仅为演示,没考虑效率问题;另外,一般阻塞式IO在处理读写操作时会使用一个固定的线程池来处理,以免读写操作太过耗时而影响所有后续连接,且这是一个很经典的做法。在如今线程已有大幅优化的情况下,阻塞IO+多线程仍是一个选择。</p></blockquote><h3 id="非阻塞式Socket"><a href="#非阻塞式Socket" class="headerlink" title="非阻塞式Socket"></a>非阻塞式Socket</h3><p>NIO是java1.4推出的重要功能,主要目的是用于构建高并发非阻塞式的服务器应用。其涉及的概念挺多,如通道(Channel),缓冲区(Buffer)以及选择器(Selector)。用法稍显复杂。先来简单介绍下这些概念。</p><p>Channel主要用到ServerSocketChannel及SocketChannel,分别表示服务器和客户端。<br>Selector为选择器,用于注册通道。选择器不太好理解,是这样:假设有好多客户端都来连这个服务器,则每个客户端都有一条Channel(通道)与服务器相连,这么多Channel,同一时刻总有的通道没数据(IO阻塞),有的准备好了(可读或可写)。选择器的作用就是把那些能读或能写的通道选出来,供程序读写。这样就能节省掉IO阻塞的时间了。</p><p>选择器除了能辨别通道是否可读或可写,还能判断是否有连接到来(accept()方法),这样把accept()阻塞的时间都省了。</p><p>不过,选择器的select()方法是阻塞的,用于表示至少一个事件准备好了(可读或可写或连接到来,具体取决于注册的事件)。</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></pre></td><td class="code"><pre><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 class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 打开一个ServerSocketChannel</span></span><br><span class="line"> ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line"> <span class="comment">// 设置监听地址</span></span><br><span class="line"> serverSocketChannel.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">8000</span>));</span><br><span class="line"> <span class="comment">//设置非阻塞模式</span></span><br><span class="line"> serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">//选择器</span></span><br><span class="line"> Selector selector = Selector.open();</span><br><span class="line"> <span class="comment">// 服务器注册接收事件</span></span><br><span class="line"> serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"> ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="comment">// 阻塞,直到连接到来</span></span><br><span class="line"> selector.select();</span><br><span class="line"> <span class="comment">// 就绪通道的集合</span></span><br><span class="line"> Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();</span><br><span class="line"> <span class="keyword">while</span> (iterator.hasNext()) {</span><br><span class="line"> SelectionKey curKey = iterator.next();</span><br><span class="line"> iterator.remove();</span><br><span class="line"> <span class="keyword">if</span> (curKey.isAcceptable()) {</span><br><span class="line"> ServerSocketChannel ssc = (ServerSocketChannel) curKey.channel();</span><br><span class="line"> <span class="comment">// 与某个客户端已连接上</span></span><br><span class="line"> SocketChannel clientChannel = ssc.accept();</span><br><span class="line"> clientChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">// 将该Channel注册到注册器上</span></span><br><span class="line"> clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (curKey.isReadable()) {</span><br><span class="line"> <span class="comment">// 某一通道可读</span></span><br><span class="line"> SocketChannel sc = (SocketChannel) curKey.channel();</span><br><span class="line"> byteBuffer.clear();</span><br><span class="line"> <span class="keyword">while</span> (sc.read(byteBuffer) > <span class="number">0</span>) {</span><br><span class="line"> byteBuffer.flip();</span><br><span class="line"> String msg = Charset.forName(<span class="string">"UTF-8"</span>).decode(byteBuffer).toString();</span><br><span class="line"> System.out.println(<span class="string">"received from: "</span> + msg);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (curKey.isWritable()) {</span><br><span class="line"> SocketChannel sc = (SocketChannel) curKey.channel();</span><br><span class="line"> String body = <span class="string">"<html><head>百度</head><body>hello baidu!</body></html>"</span>;</span><br><span class="line"> ByteBuffer buffer = ByteBuffer.wrap(body.getBytes());</span><br><span class="line"> sc.write(buffer);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>NIO的非阻塞式代码比较固定,大致都是这个写法。</p><ol><li>创建服务端的SocketChannel(ServerSocketChannel)并初始化</li><li>创建选择器(Selector)</li><li>将ServerSocketChannel及其接收新连接事件注册到选择器上。</li><li>调用选择器的select()方法阻塞,直到新连接到来(这时只注册了这一个事件)</li><li>每个新连接到来后,获取该连接对应的SocketChannel(客户端channel),将其可读或可写(或随需求)事件注册到选择器上。</li><li>此时当连接的可读或可写事件准备好后,触发对应逻辑。然后回到4循环。</li></ol><p>另外补充下</p><blockquote><p> ServerSocketChannel只有一个功能,就是接收客户端的连接请求。无法读取、写入。只能支持的操作就是接受一个新的入站请求。</p></blockquote>]]></content>
<summary type="html">
<h2 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h2><table>
<thead>
<tr>
<th></th>
<th></th>
<th>IO</th>
<th>NIO</th>
</tr>
<
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="io" scheme="https://blog.wthfeng.com/tags/io/"/>
<category term="总结" scheme="https://blog.wthfeng.com/tags/%E6%80%BB%E7%BB%93/"/>
<category term="nio" scheme="https://blog.wthfeng.com/tags/nio/"/>
</entry>
<entry>
<title>java I/O体系总结</title>
<link href="https://blog.wthfeng.com/2018-09-10-java%E7%9A%84IO%E6%B5%81%E6%80%BB%E7%BB%93/"/>
<id>https://blog.wthfeng.com/2018-09-10-java的IO流总结/</id>
<published>2018-09-10T09:00:00.000Z</published>
<updated>2018-10-16T13:41:30.053Z</updated>
<content type="html"><![CDATA[<h1 id="java-I-O体系总结"><a href="#java-I-O体系总结" class="headerlink" title="java I/O体系总结"></a>java I/O体系总结</h1><h2 id="I-O流的理解"><a href="#I-O流的理解" class="headerlink" title="I/O流的理解"></a>I/O流的理解</h2><p>先看看流的概念</p><blockquote><p>流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。</p></blockquote><p>通俗的说,有两个文件A和B,想要把A的内容拷贝到B中,可以假设两文件间有一个通道,把A的数据按字节或是字符的形式传送给B。这个通道就是java I/O体系流的概念,即可以把流当做抽象的通道。</p><p>理解了流的概念,流的分类也就很好明白了。以流的方向来说,流从外界(网络或磁盘)读取到程序(内存)中,就是输入流。流从程序流向外界就是输出流。</p><p><img src="https://img-blog.csdn.net/20180910165536363?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><h2 id="基础的流"><a href="#基础的流" class="headerlink" title="基础的流"></a>基础的流</h2><p>流又可按传输的数据类型分为字节流和字符流。简单理解就是传输是二进制的字节(字节流)还是直接可以看懂的字符(字符流)。结合输入输出流,java的I/O流基础的类(接口)主要有以下几个:</p><ol><li>InputStream(输入字节流)</li><li>OutputStream(输出字节流)</li><li>Reader(输入字符流)</li><li>Writer(输出字符流)</li></ol><h3 id="关于字节流"><a href="#关于字节流" class="headerlink" title="关于字节流"></a>关于字节流</h3><p>字节流是最基本的I/O处理流,之所以基本,是因为所有形式的储存(文件,磁盘或网络)底层都是以字节形式存储的。InputStream是所有字节输入流的父类,OutputStream是所有字节输出流的父类。字节流主要用于处理二进制数据。处理单位主要是字节或字节数组。</p><h3 id="关于字符流"><a href="#关于字符流" class="headerlink" title="关于字符流"></a>关于字符流</h3><p>鉴于有些文件是以文本存储的,为方便操作,于是有了字符流。字符流主要用于处理字符或字符串。每个字符占两个字节。在实现上,字符流即由java虚拟机将字节转为字符(每2个字节转为一个字符)形成的。字符输入流的父类是Writer,字符输出流的父类是Reader。</p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>其他流都是基于以上4个基础接口做的扩展及实现。就以文件传输为例,传输类型为字节,则有FileInputStream(文件输入流)和FileOutputStream(文件输出流)。看下示例:</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><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 输入流测试</span></span><br><span class="line"><span class="comment"> * 将磁盘中的文件读取到内存中,以字节的形式</span></span><br><span class="line"><span class="comment"> * 要读取的文件为hello.txt,其中内容为"hello,world"</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testInputStream</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> InputStream inputStream = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//代表磁盘文件</span></span><br><span class="line"> File file = <span class="keyword">new</span> File(<span class="string">"/Users/wangtonghe/local/tmp/hello.txt"</span>);</span><br><span class="line"> <span class="comment">// 构建输入流</span></span><br><span class="line"> inputStream = <span class="keyword">new</span> FileInputStream(file);</span><br><span class="line"> <span class="keyword">int</span> b = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 获取到流的内容</span></span><br><span class="line"> <span class="keyword">while</span> ((b = inputStream.read()) != -<span class="number">1</span>) {</span><br><span class="line"> <span class="comment">// 输出流的内容</span></span><br><span class="line"> System.out.println((b);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (FileNotFoundException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (inputStream != <span class="keyword">null</span>) {</span><br><span class="line"> inputStream.close();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>上面这个示例很简单,就是将待操作文件包装到文件输入流中,读取到内存。</p><p>看一个整体的示例吧,字符类型的文件输入输出。将a.txt的内容拷贝到b.txt中。FileWriter表示文件字符输出类,FileReader表示文件字符输入类。</p><p>一般的做法是,将a.txt包装到文件输入流,读取到内存。再通过文件输出流输出到b.txt文件中。</p><p>用图表述即为</p><p><img src="https://img-blog.csdn.net/20180910165513472?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></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><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testFile</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> File aFile = <span class="keyword">new</span> File(<span class="string">"/Users/wangtonghe/local/tmp/a.txt"</span>);</span><br><span class="line"> File bFile = <span class="keyword">new</span> File(<span class="string">"/Users/wangtonghe/local/tmp/b.txt"</span>);</span><br><span class="line"></span><br><span class="line"> FileReader reader = <span class="keyword">null</span>;</span><br><span class="line"> FileWriter writer = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 构建文件输入流</span></span><br><span class="line"> reader = <span class="keyword">new</span> FileReader(aFile);</span><br><span class="line"> <span class="comment">// 构建文件输出流</span></span><br><span class="line"> writer = <span class="keyword">new</span> FileWriter(bFile);</span><br><span class="line"> <span class="keyword">char</span>[] chars = <span class="keyword">new</span> <span class="keyword">char</span>[<span class="number">1024</span>];</span><br><span class="line"> <span class="keyword">int</span> len = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 读取输入流</span></span><br><span class="line"> <span class="keyword">if</span> ((len = reader.read(chars)) != -<span class="number">1</span>) {</span><br><span class="line"> <span class="comment">//写入输出流</span></span><br><span class="line"> writer.write(chars, <span class="number">0</span>, len);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (reader != <span class="keyword">null</span>) {</span><br><span class="line"> reader.close();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (writer != <span class="keyword">null</span>) {</span><br><span class="line"> writer.close();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="字节流与字符流的区别"><a href="#字节流与字符流的区别" class="headerlink" title="字节流与字符流的区别"></a>字节流与字符流的区别</h3><ol><li>操作对象不同,这是最基本的区别。字节流操作字节或字节数组;字符流操作字符或字符串。</li><li>字节流对终端(文件等)直接进行操作,而字符流使用缓冲区(即先把数据写入缓存区,再对缓存区进行操作)</li><li>由于2的存在,对字节的操作会直接影响终端(文件)结果,而对字符流的操作最后必须关闭流或强制刷新流才能写入数据,否则只是写到了缓存区,文件等终端不受影响。</li><li>详见 <a href="https://blog.csdn.net/cynhafa/article/details/6882061" target="_blank" rel="noopener">java 字节流与字符流的区别</a></li></ol><h3 id="两种流的相互转化"><a href="#两种流的相互转化" class="headerlink" title="两种流的相互转化"></a>两种流的相互转化</h3><p>InputStreamReader以及OutputStreamWriter 负责两种流的相互转换。(一般是字节流转字符流,没有字符转字节,也不需要)</p><p>InputStreamReader可以将字节输入流转为字符输入流;OutputStreamWriter可以将字节输出流转为字符输出流。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">//文本文件</span></span><br><span class="line">File aFile = <span class="keyword">new</span> File(<span class="string">"/Users/wangtonghe/local/tmp/a.txt"</span>);</span><br><span class="line"><span class="comment">// 文件字节流</span></span><br><span class="line">FileInputStream fileInputStream = <span class="keyword">new</span> FileInputStream(aFile);</span><br><span class="line"><span class="comment">// 字节流转为字符流</span></span><br><span class="line">Reader reader = <span class="keyword">new</span> InputStreamReader(fileInputStream);</span><br></pre></td></tr></table></figure><p>转化流构造方法及用法如下</p><ol><li>InputStreamReader(InputStream); //通过构造函数初始化,使用的是本系统默认的编码表GBK。</li><li>InputStreamWriter(InputStream,String charSet); //通过该构造函数初始化,可以指定编码表。</li><li>OutputStreamWriter(OutputStream); //通过该构造函数初始化,使用的是本系统默认的编码表GBK。</li><li>OutputStreamwriter(OutputStream,String charSet); //通过该构造函数初始化,可以指定编码表</li></ol><h2 id="I-O流概览"><a href="#I-O流概览" class="headerlink" title="I/O流概览"></a>I/O流概览</h2><p><img src="https://img-blog.csdn.net/20180910165628893?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d0aGZlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="这里写图片描述"></p><p>java体系I/O流大致类及结构就如上图所示。</p><p>简要介绍一下</p><p>输入字节流InputStream</p><ol><li>InputStream 是所有的输入字节流的父类,它是一个抽象类。</li><li>ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte数组、StringBuffer、和本地文件中读取数据。</li><li>ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。</li></ol><p>同理输出字节流OutputStream与此类似</p><ol><li>OutputStream 是所有的输出字节流的父类,它是一个抽象类。</li><li>ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。</li><li>ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。</li></ol><h2 id="I-O体系的装饰器模式"><a href="#I-O体系的装饰器模式" class="headerlink" title="I/O体系的装饰器模式"></a>I/O体系的装饰器模式</h2><p>关于I/O体系,一定要谈的就是其装饰器的设计模式。</p><h3 id="装饰器模式"><a href="#装饰器模式" class="headerlink" title="装饰器模式"></a>装饰器模式</h3><p><strong>概念</strong> 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它能让我们在扩展类的时候让系统较好的保持灵活性。</p><p>这里就不具体介绍装饰器模式了,可以看看<a href="https://www.cnblogs.com/xrq730/p/4908940.html" target="_blank" rel="noopener">Java设计模式12:装饰器模式</a></p><p>java I/O中实现装饰器模式主要由FilterInputStream及其子类(输入流)和FilterOutputStream及其子类完成。</p><p>如BufferedInputStream,有缓冲功能的输入流,可修饰FileIntputStream使其拥有缓冲功能。</p><blockquote><p>BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File(“/home/user/abc.txt”)));</p></blockquote><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol><li><a href="https://blog.csdn.net/cynhafa/article/details/6882061" target="_blank" rel="noopener">java 字节流与字符流的区别</a></li><li><a href="https://www.cnblogs.com/DONGb/p/7844123.html" target="_blank" rel="noopener">字节流与字符流的区别详解</a></li><li><a href="https://blog.csdn.net/zhaoyanjun6/article/details/54292148" target="_blank" rel="noopener">Java IO流学习总结一:输入输出流</a></li><li><a href="https://www.cnblogs.com/xrq730/p/4908940.html" target="_blank" rel="noopener">Java设计模式12:装饰器模式</a></li><li><a href="https://blog.csdn.net/huaweitman/article/details/50546459" target="_blank" rel="noopener">Java IO : 流,以及装饰器模式在其上的运用</a></li></ol>]]></content>
<summary type="html">
<h1 id="java-I-O体系总结"><a href="#java-I-O体系总结" class="headerlink" title="java I/O体系总结"></a>java I/O体系总结</h1><h2 id="I-O流的理解"><a href="#I-O流的理
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="io" scheme="https://blog.wthfeng.com/tags/io/"/>
<category term="总结" scheme="https://blog.wthfeng.com/tags/%E6%80%BB%E7%BB%93/"/>
</entry>
<entry>
<title>使用注解方式构建dubbo服务</title>
<link href="https://blog.wthfeng.com/2018-09-07-%E4%BD%BF%E7%94%A8%E6%B3%A8%E8%A7%A3%E6%96%B9%E5%BC%8F%E6%9E%84%E5%BB%BAdubbo%E5%BA%94%E7%94%A8/"/>
<id>https://blog.wthfeng.com/2018-09-07-使用注解方式构建dubbo应用/</id>
<published>2018-09-07T09:18:00.000Z</published>
<updated>2018-10-28T05:38:49.790Z</updated>
<content type="html"><![CDATA[<h1 id="使用注解方式构建dubbo服务"><a href="#使用注解方式构建dubbo服务" class="headerlink" title="使用注解方式构建dubbo服务"></a>使用注解方式构建dubbo服务</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><a href="https://dubbo.incubator.apache.org/zh-cn/" target="_blank" rel="noopener">Dubbo</a>是阿里巴巴开源的一个高性能优秀的服务框架,通过使用RPC实现服务调用。在业界尤其国内使用广泛。下面就从头开始构建dubbo的简单demo,配置使用注释方式完成,以zookeeper为注册中心。</p><h2 id="构建项目"><a href="#构建项目" class="headerlink" title="构建项目"></a>构建项目</h2><p>以 IntelliJ IDEA 为例,创建一个多模块的项目,项目结构如下图所示。</p><p><img src="http://img.wthfeng.com/img/wthfeng/java/dubbo/20180907171424495.jpeg" alt="这里写图片描述"></p><p>其中,dubbo-demo是父项目,其下有3个子项目,分别是:</p><ol><li>dubbo-server 服务提供者,提供服务接口具体实现,对外提供服务。</li><li>dubbo-client 服务调用者,充当客户端角色,调用服务。</li><li>dubbo-api 定义接口,为以上两者充当桥梁作用,目的解耦。</li></ol><p>需要注意的是,dubbo-demo作为父项目,它的依赖可用以子项目,且其<code>packaging</code>的值为<code>pom</code>。</p><p><img src="http://img.wthfeng.com/img/wthfeng/java/dubbo/20180907171105598.jpeg" alt="这里写图片描述"></p><p>其他3个项目即为普通的maven项目,打包方式为jar(packaging),dubbo-server及dubbo-client均依赖dubbo-api。</p><p>以dubbo-server为例,其pom.xml(部分)为</p><p><img src="http://img.wthfeng.com/img/wthfeng/java/dubbo/20180907171014458.jpeg" alt="这里写图片描述"></p><p>标注第一个框指明其继承的父项目,第二个框表示依赖了dubbo-api。<br>dubbo-client与此相似。</p><h2 id="前期准备"><a href="#前期准备" class="headerlink" title="前期准备"></a>前期准备</h2><p>本文使用zookeeper作为服务注册中心,首先要保证zookeeper服务可以正常运行。这里使用本地zookeeper为例。</p><p>进入ZOOKEEPER_HOME/bin 目录,执行 </p><p><strong>sudo ./zkServer.sh start</strong> </p><p>命令即可。默认zookeeper端口为2181,不必修改。</p><h2 id="定义接口"><a href="#定义接口" class="headerlink" title="定义接口"></a>定义接口</h2><p>为解耦以及架构清晰,于是有了dubbo-api子项目。当然这个项目不是必须的,接口的定义也可以放在dubbo-server(即服务提供方),但为以后扩展方便,对外接口统一由dubbo-api定义。</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><br><span class="line"><span class="keyword">package</span> com.wthfeng.dubboapi.service;</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">@author</span> wangtonghe</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2018/9/6 09:57</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">HelloService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function">String <span class="title">sayHello</span><span class="params">(String name)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>很简单,定义一个需要实现的接口即可。</p><h2 id="提供服务"><a href="#提供服务" class="headerlink" title="提供服务"></a>提供服务</h2><p>dubbo-server负责暴露并提供服务。</p><h4 id="1-首先,编写一个定义dubbo配置类。"><a href="#1-首先,编写一个定义dubbo配置类。" class="headerlink" title="1. 首先,编写一个定义dubbo配置类。"></a>1. 首先,编写一个定义dubbo配置类。</h4><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><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DubboConfig</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Resource</span></span><br><span class="line"> <span class="keyword">private</span> DubboProperties dubboProperties;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 应用名配置,等同于 <dubbo:application name="xxx" /></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ApplicationConfig</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ApplicationConfig <span class="title">applicationConfig</span><span class="params">()</span> </span>{</span><br><span class="line"> ApplicationConfig applicationConfig = <span class="keyword">new</span> ApplicationConfig();</span><br><span class="line"> applicationConfig.setName(dubboProperties.getName());</span><br><span class="line"> <span class="keyword">return</span> applicationConfig;</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"> * 注册中心配置,等同于 <dubbo:registry address="url" /></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> RegistryConfig</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> RegistryConfig <span class="title">registryConfig</span><span class="params">()</span> </span>{</span><br><span class="line"> RegistryConfig registryConfig = <span class="keyword">new</span> RegistryConfig();</span><br><span class="line"> registryConfig.setAddress(dubboProperties.getAddress());</span><br><span class="line"> registryConfig.setClient(dubboProperties.getClient());</span><br><span class="line"> <span class="keyword">return</span> registryConfig;</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"> * 协议配置,等同于 <dubbo:protocol name="dubbo" port="20880" /></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ProtocolConfig</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ProtocolConfig <span class="title">protocolConfig</span><span class="params">()</span> </span>{</span><br><span class="line"> ProtocolConfig protocolConfig = <span class="keyword">new</span> ProtocolConfig();</span><br><span class="line"> protocolConfig.setName(dubboProperties.getProtocolName());</span><br><span class="line"> protocolConfig.setPort(dubboProperties.getProtocolPort());</span><br><span class="line"> <span class="keyword">return</span> protocolConfig;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>注释写的很清楚了,DubboProperties 是加载的一个配置类,内容如下:</p><figure class="highlight plain"><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"></span><br><span class="line">application.dubbo.demo.server.name=dubbo-server</span><br><span class="line">application.dubbo.demo.server.address=zookeeper://127.0.0.1:2181</span><br><span class="line">application.dubbo.demo.server.client=zkclient</span><br><span class="line">application.dubbo.demo.server.protocolName=dubbo</span><br><span class="line">application.dubbo.demo.server.protocolPort=20880</span><br></pre></td></tr></table></figure><p>从配置类可以看出,注册中心使用的是zookeeper,这个稍后再说,先这样写着。下面看看暴露服务的设置。</p><h4 id="2-编写一个实现类实现要暴露的接口。"><a href="#2-编写一个实现类实现要暴露的接口。" class="headerlink" title="2. 编写一个实现类实现要暴露的接口。"></a>2. 编写一个实现类实现要暴露的接口。</h4><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="keyword">package</span> com.wthfeng.dubboserver.service;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.dubbo.config.annotation.Service;</span><br><span class="line"><span class="keyword">import</span> com.wthfeng.dubboapi.service.HelloService;</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">@author</span> wangtonghe</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2018/9/5 17:46</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span>(timeout = <span class="number">5000</span>, version = <span class="string">"1.0"</span>, group = <span class="string">"demo-dubbo"</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><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">sayHello</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> String value = <span class="string">"Hello "</span> + name + <span class="string">" !"</span>;</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>注意:@Service 这个注解是dubbo的用于暴露服务的注解。而不是spring的那个注解!其中timeout为调用该服务的超时时间,version为版本号,group为分组。interface这里指HelloService,</p></blockquote><blockquote><p>interface、group、version三者确定一个服务。</p></blockquote><h4 id="3-最后,需要在spring-boot-启动类中配置dubbo的暴露服务的包的扫描路径,即将HelloServiceImpl这样的类放于一个包中使其能扫描到。"><a href="#3-最后,需要在spring-boot-启动类中配置dubbo的暴露服务的包的扫描路径,即将HelloServiceImpl这样的类放于一个包中使其能扫描到。" class="headerlink" title="3. 最后,需要在spring boot 启动类中配置dubbo的暴露服务的包的扫描路径,即将HelloServiceImpl这样的类放于一个包中使其能扫描到。"></a>3. 最后,需要在spring boot 启动类中配置dubbo的暴露服务的包的扫描路径,即将HelloServiceImpl这样的类放于一个包中使其能扫描到。</h4><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><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@DubboComponentScan</span>(value = <span class="string">"com.wthfeng.dubboserver.service"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoServerApplication</span> </span>{</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"> SpringApplication.run(DemoServerApplication.class, args);</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><h4 id="1-设置配置文件"><a href="#1-设置配置文件" class="headerlink" title="1. 设置配置文件"></a>1. 设置配置文件</h4><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DubboClientConfig</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Resource</span></span><br><span class="line"> <span class="keyword">private</span> DubboProperties dubboProperties;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 应用名配置,等同于 <dubbo:application name="xxx" /></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ApplicationConfig</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ApplicationConfig <span class="title">applicationConfig</span><span class="params">()</span> </span>{</span><br><span class="line"> ApplicationConfig applicationConfig = <span class="keyword">new</span> ApplicationConfig();</span><br><span class="line"> applicationConfig.setName(dubboProperties.getName());</span><br><span class="line"> <span class="keyword">return</span> applicationConfig;</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"> * 注册中心配置,等同于 <dubbo:registry address="url" /></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> RegistryConfig</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> RegistryConfig <span class="title">registryConfig</span><span class="params">()</span> </span>{</span><br><span class="line"> RegistryConfig registryConfig = <span class="keyword">new</span> RegistryConfig();</span><br><span class="line"> registryConfig.setAddress(dubboProperties.getAddress());</span><br><span class="line"> registryConfig.setClient(dubboProperties.getClient());</span><br><span class="line"> <span class="keyword">return</span> registryConfig;</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"> * 协议配置,等同于 <dubbo:protocol name="dubbo" port="20880" /></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ProtocolConfig</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ProtocolConfig <span class="title">protocolConfig</span><span class="params">()</span> </span>{</span><br><span class="line"> ProtocolConfig protocolConfig = <span class="keyword">new</span> ProtocolConfig();</span><br><span class="line"> protocolConfig.setName(dubboProperties.getProtocolName());</span><br><span class="line"> protocolConfig.setPort(dubboProperties.getProtocolPort());</span><br><span class="line"> <span class="keyword">return</span> protocolConfig;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-引用服务"><a href="#2-引用服务" class="headerlink" title="2. 引用服务"></a>2. 引用服务</h4><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><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">BusinessService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 引用服务,与 <dubbo:reference/> 等同。注意这里的version和group要和暴露服务的一致</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Reference</span>(version = <span class="string">"1.0"</span>, group = <span class="string">"demo-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><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testHello</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> String str = helloService.sayHello(name);</span><br><span class="line"> System.out.println(<span class="string">"调用结果:"</span> + str);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p> @Reference 注解为dubbo服务调用的注解,和dubbo中@Service 注解对应,尤其是version和group以及interface字段两者一定要一致。</p></blockquote><h4 id="3-设置扫描包地址"><a href="#3-设置扫描包地址" class="headerlink" title="3. 设置扫描包地址"></a>3. 设置扫描包地址</h4><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><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@DubboComponentScan</span>(value = <span class="string">"com.wthfeng.democlient.service"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoClientApplication</span> </span>{</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"> SpringApplication.run(DemoClientApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在dubbo-client项目启动类中,添加dubbo包扫描地址。</p><h2 id="项目启动"><a href="#项目启动" class="headerlink" title="项目启动"></a>项目启动</h2><p>据此,dubbo项目构建完毕。为测试方便,在dubbo-client的测试包下添加一个测试类,如下</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><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner.class)</span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoClientApplicationTests</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Resource</span></span><br><span class="line"> <span class="keyword">private</span> BusinessService businessService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextLoads</span><span class="params">()</span> </span>{</span><br><span class="line"> businessService.testHello(<span class="string">"dubbo"</span>);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在zookeeper正常运行的前提下,先启动dubbo-server,再运行此测试用例。输出:</p><blockquote><p>调用结果:Hello dubbo !</p></blockquote><p>表示构建成功。</p><h2 id="项目架构"><a href="#项目架构" class="headerlink" title="项目架构"></a>项目架构</h2><p>目前,我们可以把上述项目及组件整理分类一下。可分为服务调用房,服务提供者,注册中心,(dubbo-api为系统解耦,不作为系统部分)。</p><p>根据dubbo文档及资料描述,流程如下:</p><ol><li>启动zookeeper及服务提供方(Provider)</li><li>Provider 向注册中心注册服务(告诉zookeeper我都有什么服务)</li><li>Consumer(消费方)订阅需要的服务(告诉zookeeper我需要什么服务)</li><li>注册中心告知消费者服务所在地址(zookeeper告知消费者其需要服务的地址列表)</li><li>消费者调用服务</li></ol><p>流程图如下(来自<a href="https://dubbo.incubator.apache.org/zh-cn/docs/dev/design.html" target="_blank" rel="noopener">官网</a>):</p><p><img src="http://img.wthfeng.com/img/wthfeng/java/dubbo/20180907171525877.jpeg" alt="这里写图片描述"></p><p>其他补充</p><ol><li>图中5表示,消费者和提供服务者,需要定时向监控中心发送调用次数等的数据,便于监控中心统计,此处不是服务所必须的</li><li>注册中心还需时刻保持与服务提供者的联系(通过心跳包),以确定提供者在线,若其下线,需及时告知消费者。</li><li>图中3也表示,当服务列表变化时,向消费者推送变更通知。</li></ol><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>该项目源码已传到 github上,地址 <a href="https://github.com/togethwy/dubbo-demo" target="_blank" rel="noopener">https://github.com/togethwy/dubbo-demo</a></p>]]></content>
<summary type="html">
<h1 id="使用注解方式构建dubbo服务"><a href="#使用注解方式构建dubbo服务" class="headerlink" title="使用注解方式构建dubbo服务"></a>使用注解方式构建dubbo服务</h1><h2 id="前言"><a href="
</summary>
<category term="dubbo" scheme="https://blog.wthfeng.com/categories/dubbo/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="dubbo" scheme="https://blog.wthfeng.com/tags/dubbo/"/>
<category term="教程" scheme="https://blog.wthfeng.com/tags/%E6%95%99%E7%A8%8B/"/>
</entry>
<entry>
<title>java8日期时间API讲解</title>
<link href="https://blog.wthfeng.com/2018-05-06-java8%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4API/"/>
<id>https://blog.wthfeng.com/2018-05-06-java8日期时间API/</id>
<published>2018-05-06T10:41:00.000Z</published>
<updated>2018-10-16T13:41:30.052Z</updated>
<content type="html"><![CDATA[<h1 id="java日期时间API总结"><a href="#java日期时间API总结" class="headerlink" title="java日期时间API总结"></a>java日期时间API总结</h1><h2 id="Date"><a href="#Date" class="headerlink" title="Date"></a>Date</h2><p>java中常见的表示时间的类。内部使用long类型的值表示自1970-01-01起的毫秒数。本质上是一个表示瞬时时间的类,表示级别为毫秒。且其为可变对象,即线程不安全的。目前大多数方法已废弃。可用且常见的方法如下:</p><table><thead><tr><th>方法</th><th>含义</th></tr></thead><tbody><tr><td>new Date()</td><td>创建一个表示当前时间的对象</td></tr><tr><td>new Date(long time)</td><td>传入一个表示自1970年1月1日起的毫秒数,基于此时间创建对象</td></tr><tr><td>from(Instant instant)</td><td>以Instant类的对象构建对象,java8新增</td></tr><tr><td>toInstant()</td><td>将Date对象转为Instant对象,java8新增</td></tr></tbody></table><h2 id="Instant"><a href="#Instant" class="headerlink" title="Instant"></a>Instant</h2><p>java8新增类,专门用于表示时间戳。想获取当前时间的时间戳可用<code>Instant.now()</code>获取。</p><p>关于Instant比较常用的是与Date的相互转化及比较两时间戳的差值。如下:</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">Instant now = Instant.now();</span><br><span class="line">System.out.println(now);</span><br><span class="line"></span><br><span class="line">Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Date转Instant</span></span><br><span class="line">Date today = <span class="keyword">new</span> Date();</span><br><span class="line">Instant cur = today.toInstant();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算两Instant示例差值</span></span><br><span class="line"><span class="keyword">long</span> diff = Duration.between(now, cur).toMillis();</span><br><span class="line">System.out.println(diff);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Instant 转Date</span></span><br><span class="line">Date date = Date.from(now);</span><br><span class="line">System.out.println(date);</span><br></pre></td></tr></table></figure><h2 id="LocalDate"><a href="#LocalDate" class="headerlink" title="LocalDate"></a>LocalDate</h2><p>java8新增的表示日期的类。默认格式为<code>yyyy-MM-dd</code>,表示具体某一天。可由<code>now()</code>获取当前日期,也可传入年月日构造。类中有格式化方法。</p><p>常用方法如下:</p><table><thead><tr><th>方法</th><th>静态方法</th><th>含义</th></tr></thead><tbody><tr><td>now()</td><td>是</td><td>获取当天日期</td></tr><tr><td>of(year,month,days)</td><td>是</td><td>根据传入的值构造日期</td></tr><tr><td>parse(dateStr)</td><td>是</td><td>解析日期字符串(需为yyyy-MM-dd格式)为日期格式</td></tr><tr><td>format</td><td>否</td><td>格式化日期为字符串</td></tr><tr><td>isLeapYear()</td><td>否</td><td>是否为闰年</td></tr><tr><td>lengthOfMonth()</td><td>否</td><td>该月长度</td></tr><tr><td>getDayOfWeek()</td><td>否</td><td>表示是周几,返回枚举类</td></tr><tr><td>plusXXX</td><td>否</td><td>XXX可为年、月或日,表示添加一段时间,返回新日期</td></tr><tr><td>minusXXX</td><td>否</td><td>同plusXXX类似,在实例基础上减去某个值,返回新日期</td></tr><tr><td>equal()</td><td>否</td><td>比较两日期是否相等</td></tr></tbody></table><p>总的来说,LocalDate表示代表本地时间的日期,API为我们提供了构造、解析、格式化及计算等方法,使得日期表示更简便。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">LocalDate now = LocalDate.now();</span><br><span class="line"><span class="comment">//输出:2018-04-22 ,表示其默认格式为yyyy-MM-dd</span></span><br><span class="line">System.out.println(now);</span><br><span class="line"></span><br><span class="line">LocalDate lastDay = LocalDate.of(<span class="number">2018</span>, <span class="number">4</span>, <span class="number">21</span>);</span><br><span class="line"><span class="comment">//输出:2018-04-21</span></span><br><span class="line">System.out.println(lastDay);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出:false,不是闰年</span></span><br><span class="line">System.out.println(now.isLeapYear());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出 2018-04-21,格式化字符串</span></span><br><span class="line">System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注意,会报错,LocalDate类只有日期没有时间信息,不能格式化</span></span><br><span class="line"><span class="comment">// System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取某天的年月日信息</span></span><br><span class="line">System.out.println(now.getYear());</span><br><span class="line">System.out.println(now.getMonthValue());</span><br><span class="line">System.out.println(now.getDayOfMonth());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 比较两日期是否相等</span></span><br><span class="line">System.out.println(now.equals(lastDay));</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"> LocalDate tomorrow = now.plusDays(<span class="number">1</span>);</span><br><span class="line"> System.out.println(tomorrow);</span><br><span class="line"> <span class="comment">// 一周前</span></span><br><span class="line"> LocalDate monthDay = now.minusWeeks(<span class="number">1</span>);</span><br><span class="line"> System.out.println(monthDay);</span><br><span class="line"> <span class="comment">// 一年后</span></span><br><span class="line"> LocalDate afterDay = now.plus(<span class="number">1</span>, ChronoUnit.YEARS);</span><br><span class="line"> System.out.println(afterDay);</span><br></pre></td></tr></table></figure><h2 id="LocalTime与LocalDateTime"><a href="#LocalTime与LocalDateTime" class="headerlink" title="LocalTime与LocalDateTime"></a>LocalTime与LocalDateTime</h2><p>LocalTime、LocalDateTime与LocalDate类似,LocalTime表示本地时间,LocalDateTime表示本地日期时间。用法也与LocalDate类似。下面简单介绍下。</p><h3 id="LocalTime"><a href="#LocalTime" class="headerlink" title="LocalTime"></a>LocalTime</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//获取当前时间</span></span><br><span class="line"> LocalTime curTime = LocalTime.now();</span><br><span class="line"> <span class="comment">// 输出:10:52:12.108</span></span><br><span class="line"> System.out.println(curTime);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造时间</span></span><br><span class="line"> LocalTime localTime = LocalTime.of(<span class="number">14</span>, <span class="number">34</span>, <span class="number">12</span>);</span><br><span class="line"> <span class="comment">// 输出:14:34:12</span></span><br><span class="line"> System.out.println(localTime);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 时间加减,1小时后</span></span><br><span class="line"> LocalTime nextHour = curTime.plus(<span class="number">1</span>, ChronoUnit.HOURS);</span><br><span class="line"> <span class="comment">// 11:52:12.108</span></span><br><span class="line"> System.out.println(nextHour);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 两时间差值</span></span><br><span class="line"> <span class="keyword">long</span> minuteDiff = Duration.between(curTime, localTime).toMinutes();</span><br><span class="line"> <span class="comment">// 输出:221</span></span><br><span class="line"> System.out.println(minuteDiff);</span><br></pre></td></tr></table></figure><h3 id="LocalDateTime"><a href="#LocalDateTime" class="headerlink" title="LocalDateTime"></a>LocalDateTime</h3><p>用来表示日期及时间,包含LocalDate与LocalTime的信息,比较常用。方法与前两者类似。</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"> LocalDateTime now = LocalDateTime.now();</span><br><span class="line"> <span class="comment">// 输出:2018-05-05T10:58:17.119</span></span><br><span class="line"> System.out.println(now);</span><br><span class="line"> <span class="comment">// 2018</span></span><br><span class="line"> System.out.println(now.getYear());</span><br><span class="line"> <span class="comment">//5</span></span><br><span class="line"> System.out.println(now.getMonthValue());</span><br><span class="line"> <span class="comment">//5</span></span><br><span class="line"> System.out.println(now.getDayOfMonth());</span><br><span class="line"> <span class="comment">// 10</span></span><br><span class="line"> System.out.println(now.getHour());</span><br><span class="line"> <span class="comment">//58</span></span><br><span class="line"> System.out.println(now.getMinute());</span><br></pre></td></tr></table></figure><h2 id="日期格式化"><a href="#日期格式化" class="headerlink" title="日期格式化"></a>日期格式化</h2><p>java8的日期格式化再也不用<code>SimpleDateFormat</code>类做复杂的日期转化了,格式化的函数都包含在各自的日期时间类中。以<code>LocalDateTime</code>为例:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">formatDate</span><span class="params">()</span> </span>{</span><br><span class="line"> LocalDateTime now = LocalDateTime.now();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 默认输出:2018-05-05T11:11:59.221</span></span><br><span class="line"> System.out.println(now);</span><br><span class="line"> <span class="comment">// iso日期时间格式,输出:2018-05-05T11:11:59.221</span></span><br><span class="line"> System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));</span><br><span class="line"> <span class="comment">// iso 日期格式,输出:2018-05-05</span></span><br><span class="line"> System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE));</span><br><span class="line"> <span class="comment">// 自定义格式:2018/05/05 11:11:59</span></span><br><span class="line"> System.out.println(now.format(DateTimeFormatter.ofPattern(<span class="string">"yyyy/MM/dd HH:mm:ss"</span>)));</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 字符串转日期,默认字符串格式需为ISO_LOCAL_DATE_TIME风格</span></span><br><span class="line"> LocalDateTime localDateTime = LocalDateTime.parse(<span class="string">"2018-05-01T11:00:00"</span>);</span><br><span class="line"> System.out.println(localDateTime);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 字符串转日期,字符串格式需与模式相匹配</span></span><br><span class="line"> LocalDateTime localDateTime1 = LocalDateTime.parse(<span class="string">"2018/05/05 12:00"</span>, DateTimeFormatter.ofPattern(<span class="string">"yyyy/MM/dd HH:mm"</span>));</span><br><span class="line"> System.out.println(localDateTime1);</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="时间差值"><a href="#时间差值" class="headerlink" title="时间差值"></a>时间差值</h2><p>java8可以计算时间差值的类有:<code>Period</code> 和 <code>Duration</code>以及<code>ChronoUnit</code>。其中<code>ChronoUnit</code>计算时间差值主要也是用到了<code>Duration</code>。而<code>Period</code>主要是针对日期的间隔计算,而<code>Duration</code>主要是针对时间的。看一下示例:</p><h3 id="Period"><a href="#Period" class="headerlink" title="Period"></a>Period</h3><p>Period表示以日期为单位的时间段,即其最小单位也要是<code>day</code>。常用于计算两<code>LocalDate</code>之间的差值</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><br><span class="line"><span class="comment">// 计算距22年冬奥会还有多久</span></span><br><span class="line"> LocalDate now = LocalDate.now();</span><br><span class="line"> <span class="comment">// 输出:当前日期:2018年05月06日</span></span><br><span class="line"> System.out.println(<span class="string">"当前日期:"</span> + now.format(DateTimeFormatter.ofPattern(<span class="string">"yyyy年MM月dd日"</span>)));</span><br><span class="line"> LocalDate olympicDay = LocalDate.of(<span class="number">2022</span>, <span class="number">2</span>, <span class="number">4</span>);</span><br><span class="line"> Period period = Period.between(now, olympicDay);</span><br><span class="line"> <span class="comment">// 输出:距冬奥会还有:3年8月29天</span></span><br><span class="line"> System.out.println(<span class="string">"距冬奥会还有:"</span> + period.getYears() + <span class="string">"年"</span> + period.getMonths() + <span class="string">"月"</span> + period.getDays() + <span class="string">"天"</span>);</span><br></pre></td></tr></table></figure><h3 id="Duration"><a href="#Duration" class="headerlink" title="Duration"></a>Duration</h3><p>相比<code>Period</code>,<code>Duration</code>用于表示两个时间的时间差值,而不仅仅是日期。如可以表示两时间点差值,日期差值。</p><blockquote><p>需注意的是,Duration内部表示时间差是用两个long类型的值:秒(second)及纳秒(nanos)表示的,若用于计算两日期相差天数等问题,通常是将秒换算成天得到的。这样也表明,两时间值至少需要精确到秒,否则不能完成转化,即LocalDate是不能用Duration</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 计算两个LocalTime相差的毫秒数</span></span><br><span class="line">LocalTime start = LocalTime.now();</span><br><span class="line">TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">Duration duration = Duration.between(start, LocalTime.now());</span><br><span class="line"><span class="comment">// 输出:1004</span></span><br><span class="line">System.out.println(<span class="string">"1秒后相差毫秒数:"</span> + duration.toMillis());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 距冬奥会天数,不能使用LocalDate,因其没有时分秒等单位</span></span><br><span class="line">LocalDateTime curTime = LocalDateTime.now();</span><br><span class="line">LocalDateTime nextTime = LocalDateTime.of(<span class="number">2022</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">System.out.println(<span class="string">"距冬奥会相差:"</span> + Duration.between(curTime, nextTime).toDays() + <span class="string">"天"</span>);</span><br></pre></td></tr></table></figure><h3 id="ChronoUnit"><a href="#ChronoUnit" class="headerlink" title="ChronoUnit"></a>ChronoUnit</h3><p>上面说Duration不能用在LocalDate上,而我需要计算两日期相差天数,就不能直接计算得到吗。事实上当然可以了,可以使用<code>ChronoUnit</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">//输出: 距冬奥会相差:1370天</span></span><br><span class="line">System.out.println(<span class="string">"距冬奥会相差:"</span> + Duration.between(curTime, nextTime).toDays() + <span class="string">"天"</span>);</span><br></pre></td></tr></table></figure><p>不仅如此,<code>ChronoUnit</code>类可用于计算上述所提到的各种时间差。计算模式也很固定</p><blockquote><p>ChronoUnit.计量单位.between(start,end)</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1秒钟后</span></span><br><span class="line"> <span class="keyword">long</span> diffOneMinute = ChronoUnit.MILLIS.between(start, LocalTime.now());</span><br><span class="line"> System.out.println(<span class="string">"1秒钟后"</span> + diffOneMinute);</span><br></pre></td></tr></table></figure><h2 id="新旧时间转化"><a href="#新旧时间转化" class="headerlink" title="新旧时间转化"></a>新旧时间转化</h2><h3 id="Date与Instant"><a href="#Date与Instant" class="headerlink" title="Date与Instant"></a>Date与Instant</h3><p>Date与Instant都可表示瞬时时间,转化方式上面也有所提及,即:</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><br><span class="line"><span class="comment">// Date转Instant</span></span><br><span class="line">Date today = <span class="keyword">new</span> Date();</span><br><span class="line">Instant cur = today.toInstant();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Instant 转Date</span></span><br><span class="line">Date date = Date.from(now);</span><br></pre></td></tr></table></figure><h3 id="Data与LocalDate"><a href="#Data与LocalDate" class="headerlink" title="Data与LocalDate"></a>Data与LocalDate</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// Date转LocalDateTime、LocalDate</span></span><br><span class="line">LocalDateTime now = LocalDateTime.ofInstant(<span class="keyword">new</span> Date().toInstant(), ZoneId.systemDefault());</span><br><span class="line"><span class="comment">// 输出:2018-05-06T18:29:52.184</span></span><br><span class="line">System.out.println(now);</span><br><span class="line">LocalDate today = now.toLocalDate();</span><br><span class="line"><span class="comment">//输出:2018-05-06</span></span><br><span class="line">System.out.println(today);</span><br><span class="line"></span><br><span class="line"><span class="comment">// LocalDateTime 转Date</span></span><br><span class="line">Date date = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());</span><br><span class="line"></span><br><span class="line"><span class="comment">// LocalDate 转Date</span></span><br><span class="line">Date date1 = Date.from(today.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="java日期时间API总结"><a href="#java日期时间API总结" class="headerlink" title="java日期时间API总结"></a>java日期时间API总结</h1><h2 id="Date"><a href="#Date"
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="java8" scheme="https://blog.wthfeng.com/tags/java8/"/>
<category term="API" scheme="https://blog.wthfeng.com/tags/API/"/>
</entry>
<entry>
<title>3月java面试总结</title>
<link href="https://blog.wthfeng.com/2018-03-18-3%E6%9C%88java%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/"/>
<id>https://blog.wthfeng.com/2018-03-18-3月java面试总结/</id>
<published>2018-03-18T09:53:00.000Z</published>
<updated>2018-10-16T13:41:30.051Z</updated>
<content type="html"><![CDATA[<h1 id="3月java面试总结"><a href="#3月java面试总结" class="headerlink" title="3月java面试总结"></a>3月java面试总结</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这段时间的面试总算是告一段落了。回想起近半个月的面试体验,感觉有必要总结总结经验得失,也为以后留个备忘。这段时间共经历7次面试+2次电面。公司有大有小,有产品也有外包,面试形式也多种多样,真可是体验了一番。最大的感慨还是感觉自己技术体系有欠缺,不完整。书到用时方恨少真是面试时的最佳描述了,今后还是要多学多实战。</p><p>再者推荐两本书吧,一是<a href="https://book.douban.com/subject/24722612/" target="_blank" rel="noopener">《深入理解java虚拟机 第二版》</a>,这本书简直是java进阶必备啊,从我经历的面试来看,除了某些外包公司外,还真没哪个公司不问虚拟机垃圾回收的。书中讲的也很好,十分推荐。</p><p>还有一本是 <a href="https://book.douban.com/subject/26591326/" target="_blank" rel="noopener">《java并发编程的艺术》</a>,这本书是讲多线程并发相关的,也是进阶必备知识。之所以没说<a href="https://book.douban.com/subject/10484692/" target="_blank" rel="noopener">《Java并发编程实战》</a>是因为感觉它有些晦涩,如果先读前者再读的话可能会好些。</p><h2 id="知识汇总"><a href="#知识汇总" class="headerlink" title="知识汇总"></a>知识汇总</h2><p>面试中涉及到知识有java基础知识,JVM、设计模式、并发与多线程、分布式相关、数据库、缓存、算法等,整理如下:</p><h3 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h3><h4 id="1-HashMap、ConcurrentHashMap相关"><a href="#1-HashMap、ConcurrentHashMap相关" class="headerlink" title="1. HashMap、ConcurrentHashMap相关"></a>1. HashMap、ConcurrentHashMap相关</h4><p>HashMap是必问的,主要问到的点有HashMap的原理及结构(注意java7和java8的区别),HashMap和HashTable的区别,是线程安全的吗,HashMap在多线程中怎样发生的死锁?总体来说HashMap问的挺多,需要掌握到源码级别。ConcurrentHashMap也有机率会问到,一般问的是它怎样保证线程安全(注意java7和java8的区别)。</p><h4 id="2-IO-NIO"><a href="#2-IO-NIO" class="headerlink" title="2. IO/NIO"></a>2. IO/NIO</h4><p>两者的概念、区别,使用场景</p><h4 id="3-面向对象相关"><a href="#3-面向对象相关" class="headerlink" title="3. 面向对象相关"></a>3. 面向对象相关</h4><p>面向对象三大特征,抽象类和接口的区别,值传递和引用传递理解等。</p><p>还有值类比较相等,如Integer,主要考察Integer缓存策略,详见<a href="https://www.jianshu.com/p/9cb9c61b0986" target="_blank" rel="noopener">Integer判断相等,到底该用==还是equals</a></p><h4 id="4-类加载顺序"><a href="#4-类加载顺序" class="headerlink" title="4. 类加载顺序"></a>4. 类加载顺序</h4><p>在笔记题中遇到过,一般是子类继承父类,在静态方法和构造函数打印语句,求执行顺序等。</p><h4 id="5-页面优化"><a href="#5-页面优化" class="headerlink" title="5. 页面优化"></a>5. 页面优化</h4><p>勉强算放基础吧,一般会问一个页面加载很慢可以从哪些地方排查,考经验的。</p><h3 id="JVM"><a href="#JVM" class="headerlink" title="JVM"></a>JVM</h3><ol><li>JVM内存结构及各部分作用(必问)</li><li>常见的垃圾回收算法(上面答了就必问)</li><li>了解常见的垃圾回收器(如CMS、G1等)</li><li>对象被回收的条件,可作为GC Root的对象有哪些?</li><li>新生代向老年代晋升的条件及过程描述</li></ol><p>JVM内存结构划分及常见的垃圾回收算法算是必问的了,需了解。</p><p>### 设计模式</p><ol><li>常见的设计模式有哪些?</li><li>说一个你熟悉的设计模式并画出类图。</li><li>代理模式的使用场景?</li><li>Spring/Spring MVC用到了哪些设计模式?</li></ol><p>常用设计模式及与现实场景、框架的结合</p><h3 id="数据库与缓存"><a href="#数据库与缓存" class="headerlink" title="数据库与缓存"></a>数据库与缓存</h3><ol><li>MySQL有哪些存储引擎?有哪些区别?(主要是问InnoDB和MyISAM底层实现及区别,必问)</li><li>除了主键索引外,还有哪些索引?(普通、组合、唯一、全文等)</li><li>一条sql很慢可以从哪些方面排查(数据库优化,这个几乎都有问到)</li><li>数据库表设计,项目近期的表或者设计一个场景来设计表,哪些字段可以加索引等</li><li>mongoDB储存机制</li><li>redis/memcache区别</li><li>在你的项目中,redis都缓存了哪些数据?设置了多大的过期时间?</li><li>redis是单线程的吗?为什么设计成单线程?(重要)</li><li>怎样使用redis实现分布式锁</li></ol><p>数据库储存原理,索引、优化等</p><h3 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h3><ol><li>java实现多线程的方式?除了继承Thread、实现Runnable还有吗?(Callable、线程池)</li><li>java内存模型(工作内存、主内存那块,必问)</li><li>volatile的作用及原理(必问)</li><li>java并发包(java.util.concurrent)都有哪些类?</li><li>java实现同步都有哪些方式?(重要)</li><li>线程池实现原理(重要)</li><li>ReentrantLock实现原理?可读写锁呢?</li><li>ThreadLocal原理,使用场景</li><li>notify/wait 通知阻塞机制</li></ol><p>多线程是重点,主要有java并发包及java内存模型</p><h3 id="数据结构与算法"><a href="#数据结构与算法" class="headerlink" title="数据结构与算法"></a>数据结构与算法</h3><p>数据结构这块一般结合算法来考察,主要是栈、链表、Map、二分查找、B/B+树等</p><ol><li>B/B+树结构及区别(一般在mysql储存引擎那边问,或给你几个数让你构造B/B+树)</li><li>二分查找算法、归并排序(常用算法都应掌握)</li><li>怎样使用栈实现四则运算,说说思路</li><li>手写一个链表,怎样添加、删除节点方法</li></ol><p>### 框架相关</p><ol><li>Spring的Ioc、AOP(老生常谈,必问)</li><li>Spring AOP的实现方式,使用场景(重要)</li><li>Spring MVC整体流程(必问),其中的关键类有哪些?</li><li>过滤器和拦截器的区别</li><li>mybatis中<code>#{}</code>和<code>${}</code>有哪些区别?</li><li>netty实现原理</li><li>@Resource和@Autowired区别?分别来自哪里?</li></ol><h3 id="分布式"><a href="#分布式" class="headerlink" title="分布式"></a>分布式</h3><ol><li>zookeeper实现原理?是怎样保证数据一致性的?(重要)</li><li>分布式锁有哪些实现方式?(重要)</li><li>rpc理解</li><li>dubbo是怎样实现的?为什么要用dubbo?</li></ol><p>分布式这块我不太熟,所以问的也不多,重点集中在对zookeeper的理解上了。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>其他一些就和项目以及你的个人简历相关了,介绍一下最近的项目,有哪些难点,怎样解决之类的,或是你简历中有其他可供提问的技术等,因人而异了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>问法多种多样,核心原理是一样的。从面试问的题来看,JVM、多线程和数据库是每次都会涉及的东西。另外,有一定名气的公司都会重视基础,无论大小。今后的学习方向还应从实战入手,深入架构原理分析。</p>]]></content>
<summary type="html">
<h1 id="3月java面试总结"><a href="#3月java面试总结" class="headerlink" title="3月java面试总结"></a>3月java面试总结</h1><h2 id="前言"><a href="#前言" class="headerli
</summary>
<category term="面试" scheme="https://blog.wthfeng.com/categories/%E9%9D%A2%E8%AF%95/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="面试" scheme="https://blog.wthfeng.com/tags/%E9%9D%A2%E8%AF%95/"/>
</entry>
<entry>
<title>再谈生产者消费者模式与阻塞队列</title>
<link href="https://blog.wthfeng.com/2018-01-21-%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97%E4%B8%8E%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
<id>https://blog.wthfeng.com/2018-01-21-阻塞队列与生产者消费者模式/</id>
<published>2018-01-21T06:30:00.000Z</published>
<updated>2018-10-16T13:41:30.050Z</updated>
<content type="html"><![CDATA[<h1 id="再谈生产者消费者模式与阻塞队列"><a href="#再谈生产者消费者模式与阻塞队列" class="headerlink" title="再谈生产者消费者模式与阻塞队列"></a>再谈生产者消费者模式与阻塞队列</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在<a href="http://img.wthfeng.com/java/thread/2017/12/09/wait%E4%B8%8Enotify%E6%9C%BA%E5%88%B6%E8%AE%B2%E8%A7%A3/" target="_blank" rel="noopener">Wait/Notify通知机制解析</a>文章中,介绍了生产者消费者模式及其应用,而阻塞队列的自身特点也适合生产者消费者。本文即探讨如何一步步用阻塞队列构建生产者、消费者模式。</p><h2 id="使用普通队列"><a href="#使用普通队列" class="headerlink" title="使用普通队列"></a>使用普通队列</h2><p>使用普通队列构建生产者消费者最需要考虑的问题是,如何保证队列在添加、移除操作时的线程安全。我们本例使用Lock/Condition机制确保。</p><blockquote><p>从实现来说,原生<code>synchronized</code>+<code>wait\notify</code>也能实现相同的功能,不过Lock机制具有更大灵活性,更推荐使用。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Lock lock = <span class="keyword">new</span> ReentrantLock(); <span class="comment">//锁</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Condition condition = lock.newCondition(); <span class="comment">//等待条件</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//使用ArrayDeque作为任务队列,你也可以自定义一个队列</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Queue<Task> queue = <span class="keyword">new</span> ArrayDeque<>(); </span><br><span class="line"></span><br><span class="line"><span class="comment">// 其他变量略</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//消费者线程</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Consume</span> <span class="keyword">implements</span> <span class="title">Runnable</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">run</span><span class="params">()</span> </span>{</span><br><span class="line"> lock.lock(); <span class="comment">//加锁</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (queue.size() == <span class="number">0</span>) { <span class="comment">//若任务队列为空则等待</span></span><br><span class="line"> condition.await();</span><br><span class="line"> }</span><br><span class="line"> Task task = queue.poll(); <span class="comment">//取出任务消费</span></span><br><span class="line"> System.out.println(<span class="string">"模拟消费:"</span> + task.no);</span><br><span class="line"> condition.signal(); <span class="comment">//通知生产者已消费</span></span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.MILLISECONDS.sleep(<span class="number">200</span>); <span class="comment">//暂停200ms休息</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 生产者线程</span></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Produce</span> <span class="keyword">implements</span> <span class="title">Runnable</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">run</span><span class="params">()</span> </span>{</span><br><span class="line"> lock.lock(); <span class="comment">//加锁</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (queue.size() == cap) { <span class="comment">//若达到边界值则等待</span></span><br><span class="line"> condition.await();</span><br><span class="line"> }</span><br><span class="line"> Task task = <span class="keyword">new</span> Task(number.incrementAndGet()); <span class="comment">//生产任务</span></span><br><span class="line"> queue.add(task);</span><br><span class="line"> condition.signal(); <span class="comment">//通知消费者已生产</span></span><br><span class="line"> </span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock(); <span class="comment">//解锁</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.MILLISECONDS.sleep(<span class="number">500</span>); <span class="comment">//模拟生产流程,等待200毫秒生产一个</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></pre></td></tr></table></figure><p>当生产者超过cap(任务队列最大值)时,阻塞以等待消费者消费;当消费者消费完任务后,阻塞以等待生产者生产。受篇幅限制,全部代码放于<a href="https://github.com/wangtonghe/learn-sample/blob/master/learn/src/java/com/wthfeng/learn/db/CPModel.java" target="_blank" rel="noopener">github</a>上。</p><h2 id="构建阻塞队列"><a href="#构建阻塞队列" class="headerlink" title="构建阻塞队列"></a>构建阻塞队列</h2><p>使用普通队列+Lock/Condition机制已初步实现了要求。为简洁,可以将加锁、解锁等同步机制移到队列里实现,即构成了阻塞队列。上述示例即是一个简单的阻塞队列。</p><p>另外,仔细思考上面示例,会发现生产者、消费者在调用await阻塞时等待着同一个condition条件。理论上不会出现生产者、消费者同在等待队列的情况,但为结构清晰,一般(对于数组结构的队列)使用两个等待队列实现。</p><blockquote><p>我们知道,synchronized的对象锁一个对象只能关联一个等待队列,而Lock机制则可以关联多个。可以分别为生产者和消费者分别关联各自的等待队列,<code>ArrayBlockingQueue</code>就是这么做的。</p></blockquote><p> ArrayBlockingQueue 有关锁的声明</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><br><span class="line"> </span><br><span class="line"><span class="comment">/** 锁对象 */</span></span><br><span class="line"> <span class="keyword">final</span> ReentrantLock lock;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** 等待take的等待条件对象 */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Condition notEmpty;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** 等待put操作的等待条件对象 */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Condition notFull;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//由同一锁关联的等待条件</span></span><br><span class="line"> notEmpty = lock.newCondition();</span><br><span class="line"> notFull = lock.newCondition();</span><br></pre></td></tr></table></figure><p>这样整体构造如下图所示</p><p><img src="/2018-01-21-阻塞队列与生产者消费者模式/Users/wangtonghe/Downloads/未命名文件 (3" alt>.png)</p><p>下面就用<code>ArrayBlockingQueue</code>来构建生产者消费者</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> cap = <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用ArrayBlockingQueue作为阻塞队列</span></span><br><span class="line"><span class="keyword">private</span> BlockingQueue<Task> queue = <span class="keyword">new</span> ArrayBlockingQueue<>(cap); </span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> AtomicInteger taskNo = <span class="keyword">new</span> AtomicInteger(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//消费者线程</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Consume</span> <span class="keyword">implements</span> <span class="title">Runnable</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">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Task task = queue.take(); <span class="comment">//消费出队,阻塞队列本身就可确保线程安全</span></span><br><span class="line"> System.out.println(task.no); <span class="comment">//模拟消费</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生产者线程</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Produce</span> <span class="keyword">implements</span> <span class="title">Runnable</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">run</span><span class="params">()</span> </span>{</span><br><span class="line"> Task task = <span class="keyword">new</span> Task(taskNo.getAndIncrement());</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> queue.put(task); <span class="comment">//生产入队,阻塞队列确保线程安全</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></pre></td></tr></table></figure><h2 id="ArrayBlockingQueue-实现简析"><a href="#ArrayBlockingQueue-实现简析" class="headerlink" title="ArrayBlockingQueue 实现简析"></a>ArrayBlockingQueue 实现简析</h2><p><code>ArrayBlockingQueue</code>实现原理上文已经提及,即与上面的普通队列类似,不同之处在于<code>ArrayBlockingQueue</code>使用的是一个锁和其关联的两个等待条件。一个为<code>notEmpty</code>,表示消费的等待条件(队列没元素可消费了),一个为<code>notFull</code>,表示生产的等待条件(没空位可生产了)。这里以<code>take()</code>方法为例简单了解下。</p><p><code>take()</code>方法可类比消费者消费。含义与前面类似,不同的只是其生产或消费阻塞时用了各自的等待条件。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">public E take() throws InterruptedException {</span><br><span class="line"> final ReentrantLock lock = this.lock; //锁对象</span><br><span class="line"> lock.lockInterruptibly(); //加锁,可中断</span><br><span class="line"> try {</span><br><span class="line"> while (count == 0)</span><br><span class="line"> notEmpty.await(); //若队列为空,take操作等待</span><br><span class="line"> return dequeue();</span><br><span class="line"> } finally {</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"> private E dequeue() {</span><br><span class="line"> // assert lock.isHeldByCurrentThread();</span><br><span class="line"> // assert lock.getHoldCount() == 1;</span><br><span class="line"> // assert items[takeIndex] != null;</span><br><span class="line"> final Object[] items = this.items;</span><br><span class="line"> @SuppressWarnings("unchecked")</span><br><span class="line"> E e = (E) items[takeIndex];</span><br><span class="line"> items[takeIndex] = null;</span><br><span class="line"> if (++takeIndex == items.length) takeIndex = 0;</span><br><span class="line"> count--;</span><br><span class="line"> if (itrs != null)</span><br><span class="line"> itrs.elementDequeued();</span><br><span class="line"> notFull.signal(); // 唤醒可能阻塞的生产者</span><br><span class="line"> return e;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="使用链表式的阻塞队列"><a href="#使用链表式的阻塞队列" class="headerlink" title="使用链表式的阻塞队列"></a>使用链表式的阻塞队列</h2><p>上面我们实现了生产者、消费者模式,这样实现的一大硬伤在于:同一时刻只能有一个生产者或消费者操作队列,而生产和消费本就是不相关的操作。两者能各自操作吗?</p><p>对于数组来说显然是不能的,本身即一个整体无法同时线程安全的插入和删除。不过可以使用链表:对于添加只在尾指针操作;对于删除则在头指针操作。这样即可以同时添加和删除,互不影响。</p><p>链表式阻塞队列的简要实现(代码见<a href="https://github.com/wangtonghe/learn-sample/blob/master/learn/src/java/com/wthfeng/learn/db/MyLinkedBlockingQueue.java" target="_blank" rel="noopener">github</a>),具体说明见注释</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><br><span class="line"> <span class="keyword">private</span> Lock takeLock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Condition takeCondition = takeLock.newCondition();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Lock putLock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Condition putCondition = putLock.newCondition();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 入队,若队列满则等待</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> e 入队元素</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><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> }</span><br><span class="line"> Node<E> node = <span class="keyword">new</span> Node<>(e);</span><br><span class="line"> <span class="keyword">int</span> c = -<span class="number">1</span>;</span><br><span class="line"> takeLock.lockInterruptibly(); <span class="comment">//takeLock,添加元素的锁</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (count.get() == capacity) { <span class="comment">//若队列满,阻塞以等待</span></span><br><span class="line"> takeCondition.await();</span><br><span class="line"> }</span><br><span class="line"> enqueue(node);</span><br><span class="line"> c = count.incrementAndGet(); <span class="comment">//更新队列元素数</span></span><br><span class="line"> <span class="keyword">if</span> (c < capacity) {</span><br><span class="line"> takeCondition.signal(); <span class="comment">//若入队后发现还有空位,通知其他阻塞的入队线程(若有)</span></span><br><span class="line"> }</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 == <span class="number">1</span>) { <span class="comment">//若入队前队列为空,则通知被阻塞的出队线程,现在可以出队了</span></span><br><span class="line"> putLock.lockInterruptibly();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> putCondition.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><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 出队,若无元素一直等待</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="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">public</span> E <span class="title">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> takeLock.lock(); <span class="comment">//takeLock,移除元素的锁</span></span><br><span class="line"> E e = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">int</span> c = -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (count.get() == <span class="number">0</span>) { <span class="comment">//队列为空,移除操作阻塞</span></span><br><span class="line"> takeCondition.await();</span><br><span class="line"> }</span><br><span class="line"> e = dequeue();</span><br><span class="line"> c = count.decrementAndGet(); <span class="comment">//更新队列元素数</span></span><br><span class="line"> <span class="keyword">if</span> (c > <span class="number">0</span>) { <span class="comment">//若出队后仍有元素,通知其他被阻塞的出队线程(若有)</span></span><br><span class="line"> takeCondition.signal();</span><br><span class="line"> }</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 class="number">1</span>) { <span class="comment">//若出队前队列已满,通知阻塞的入队线程,现在可以入队了</span></span><br><span class="line"> putLock.lockInterruptibly();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> putCondition.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><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="参考资源"><a href="#参考资源" class="headerlink" title="参考资源"></a>参考资源</h2><ol><li>java8 JDK ArrayBlockingQueue、LinkedBlockingQueue 源码</li><li><a href="http://img.wthfeng.com/java/thread/2017/12/09/wait%E4%B8%8Enotify%E6%9C%BA%E5%88%B6%E8%AE%B2%E8%A7%A3/" target="_blank" rel="noopener">Wait/Notify通知机制解析</a></li><li><a href="http://www.importnew.com/27063.html" target="_blank" rel="noopener">Java 实现生产者 – 消费者模型</a></li></ol>]]></content>
<summary type="html">
<h1 id="再谈生产者消费者模式与阻塞队列"><a href="#再谈生产者消费者模式与阻塞队列" class="headerlink" title="再谈生产者消费者模式与阻塞队列"></a>再谈生产者消费者模式与阻塞队列</h1><h2 id="前言"><a href="
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="设计模式" scheme="https://blog.wthfeng.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>百万英雄类答题游戏的程序员打开方式</title>
<link href="https://blog.wthfeng.com/2018-01-10-%E7%99%BE%E4%B8%87%E8%8B%B1%E9%9B%84%E7%B1%BB%E6%B8%B8%E6%88%8F%E8%BE%85%E5%8A%A9/"/>
<id>https://blog.wthfeng.com/2018-01-10-百万英雄类游戏辅助/</id>
<published>2018-01-10T01:30:00.000Z</published>
<updated>2018-10-16T13:41:30.050Z</updated>
<content type="html"><![CDATA[<h1 id="百万英雄类答题游戏的程序员打开方式"><a href="#百万英雄类答题游戏的程序员打开方式" class="headerlink" title="百万英雄类答题游戏的程序员打开方式"></a>百万英雄类答题游戏的程序员打开方式</h1><p>看了<a href="https://juejin.im/post/5a52f59f51882573520d3dc6" target="_blank" rel="noopener">《程序员如何玩转《冲顶大会》?》</a>大受启发,刚好前几天研究了下微信跳一跳的辅助,正好可以用上。</p><p>思路很明确,把答案截图pull过来,通过OCR识别成文字后再放到百度搜索。记过几番尝试后,一些容易搜索的问题还是是可以搜索答案的。</p><p>目前它是手动的,也就是说每次答案出现,手动执行脚本返回答案。同样由于个别题目原因(如某个词有多少笔画),不是每次都能搜出来。这时就考验你的手速和运气了。</p><p>实现语言python,用到的类库如下:</p><ol><li>PIL</li><li>pytesseract(图片识别库)</li><li>BeautifulSoup(页面解析)</li></ol><p>文字识别引擎需单独安装,参见<a href="http://blog.csdn.net/qiushi_1990/article/details/78041375" target="_blank" rel="noopener">Python人工智能之图片识别,Python3一行代码实现图片文字识别</a>以及<a href="http://blog.csdn.net/u010670689/article/details/78374623" target="_blank" rel="noopener">mac上文字识别 Tesseract-OCR for mac</a></p><p>主体代码如下:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">import</span> pytesseract</span><br><span class="line"><span class="keyword">from</span> urllib.request <span class="keyword">import</span> urlopen</span><br><span class="line"><span class="keyword">import</span> urllib.request</span><br><span class="line"><span class="keyword">from</span> bs4 <span class="keyword">import</span> BeautifulSoup</span><br><span class="line"></span><br><span class="line">DEFAULT_WIDTH = <span class="number">720</span></span><br><span class="line">DEFAULT_HEIGHT = <span class="number">1280</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment"># 720*1280分辨率坐标</span></span><br><span class="line"> left_top_x = <span class="number">30</span></span><br><span class="line"> left_top_y = <span class="number">200</span></span><br><span class="line"> right_bottom_x = <span class="number">680</span></span><br><span class="line"> right_bottom_y = <span class="number">380</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 1. 截图</span></span><br><span class="line"> os.system(<span class="string">'adb shell screencap -p /sdcard/answer.png'</span>)</span><br><span class="line"> os.system(<span class="string">'adb pull /sdcard/answer.png answer.png'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 2. 截取题目并文字识别</span></span><br><span class="line"> image = Image.open(<span class="string">'answer.png'</span>)</span><br><span class="line"> crop_img = image.crop((left_top_x, left_top_y, right_bottom_x, right_bottom_y))</span><br><span class="line"> crop_img.save(<span class="string">'crop.png'</span>)</span><br><span class="line"> text = pytesseract.image_to_string(crop_img, lang=<span class="string">'chi_sim'</span>)</span><br><span class="line"> print(text)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 3. 去百度知道搜索</span></span><br><span class="line"> text = text[<span class="number">2</span>:] <span class="comment"># 把题号去掉</span></span><br><span class="line"> <span class="comment"># text = '一亩地大约是多少平米'</span></span><br><span class="line"> wd = urllib.request.quote(text)</span><br><span class="line"> url = <span class="string">'https://zhidao.baidu.com/search?ct=17&pn=0&tn=ikaslist&rn=10&fr=wwwt&word={}'</span>.format(</span><br><span class="line"> wd)</span><br><span class="line"> print(url)</span><br><span class="line"> result = urlopen(url)</span><br><span class="line"> body = BeautifulSoup(result.read(), <span class="string">'html5lib'</span>)</span><br><span class="line"> good_result_div = body.find(class_=<span class="string">'list-header'</span>).find(<span class="string">'dd'</span>)</span><br><span class="line"> second_result_div = body.find(class_=<span class="string">'list-inner'</span>).find(class_=<span class="string">'list'</span>)</span><br><span class="line"> <span class="keyword">if</span> good_result_div <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> good_result = good_result_div.get_text()</span><br><span class="line"> print(good_result.strip())</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> second_result_div <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> second_result = second_result_div.find(<span class="string">'dl'</span>).find(<span class="string">'dd'</span>).get_text()</span><br><span class="line"> print(second_result.strip())</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> main()</span><br></pre></td></tr></table></figure><p>文字识别需经训练,训练越多结果越准。</p><p>我把代码放到github上了,可围观<a href="https://github.com/wangtonghe/hq-answer-assist" target="_blank" rel="noopener">hq-answer-assist</a></p><p>要想实现更智能化,有个思路是不停的截图(1秒一次),一旦截到答题页(可以用答题页的色差来做),做文字识别后百度,将百度后的结果与选项做比较,哪个出现次数最多哪个就是最佳答案,这里可以加个判断,如果特别确定直接模拟点击事件选答案,不确定就手工。</p><p>有同学提到分析请求,也是个思路,后续可以研究。</p><p>欢迎探讨其他更好的实现方式。</p>]]></content>
<summary type="html">
<h1 id="百万英雄类答题游戏的程序员打开方式"><a href="#百万英雄类答题游戏的程序员打开方式" class="headerlink" title="百万英雄类答题游戏的程序员打开方式"></a>百万英雄类答题游戏的程序员打开方式</h1><p>看了<a href=
</summary>
<category term="python" scheme="https://blog.wthfeng.com/categories/python/"/>
<category term="python" scheme="https://blog.wthfeng.com/tags/python/"/>
<category term="游戏" scheme="https://blog.wthfeng.com/tags/%E6%B8%B8%E6%88%8F/"/>
<category term="HQ" scheme="https://blog.wthfeng.com/tags/HQ/"/>
</entry>
<entry>
<title>微信跳一跳辅助原理浅析</title>
<link href="https://blog.wthfeng.com/2018-01-07-%E8%B7%B3%E4%B8%80%E8%B7%B3%E8%BE%85%E5%8A%A9%E5%AE%9E%E7%8E%B0/"/>
<id>https://blog.wthfeng.com/2018-01-07-跳一跳辅助实现/</id>
<published>2018-01-07T07:30:00.000Z</published>
<updated>2018-10-16T13:41:30.049Z</updated>
<content type="html"><![CDATA[<h1 id="微信跳一跳辅助原理浅析"><a href="#微信跳一跳辅助原理浅析" class="headerlink" title="微信跳一跳辅助原理浅析"></a>微信跳一跳辅助原理浅析</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本文从原理和算法的角度(参考<a href="https://github.com/wangshub/wechat_jump_game" target="_blank" rel="noopener">https://github.com/wangshub/wechat_jump_game</a>的实现)<strong>探讨</strong>怎样实现跳一跳的辅助,做到知其然还要只其所以然。<strong>尽量使一个没任何外挂经验的任何语言的普通人也能做出辅助</strong>来。当然如果你只打算刷分的话,那本文可能没什么帮助了。(另外,本教程只针对android)</p><h2 id="原理介绍"><a href="#原理介绍" class="headerlink" title="原理介绍"></a>原理介绍</h2><p>原理其实很简单,棋子跳跃的时间和距离有关。那就利用adb(Android Debug Bridge)将当前手机的截图下载到电脑上,对截图进行分析,算出棋子和要跳的方块之间的距离,再乘以适当的参数即可得到时间。再利用adb模拟手机触摸事件,触摸对应的时间即可。</p><p>有2点需要明白的关键:</p><ol><li>什么是adb?</li><li>怎样根据图片算出距离</li></ol><p>这2点也是实现辅助的关键,下面一一介绍</p><h2 id="ADB介绍"><a href="#ADB介绍" class="headerlink" title="ADB介绍"></a>ADB介绍</h2><p>adb这东西应该算android的概念,全称<code>Android Debug Bridge</code>,翻译过来android调试桥?反正是与android设备(如手机)交互的工具。可使用它调试手机应用(需要手机授权)。如安装一个APP,模拟点击事件等,下面列出几个与本文有关的命令。(详情参考<a href="https://developer.android.com/studio/command-line/adb.html?hl=zh-cn" target="_blank" rel="noopener">Android adb</a>)</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">adb shell screencap /sdcard/screen.png #屏幕截图并储存</span><br><span class="line"></span><br><span class="line">adb pull /sdcard/screen.png # 将指定位置图片拉取过来</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 向设备发送模拟操作(输入、触摸等)</span></span><br><span class="line">adb shell input </span><br><span class="line">usage:input text <string> # 输入文字</span><br><span class="line"> input keyevent <key code number or name> # 按键</span><br><span class="line"> input tap <x> <y> # 点击</span><br><span class="line"> input swipe <x1> <y1> <x2> <y2> [duration(ms)] # 滑动</span><br></pre></td></tr></table></figure><p>模拟操作的只用到了 <code>adb shell input swipe <x1> <y1> <x2> <y2> [duration(ms)]</code>,表示从(x1,x2)的位置滑动到(x2,y2)的位置,滑动时间为duration毫秒。</p><h2 id="怎样算出距离"><a href="#怎样算出距离" class="headerlink" title="怎样算出距离"></a>怎样算出距离</h2><p>图片拿到了,怎样算出距离是个难题。在介绍算法之前,有必要先了解下计算机是怎样储存和表示图片信息的。</p><p>计算机将图片的颜色表示为RGBA值。我们知道颜色的三原色红绿蓝,RGB即分别表示红绿蓝。剩下的A表示透明度。每个GRBA值表示一个像素,每个图片就是由这些千千万万个像素点构成的。</p><p>同时,为表示每个像素点,引入了坐标概念。以左上顶点为原点(0,0),向右为x轴,向下为y轴,像素点坐标均为正值(与数学上不太一致)</p><p><img src="http://img.wthfeng.com/img/posts/game/autojump.png" alt></p><p>如上图所示,我们要想找到棋子,需要充分利用棋子颜色的这个特征,一行一行遍历像素点,直到找到棋子底座颜色(深紫色的)的若干像素点(实际是一个颜色范围区间),平均后得到底座中心位置。</p><p>至于方块的位置就不好找了,可以利用色差来做。如上图,背景色是浅黄色,要跳的方块是深灰色,可从上到下扫描像素点,记录背景色,一旦发现有与背景色相差太大的像素点,即表示发现了目标方块,照着此颜色多找几个点,平均下得到方块的中心点。(也可直接用其他方法或用其他方式选取中点,看你自己研究了)</p><p>不过这样会有点问题,万一棋子高度大于目标方块就会有误,这在奶茶杯的方块会遇到。这时将棋子的颜色排除掉即可。总之原则就是根据色差找到目标中点。</p><h2 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h2><p>上面的介绍大致就知道了实现原理,由此可知重点是根据图片找到适合的中点。原则上讲只要有处理图片及像素的库的语言都可实现。python、go、java等均可。python处理图片更简单,下面用python简要实现。</p><p>寻找棋子中点</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">width, height = image.size <span class="comment"># image为图片实例</span></span><br><span class="line"> num = <span class="number">0</span></span><br><span class="line"> sum_x = <span class="number">0</span></span><br><span class="line"> sum_y = <span class="number">0</span></span><br><span class="line"> s_height = int(height / <span class="number">3</span>) <span class="comment"># 查找的起始高度</span></span><br><span class="line"> e_height = int(height / <span class="number">5</span> * <span class="number">4</span>) <span class="comment"># 查找的终止高度</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">0</span>, width, <span class="number">10</span>): <span class="comment"># 宽度查找,步长为10</span></span><br><span class="line"> <span class="keyword">for</span> j <span class="keyword">in</span> range(s_height, e_height, <span class="number">20</span>): <span class="comment"># 高度查找,步长为20</span></span><br><span class="line"> pixel = image.getpixel((i, j)) <span class="comment"># 获取像素点</span></span><br><span class="line"> <span class="comment"># print(pixel)</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="number">0x20</span> < pixel[<span class="number">0</span>] < <span class="number">0x40</span>) \</span><br><span class="line"> <span class="keyword">and</span> (<span class="number">0x20</span> < pixel[<span class="number">1</span>] < <span class="number">0x40</span>) \</span><br><span class="line"> <span class="keyword">and</span> (<span class="number">0x45</span> < pixel[<span class="number">2</span>] < <span class="number">0x80</span>): <span class="comment"># 比较是否在紫色棋子的颜色范围</span></span><br><span class="line"> r, g, b, a = pixel</span><br><span class="line"> <span class="comment"># print('颜色是{},{},{}'.format(r, g, b))</span></span><br><span class="line"> <span class="comment"># print('坐标:{},{}'.format(i, j))</span></span><br><span class="line"> num = num + <span class="number">1</span></span><br><span class="line"> sum_x = sum_x + i</span><br><span class="line"> sum_y = sum_y + j</span><br><span class="line"> <span class="keyword">if</span> num == <span class="number">0</span>: <span class="comment"># 出错,停止</span></span><br><span class="line"> <span class="keyword">return</span> DEFAULT_ERROR_DISTANCE</span><br><span class="line"> avg_x = int(sum_x / num) <span class="comment"># 找出的点平均后得到中点</span></span><br><span class="line"> avg_y = int(sum_y / num)</span><br><span class="line"></span><br><span class="line"> print(<span class="string">'棋子坐标为:{},{}'</span>.format(avg_x, avg_y))</span><br></pre></td></tr></table></figure><p>寻找方块中点</p><blockquote><p>以下为寻找方块中点,原理是根据色差。具体:记录上一个像素点,当前像素点与上一个的比较,<br>若相差过大,表明找到了目标方块的第一个像素点(需排除是棋子的可能),记录在select_color里。<br>以后遍历时当前像素点就与select_color比较,相差不大则表明是目标方块的点,记录。<br>收集足够多的点或遍历完成后求中点即可。</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line"> pre_px = <span class="literal">None</span></span><br><span class="line"> h_num = <span class="number">0</span></span><br><span class="line"> h_sum_x = <span class="number">0</span></span><br><span class="line"> h_sum_y = <span class="number">0</span></span><br><span class="line"> select_color = <span class="literal">None</span></span><br><span class="line"> l_num = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"> flag = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> j <span class="keyword">in</span> range(s_h, e_h, <span class="number">30</span>):</span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">0</span>, width, <span class="number">30</span>):</span><br><span class="line"> px = image.getpixel((i, j))</span><br><span class="line"> r, g, b, a = px</span><br><span class="line"> cap, l_num = compare_px(pre_px, px, select_color, l_num, i) <span class="comment"># 比较</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> is_spot(px):</span><br><span class="line"> pre_px = px</span><br><span class="line"> <span class="keyword">if</span> cap: <span class="comment"># 若色差够大</span></span><br><span class="line"> h_num = h_num + <span class="number">1</span></span><br><span class="line"> h_sum_x = h_sum_x + i</span><br><span class="line"> h_sum_y = h_sum_y + j</span><br><span class="line"> <span class="comment"># print('颜色是{},{},{}'.format(r, g, b))</span></span><br><span class="line"> print(<span class="string">'寻找的坐标:{},{}'</span>.format(i, j))</span><br><span class="line"> <span class="keyword">if</span> h_num == <span class="number">1</span>:</span><br><span class="line"> select_color = r, g, b, i</span><br><span class="line"> <span class="keyword">if</span> l_num == <span class="number">12</span>: <span class="comment"># 找到足够多的点,跳出循环</span></span><br><span class="line"> flag = <span class="literal">True</span></span><br><span class="line"> <span class="keyword">if</span> flag:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">if</span> h_num == <span class="number">0</span>: <span class="comment"># 未找到,重新开始</span></span><br><span class="line"> <span class="keyword">return</span> DEFAULT_ERROR_DISTANCE</span><br><span class="line"></span><br><span class="line"> h_avg_x = int(h_sum_x / h_num)</span><br><span class="line"> h_avg_y = int(h_sum_y / h_num)</span><br><span class="line"> </span><br><span class="line"> print(<span class="string">'棋盘坐标为:{},{}'</span>.format(h_avg_x, h_avg_y))</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="function"><span class="keyword">def</span> <span class="title">compare_px</span><span class="params">(pre, cur, select_cl, like_num, x)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> pre <span class="keyword">is</span> <span class="literal">None</span>: <span class="comment"># 第一次查找</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span>, like_num</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> pr, py, pb, pa = pre</span><br><span class="line"> r, y, b, a = cur</span><br><span class="line"> <span class="keyword">if</span> select_cl <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line"> <span class="keyword">if</span> (abs(pr - r) + abs(py - y) + abs(pb - b) > DEFAULT_GAP) \</span><br><span class="line"> <span class="keyword">and</span> <span class="keyword">not</span> is_spot(cur): <span class="comment"># 若与上一个点色差过大且不是棋子</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span>, like_num + <span class="number">1</span> <span class="comment"># 目标色点还未赋值,表明第一次发现色点</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span>, like_num <span class="comment"># 色差过大,未找到</span></span><br><span class="line"> <span class="keyword">else</span>: <span class="comment"># 则与目标色点比较,若相似且距离合适则表明又发现了一个色点</span></span><br><span class="line"> sr, sy, sb, sx = select_cl</span><br><span class="line"> <span class="keyword">if</span> abs(sr - r) < DEFAULT_GAP \</span><br><span class="line"> <span class="keyword">and</span> abs(sy - y) < DEFAULT_GAP \</span><br><span class="line"> <span class="keyword">and</span> abs(sb - b) < DEFAULT_GAP \</span><br><span class="line"> <span class="keyword">and</span> abs(sx - x) < DEFAULT_DISTANCE:</span><br><span class="line"> like_num += <span class="number">1</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span>, like_num</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span>, like_num <span class="comment"># 相差过大,不是上次选中的色点</span></span><br></pre></td></tr></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>我将其简要实现放到了github上,有详细的注释及说明,地址在<a href="https://github.com/wangtonghe/wechat-simple-jump" target="_blank" rel="noopener">微信跳一跳辅助</a>,感兴趣的同学可以去看看。一起谈探讨更好的实现。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://github.com/wangshub/wechat_jump_game" target="_blank" rel="noopener">python 微信《跳一跳》辅助</a></li><li><a href="https://book.douban.com/subject/26836700/" target="_blank" rel="noopener">Python编程快速上手</a> P333</li><li><a href="https://developer.android.com/studio/command-line/adb.html?hl=zh-cn" target="_blank" rel="noopener">Android adb介绍</a></li></ol>]]></content>
<summary type="html">
<h1 id="微信跳一跳辅助原理浅析"><a href="#微信跳一跳辅助原理浅析" class="headerlink" title="微信跳一跳辅助原理浅析"></a>微信跳一跳辅助原理浅析</h1><h2 id="前言"><a href="#前言" class="head
</summary>
<category term="python" scheme="https://blog.wthfeng.com/categories/python/"/>
<category term="python" scheme="https://blog.wthfeng.com/tags/python/"/>
<category term="游戏" scheme="https://blog.wthfeng.com/tags/%E6%B8%B8%E6%88%8F/"/>
<category term="wechat" scheme="https://blog.wthfeng.com/tags/wechat/"/>
</entry>
<entry>
<title>AbstractQueuedSynchronizer整体解析</title>
<link href="https://blog.wthfeng.com/2017-12-10-AQS%E6%95%B4%E4%BD%93%E8%AE%B2%E8%A7%A3/"/>
<id>https://blog.wthfeng.com/2017-12-10-AQS整体讲解/</id>
<published>2017-12-10T14:47:00.000Z</published>
<updated>2018-10-16T13:41:30.048Z</updated>
<content type="html"><![CDATA[<h1 id="AbstractQueuedSynchronizer整体解析"><a href="#AbstractQueuedSynchronizer整体解析" class="headerlink" title="AbstractQueuedSynchronizer整体解析"></a>AbstractQueuedSynchronizer整体解析</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在此之前,我们深入源码分析过<a href="http://img.wthfeng.com/java/aqs/%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/2017/05/21/ReentrantLock%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6-%E4%B8%80/" target="_blank" rel="noopener">ReentrantLock系列</a>,在那里就探讨过AbstractQueuedSynchronizer(下称AQS)类,称其是同步组件乃至整个并发包的基础类。这篇文章就深入AQS,从AQS的角度了解同步器以及ReentrantLock、ReentrantReadWriteLock等的实现机制,实现自定义的同步组件,以窥探整个同步框架的全貌。</p><h2 id="AQS及同步器整体介绍"><a href="#AQS及同步器整体介绍" class="headerlink" title="AQS及同步器整体介绍"></a>AQS及同步器整体介绍</h2><p>有关类字段及方法的介绍,在<a href="http://img.wthfeng.com/java/aqs/%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/2017/05/21/ReentrantLock%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6-%E4%B8%80/#2-aqs类概览" target="_blank" rel="noopener">ReentrantLock原理探究(一)</a>就已说过,今天我们从整体上来了解AQS。</p><p>从AQS类的注释中,我们可以了解到:<strong>该类是一个用于构建锁或其他同步器的基础框架,使用一个int的成员变量表示同步状态。另外,还有一个内置的先进先出的队列可储存竞争同步状态时排队的线程。</strong></p><p>将上面描述的翻译成通俗的语言就是:<strong>有一个共享资源state(int类型的变量),各个线程去竞争这个资源,竞争到的拥有资源,去处理自己的逻辑;没竞争到去排队(进入先进先出队列),等拥有资源的线程释放共享资源后,队列中线程的再去竞争。</strong></p><p>一图胜千言,画成流程图就像下面的样子:</p><p><img src="http://img.wthfeng.com/img/posts/java/thread/aqs-status.png" alt></p><blockquote><p>有4个线程去竞争同步变量(锁的内在表示)。这里我们假设线程A得到了,其他竞争失败的线程进入同步队列等待,得到同步变量的线程A执行自己的逻辑。执行完毕后通知同步队列的线程再去竞争锁。</p></blockquote><p>AQS基本实现了以上通用的功能,包括获取锁后的同步处理,释放锁后通知事件等。但<strong>有关获取、释放锁的条件等业务相关代码留给了子类去实现</strong>。即AQS搭好了整体框架,子类去实现某个业务点。以下是2个具体实例。</p><ol><li><p>ReentrantLock,是排他锁,某个线程获取锁后其他线程就会阻塞直至锁的释放。共享资源state初始值为0,表示资源未被占有。某线程访问并设置state为1,表示该线程占有了锁。当其他线程读取到state不为0后进入队列等待,直到占有锁的线程将其设为0后,队列线程才会得到通知,重新竞争锁。(事实上ReentrantLock作为可重入锁,占有锁的线程再次进入锁会使state加1,退出一次state减1,不会把自己锁死)</p></li><li><p>CountDownLatch,共享锁。可用于控制线程执行、结束的时机。如我们想要主线程在2个子线程执行完后再结束,这时使用CountDownLatch通过构造函数将共享变量state设为2,将主线程锁住,每个子线程结束后state减一,state为0后表示两子线程执行完毕,此时主线程才得以释放。</p></li></ol><p>也即是说,通过AQS,我们将能很简单的实现同步的要求。这也是<strong>模板方法模式</strong>的运用。</p><h2 id="一个简单的锁"><a href="#一个简单的锁" class="headerlink" title="一个简单的锁"></a>一个简单的锁</h2><p>根据上面提到的,我们来自制一个独占类型的锁。</p><blockquote><p>根据AQS的建议,实现AQS的类最好为同步器的内部类,外部类方法再去引用其内部类的方法。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyLock</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Sync sync = <span class="keyword">new</span> Sync();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//AQS的子类,由于是独占锁,实现tryAcquire和tryRelease两方法</span></span><br><span class="line"> <span class="keyword">private</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><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="comment">//若状态为1,说明有其他线程已占有锁,直接返回false</span></span><br><span class="line"> <span class="keyword">if</span>(getState()==arg){</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">//若状态为0,将其设为1,表示占有锁</span></span><br><span class="line"> <span class="keyword">return</span> compareAndSetState(<span class="number">0</span>, arg);</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">protected</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="comment">//设置状态为0,表示释放锁</span></span><br><span class="line"> setState(<span class="number">0</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><br><span class="line"></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">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> sync.acquire(<span class="number">1</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>{</span><br><span class="line"> sync.release(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样我们就实现了一个简单的锁。不过这个锁相比ReentrantLock来说,没有实现可重入性(也没有实现关联条件Condition)。也就是说它会被自己锁死:当某个线程在获取锁后再次尝试获取锁,会导致死锁。不过,实现类似i++的同步倒是可以做到的。</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="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"> myLock.lock();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> total++;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> myLock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="示例解析"><a href="#示例解析" class="headerlink" title="示例解析"></a>示例解析</h2><p>关于以上示例,<code>tryAcquire()</code>和<code>tryRelease()</code>两个方法即为子类需实现的模板方法(这是对于独占锁而言,对于共享锁是<code>tryAcquireShared/tryReleaseShared</code>,下文会提到)。其返回值表示锁是否获取、释放成功。</p><p>以获取锁为例,<code>acquire</code>是AQS具体获取锁的方法,在其中会调用子类实现的<code>tryAcquire()</code>,并根据返回值进行具体操作。</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="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="keyword">if</span> (!tryAcquire(arg) && <span class="comment">//会调用子类的tryAcquire方法,实现不同的acquire含义</span></span><br><span class="line"> acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) <span class="comment">//锁获取失败,加入同步队列等其他操作</span></span><br><span class="line"> selfInterrupt();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>AQS关于获取、释放锁方法如下</p><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>acquire / acquireInterruptibly</td><td>独占式获取同步状态,若获取失败,将进入同步队列。后者与前者的区别在于,后者能在同步队列中响应中断</td></tr><tr><td>acquireShared / acquireSharedInterruptibly</td><td>共享式获取同步状态,后者能响应中断</td></tr><tr><td>release</td><td>独占式释放同步状态,成功后将同步队列的第一个线程唤醒</td></tr><tr><td>releaseShared</td><td>共享式释放同步状态</td></tr></tbody></table><p>AQS关于同步状态的方法如下</p><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>getState</td><td>获取同步状态</td></tr><tr><td>setState(state)</td><td>设置同步状态</td></tr><tr><td>compareAndSetState(except,update)</td><td>使用CAS设置同步状态,只有当同步状态值为except时,才将其设置update</td></tr></tbody></table><p>需要子类实现的方法如下</p><table><thead><tr><th>方法</th><th>实现思路</th></tr></thead><tbody><tr><td>tryAcquire</td><td>独占式获取同步状态,实现该方法需要查询当前状态,并判断状态是否符合预期(根据各子类不同功能判断条件各异),然后再根据CAS设置同步状态</td></tr><tr><td>tryRelease</td><td>独占式释放同步状态</td></tr><tr><td>tryAcquireShared</td><td>共享式获取同步状态,若返回值大于等于0,表示获取成功,否则表示失败</td></tr><tr><td>tryReleaseShared</td><td>共享式释放同步状态</td></tr><tr><td>isHeldExclusively</td><td>在独占模式下,同步状态是否被占用</td></tr></tbody></table><p>了解这些知识后再来看上面的例子,尤其是开始展示的那张流程图,对AQS的实现机制应该有了大致了解。你可以尝试实现ReentrantLock试试,需注意的是,可重入锁要保存持有锁的线程,当加锁时,判断当前线程是否持有锁,若持有,直接进入同步块,同时将state加1,当试图释放锁时,将state减1。若state减到0,释放锁。其他过程与其他一致。</p><h2 id="Condition条件变量"><a href="#Condition条件变量" class="headerlink" title="Condition条件变量"></a>Condition条件变量</h2><p>synchronized配合wait/notify可实现等待通知模式。同样,AQS及其子类也可实现类似语义。这就是AQS的Condition接口。</p><p>Condition使用方式与wait/notify类似,都需要在持有锁的情况下调用,都有等待和超时等待,唤醒和全部唤醒。具体操作流程如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">private</span> Lock lock = <span class="keyword">new</span> ReentrantLock(); <span class="comment">//创建锁</span></span><br><span class="line"></span><br><span class="line">Condition condition = lock.newCondition(); <span class="comment">//创建条件</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//条件等待</span></span><br><span class="line">lock.lock(); <span class="comment">//先加锁</span></span><br><span class="line"><span class="comment">//条件等待操作</span></span><br><span class="line">condition.await(); <span class="comment">//等待</span></span><br><span class="line">lock.unlock(); <span class="comment">//释放锁</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 条件唤醒</span></span><br><span class="line">lock.lock(); <span class="comment">//先加锁</span></span><br><span class="line"><span class="comment">//唤醒操作</span></span><br><span class="line">condition.signal(); <span class="comment">//唤醒</span></span><br><span class="line">lock.unlock(); <span class="comment">//释放锁</span></span><br></pre></td></tr></table></figure><p>以上使用的是ReentrantLock类作为示例,其他类的条件变量操作与之类似。与前面一样,我们可以简单在AQS子类中简单重写即可实现此功能。</p><p>还用上面的MyLock类实现Condition,其他部分省略</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="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyLock</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Sync sync = <span class="keyword">new</span> Sync();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//AQS的子类,由于是独占锁,实现tryAcquire和tryRelease两方法</span></span><br><span class="line"> <span class="keyword">private</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><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//判断锁被占有的条件,在ConditionObject的方法中会使用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">isHeldExclusively</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> getState()==<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//ConditionObject类为AQS的成员类,返回ConditionObject实例即可</span></span><br><span class="line"> <span class="function">Condition <span class="title">newCondition</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ConditionObject();</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><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> Condition <span class="title">getCondition</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> sync.newCondition();</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><br><span class="line">}</span><br></pre></td></tr></table></figure><p>AQS内部有个称为<code>ConditionObject</code>的内部成员类,该类实现了<code>Condition</code>接口,且与AQS的状态相关联。实现Condition功能时只需在子类构建出<code>ConditionObject</code>对象即可。就如MyLock展示的一样。</p><p>关于Condition的实现原理,首先需要知道这2点:</p><ol><li>无论调用await或是signal方法,都必须获取到该Condition关联的锁。</li><li>Condition持有一个等待队列(不同于上面提到的同步队列,上面的同步队列为AQS类持有,而这个等待队列由Condition(AQS的内部类)持有)</li></ol><p>同步队列与等待队列结构图如下所示</p><p><img src="http://img.wthfeng.com/img/posts/java/thread/condition.png" alt></p><p>我们假设持有锁的线程A调用了await,线程A会进入等待队列,随后释放锁,通知同步队列其他节点去竞争锁。做完这些操作会就等着被其他线程唤醒或超时时间了。</p><p>若此时线程B调用了signal,则会从等待队列中取出一个节点加入同步队列并唤醒(也就是将线程A从等待队列移到同步队列)。此时线程A已被B唤醒并处于同步队列中,这时就可以重新竞争锁并执行了。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>有关AQS的整体分析就到这了,有时间再来从源码具体实现角度解析。</p><p>若本文有不正确之处,还请各位指正。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://book.douban.com/subject/26591326/" target="_blank" rel="noopener">java并发编程的艺术</a></li><li><a href="http://img.wthfeng.com/java/aqs/%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/2017/05/21/ReentrantLock%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6-%E4%B8%80/" target="_blank" rel="noopener">ReentrantLock原理探究</a></li></ol>]]></content>
<summary type="html">
<h1 id="AbstractQueuedSynchronizer整体解析"><a href="#AbstractQueuedSynchronizer整体解析" class="headerlink" title="AbstractQueuedSynchronizer整体解析">
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="并发与多线程" scheme="https://blog.wthfeng.com/tags/%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
<category term="AQS" scheme="https://blog.wthfeng.com/tags/AQS/"/>
</entry>
<entry>
<title>Wait/Notify通知机制解析</title>
<link href="https://blog.wthfeng.com/2017-12-09-wait%E4%B8%8Enotify%E6%9C%BA%E5%88%B6%E8%AE%B2%E8%A7%A3/"/>
<id>https://blog.wthfeng.com/2017-12-09-wait与notify机制讲解/</id>
<published>2017-12-09T09:15:00.000Z</published>
<updated>2018-10-16T13:41:30.047Z</updated>
<content type="html"><![CDATA[<h1 id="Wait-Notify通知机制解析"><a href="#Wait-Notify通知机制解析" class="headerlink" title="Wait/Notify通知机制解析"></a>Wait/Notify通知机制解析</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>我们知道,java的wait/notify的通知机制可以用来实现线程间通信。wait表示线程的等待,调用该方法会导致线程阻塞,直至另一线程调用notify或notifyAll方法才可另其继续执行。经典的生产者、消费者模式即是使用wait/notify机制得以完成。在这篇文章中,我们将深入解析这一机制,了解其背后的原理。</p><h2 id="线程的状态"><a href="#线程的状态" class="headerlink" title="线程的状态"></a>线程的状态</h2><p>在了解wait/notify机制前,先熟悉一下java线程的几个生命周期。分别为初始(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)、终止(TERMINATED)等状态(位于java.lang.Thread.State枚举类中)。</p><p>以下是对这几个状态的简要说明,详细说明见该类注释。</p><table><thead><tr><th>状态名称</th><th>说明</th></tr></thead><tbody><tr><td>NEW</td><td>初始状态,线程被构建,但未调用start()方法</td></tr><tr><td>RUNNABLE</td><td>运行状态,调用start()方法后。在java线程中,将操作系统线程的就绪和运行统称运行状态</td></tr><tr><td>BLOCKED</td><td>阻塞状态,线程等待进入synchronized代码块或方法中,等待获取锁</td></tr><tr><td>WAITING</td><td>等待状态,线程可调用wait、join等操作使自己陷入等待状态,并等待其他线程做出特定操作(如notify或中断)</td></tr><tr><td>TIMED_WAITING</td><td>超时等待,线程调用sleep(timeout)、wait(timeout)等操作进入超时等待状态,超时后自行返回</td></tr><tr><td>TERMINATED</td><td>终止状态,线程运行结束</td></tr></tbody></table><p><img src="http://img.wthfeng.com/img/posts/java/thread/java-thread-status.png" alt></p><p>对于以上线程间的状态及转化关系,我们需要知道</p><ol><li>WAITING(等待状态)和TIMED_WAITING(超时等待)都会令线程进入等待状态,不同的是TIMED_WAITING会在超时后自行返回,而WAITING则需要等待至条件改变。</li><li>进入阻塞状态的唯一前提是在等待获取同步锁。java注释说的很明白,只有两种情况可以使线程进入阻塞状态:一是等待进入synchronized块或方法,另一个是在调用wait()方法后重新进入synchronized块或方法。下文会有详细解释。</li><li>Lock类对于锁的实现不会令线程进入阻塞状态,Lock底层调用LockSupport.park()方法,使线程进入的是等待状态。</li></ol><h2 id="wait-notify用例"><a href="#wait-notify用例" class="headerlink" title="wait/notify用例"></a>wait/notify用例</h2><p>让我们先通过一个示例解析</p><p>wait()方法可以使线程进入等待状态,而notify()可以使等待的状态唤醒。这样的同步机制十分适合生产者、消费者模式:消费者消费某个资源,而生产者生产该资源。当该资源缺失时,消费者调用wait()方法进行自我阻塞,等待生产者的生产;生产者生产完毕后调用notify/notifyAll()唤醒消费者进行消费。</p><p>以下是代码示例,其中flag标志表示资源的有无。</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><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Object obj = <span class="keyword">new</span> Object(); <span class="comment">//对象锁</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> flag = <span class="keyword">false</span>;</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 class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> Thread consume = <span class="keyword">new</span> Thread(<span class="keyword">new</span> Consume(), <span class="string">"Consume"</span>);</span><br><span class="line"> Thread produce = <span class="keyword">new</span> Thread(<span class="keyword">new</span> Produce(), <span class="string">"Produce"</span>);</span><br><span class="line"> consume.start();</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> produce.start();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> produce.join();</span><br><span class="line"> consume.join();</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 生产者线程</span></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Produce</span> <span class="keyword">implements</span> <span class="title">Runnable</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">run</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">synchronized</span> (obj) {</span><br><span class="line"> System.out.println(<span class="string">"进入生产者线程"</span>);</span><br><span class="line"> System.out.println(<span class="string">"生产"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.MILLISECONDS.sleep(<span class="number">2000</span>); <span class="comment">//模拟生产过程</span></span><br><span class="line"> flag = <span class="keyword">true</span>;</span><br><span class="line"> obj.notify(); <span class="comment">//通知消费者</span></span><br><span class="line"> TimeUnit.MILLISECONDS.sleep(<span class="number">1000</span>); <span class="comment">//模拟其他耗时操作</span></span><br><span class="line"> System.out.println(<span class="string">"退出生产者线程"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//消费者线程</span></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Consume</span> <span class="keyword">implements</span> <span class="title">Runnable</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">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (obj) {</span><br><span class="line"> System.out.println(<span class="string">"进入消费者线程"</span>);</span><br><span class="line"> System.out.println(<span class="string">"wait flag 1:"</span> + flag);</span><br><span class="line"> <span class="keyword">while</span> (!flag) { <span class="comment">//判断条件是否满足,若不满足则等待</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> System.out.println(<span class="string">"还没生产,进入等待"</span>);</span><br><span class="line"> obj.wait();</span><br><span class="line"> System.out.println(<span class="string">"结束等待"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"wait flag 2:"</span> + flag);</span><br><span class="line"> System.out.println(<span class="string">"消费"</span>);</span><br><span class="line"> System.out.println(<span class="string">"退出消费者线程"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出结果为:</p><blockquote><p>进入消费者线程 <br><br>wait flag 1:false <br><br>还没生产,进入等待 <br><br>进入生产者线程 <br><br>生产 <br><br>退出生产者线程 <br><br>结束等待 <br><br>wait flag 2:true <br><br>消费 <br><br>退出消费者线程 <br></p></blockquote><p>理解了输出结果的顺序,也就明白了wait/notify的基本用法。有以下几点需要知道:</p><ol><li>在示例中没有体现但很重要的是,<strong>wait/notify方法的调用必须处在该对象的锁(Monitor)中,也即,在调用这些方法时首先需要获得该对象的锁。</strong>否则会抛出IllegalMonitorStateException异常。</li><li>从输出结果来看,在生产者调用notify()后,消费者并没有立即被唤醒,而是等到生产者退出同步块后才唤醒执行。(这点其实也好理解,synchronized同步方法(块)同一时刻只允许一个线程在里面,生产者不退出,消费者也进不去)</li><li>注意,消费者被唤醒后是从wait()方法(被阻塞的地方)后面执行,而不是重新从同步块开始。</li></ol><h2 id="深入了解"><a href="#深入了解" class="headerlink" title="深入了解"></a>深入了解</h2><p>这一节我们探讨wait/notify与线程状态之间的关系。深入了解线程的生命周期。</p><p>由前面线程的状态转化图可知,当调用wait()方法后,线程会进入WAITING(等待状态),后续被notify()后,并没有立即被执行,而是进入等待获取锁的阻塞队列。</p><p><img src="http://img.wthfeng.com/img/posts/java/thread/java-wait-notify.png" alt></p><p>对于每个对象来说,都有自己的等待队列和阻塞队列。以前面的生产者、消费者为例,我们拿obj对象作为对象锁,配合图示。内部流程如下</p><ol><li>当线程A(消费者)调用wait()方法后,线程A让出锁,自己进入等待状态,同时加入锁对象的等待队列。</li><li>线程B(生产者)获取锁后,调用notify方法通知锁对象的等待队列,使得线程A从等待队列进入阻塞队列。</li><li>线程A进入阻塞队列后,直至线程B释放锁后,线程A竞争得到锁继续从wait()方法后执行。</li></ol><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li>《java并发编程的艺术》</li><li><a href="http://blog.csdn.net/ns_code/article/details/17225469" target="_blank" rel="noopener">【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明</a></li><li><a href="http://www.cnblogs.com/paddix/p/5381958.html" target="_blank" rel="noopener">Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)</a></li></ol>]]></content>
<summary type="html">
<h1 id="Wait-Notify通知机制解析"><a href="#Wait-Notify通知机制解析" class="headerlink" title="Wait/Notify通知机制解析"></a>Wait/Notify通知机制解析</h1><h2 id="前言"><
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="thread" scheme="https://blog.wthfeng.com/tags/thread/"/>
</entry>
<entry>
<title>ElasticSearch笔记二:5.x版本变化</title>
<link href="https://blog.wthfeng.com/2017-10-15-ElasticSearch%205.x%E5%8F%98%E5%8C%96/"/>
<id>https://blog.wthfeng.com/2017-10-15-ElasticSearch 5.x变化/</id>
<published>2017-10-15T07:22:00.000Z</published>
<updated>2018-10-16T13:41:30.046Z</updated>
<content type="html"><![CDATA[<blockquote><p>写在前面:去年写的有关Elastic的一些知识是基于2.x版本的,目前最新的版本是5.6(2017-10),一些重要的API与用法已经发生改变。这篇文章在之前系列的基础上,重点从API角度讲讲变化的部分。</p></blockquote><h2 id="一、映射的变化"><a href="#一、映射的变化" class="headerlink" title="一、映射的变化"></a>一、映射的变化</h2><h3 id="string类型变为为text-keyword"><a href="#string类型变为为text-keyword" class="headerlink" title="string类型变为为text/keyword"></a>string类型变为为text/keyword</h3><p>变化最大的是ES的基本类型string。目前string类型已标为废弃的,取而代之的变成了 text/keyword。text表示全文分析的string(即之前默认的string),keyword为不经分析的string(即not_analyzed的string)。</p><p>目前默认的字符串映射为</p><figure class="highlight json"><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><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"fields"</span>: {</span><br><span class="line"> <span class="attr">"keyword"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span>,</span><br><span class="line"> <span class="attr">"ignore_above"</span>: <span class="number">256</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>即表明默认的字符串类型为分词的,可进行全文搜索等;其子关键字字段是未分析的,可进行精确查找、聚合及排序等。</p><blockquote><p>如有个字段名为title为字符串类型。自动映射后,<code>title</code>可用于全文搜索,而<code>title.keyword</code>字段可进行聚合、排序等操作。</p></blockquote><h2 id="二、document-API-变化"><a href="#二、document-API-变化" class="headerlink" title="二、document API 变化"></a>二、document API 变化</h2><p>为演示方便,这里往ES添加一些数据。</p><blockquote><p>POST /cars/sale/_bulk<br><figure class="highlight json"><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="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">10000</span>, <span class="attr">"color"</span> : <span class="string">"red"</span>, <span class="attr">"make"</span> : <span class="string">"honda"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-10-28"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">20000</span>, <span class="attr">"color"</span> : <span class="string">"red"</span>, <span class="attr">"make"</span> : <span class="string">"honda"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-11-05"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">30000</span>, <span class="attr">"color"</span> : <span class="string">"green"</span>, <span class="attr">"make"</span> : <span class="string">"ford"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-05-18"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">15000</span>, <span class="attr">"color"</span> : <span class="string">"blue"</span>, <span class="attr">"make"</span> : <span class="string">"toyota"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-07-02"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">12000</span>, <span class="attr">"color"</span> : <span class="string">"green"</span>, <span class="attr">"make"</span> : <span class="string">"toyota"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-08-19"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">20000</span>, <span class="attr">"color"</span> : <span class="string">"red"</span>, <span class="attr">"make"</span> : <span class="string">"honda"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-11-05"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">80000</span>, <span class="attr">"color"</span> : <span class="string">"red"</span>, <span class="attr">"make"</span> : <span class="string">"bmw"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-01-01"</span> }</span><br><span class="line">{ <span class="attr">"index"</span>: {}}</span><br><span class="line">{ <span class="attr">"price"</span> : <span class="number">25000</span>, <span class="attr">"color"</span> : <span class="string">"blue"</span>, <span class="attr">"make"</span> : <span class="string">"ford"</span>, <span class="attr">"sold"</span> : <span class="string">"2014-02-12"</span> }</span><br></pre></td></tr></table></figure></p></blockquote><p>现在我们有了关于汽车销售的有关数据。</p><h3 id="update-by-query"><a href="#update-by-query" class="headerlink" title="_update_by_query"></a>_update_by_query</h3><p>5.x版本ES添加了<code>_update_by_query</code>API,可以根据查询到的结果进行更新。</p><p>目前我们有个需求是,所有福特(ford)汽车决定降价1000元。这正好可以使用<code>_update_by_query</code>完成。</p><blockquote><p>POST /cars/sale/_update_by_query<br><figure class="highlight json"><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><br><span class="line"><span class="attr">"query"</span>:{</span><br><span class="line"><span class="attr">"term"</span>:{</span><br><span class="line"><span class="attr">"make"</span>:<span class="string">"ford"</span></span><br><span class="line">}</span><br><span class="line">},</span><br><span class="line"><span class="attr">"script"</span>:{</span><br><span class="line"><span class="attr">"inline"</span>:<span class="string">"ctx._source.price=ctx._source.price-1000"</span>,</span><br><span class="line"><span class="attr">"lang"</span>:<span class="string">"painless"</span> ①</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p></blockquote><p>① <code>painless</code>为ES最新默认的脚本语言,相关资料可参考<a href="https://www.elastic.co/guide/en/elasticsearch/painless/5.6/index.html" target="_blank" rel="noopener">painless脚本语言</a>。</p><h3 id="delete-by-query"><a href="#delete-by-query" class="headerlink" title="_delete_by_query"></a>_delete_by_query</h3><p><code>_delete_by_query</code>与上面提到的<code>_update_by_query</code>类似。它是根据查询删除某些文档。继续上面的示例。</p><p>删除所有宝马(bmw)车系。</p><blockquote><p>POST /cars/sale/_delete_by_query</p></blockquote><figure class="highlight json"><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><br><span class="line"><span class="attr">"query"</span>:{</span><br><span class="line"><span class="attr">"term"</span>:{</span><br><span class="line"><span class="attr">"make"</span>:<span class="string">"bmw"</span></span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="reindex"><a href="#reindex" class="headerlink" title="reindex"></a>reindex</h3><p><code>_reindex</code> 功能为将文档从一个索引复制到另一个索引。利用它可以实现数据索引级别的无痛迁移,其中重要的是,在迁移时我们可以改变目标索引的某些字段类型。即平滑地升级我们的索引类型。</p><p>在我们的数据中,<code>color</code>和<code>make</code>都是text类型,意味着可用于全文检索,可在实际应用中,我们总是需要精确匹配他们,没必要分词,而ES的字段类型一旦确定又无法修改。</p><p>之前的做法是重建一个索引,然后利用<code>_bulk</code> 把数据批量导入新索引中。现在利用<code>_reindex</code>,可以实现一步导入。</p><p>首先需要重建一个索引,设置为需要的类型。</p><blockquote><p>PUT /cars_new</p></blockquote><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"mappings"</span>: {</span><br><span class="line"> <span class="attr">"sale"</span>: {</span><br><span class="line"> <span class="attr">"properties"</span>: {</span><br><span class="line"> <span class="attr">"color"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"make"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"price"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"integer"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"sold"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"date"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>新索引<code>cars_new</code>的<code>color</code>和<code>make</code>为keyword类型,<code>price</code>修改为了<code>integer</code>类型(之前为<code>long</code>)。</p><p>使用<code>_reindex</code>迁移索引。</p><blockquote><p>POST /_reindex<br><figure class="highlight json"><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><br><span class="line"><span class="attr">"source"</span>:{</span><br><span class="line"><span class="attr">"index"</span>:<span class="string">"cars"</span></span><br><span class="line">},</span><br><span class="line"><span class="attr">"dest"</span>:{</span><br><span class="line"><span class="attr">"index"</span>:<span class="string">"cars_new"</span></span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p></blockquote><p>查看新索引<code>cars_new</code>确实创建成功,查看cars_new映射。</p><blockquote><p>GET /cars_new/_mappings/<br><figure class="highlight json"><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><br><span class="line"> <span class="attr">"cars_new"</span>: {</span><br><span class="line"> <span class="attr">"mappings"</span>: {</span><br><span class="line"> <span class="attr">"sale"</span>: {</span><br><span class="line"> <span class="attr">"properties"</span>: {</span><br><span class="line"> <span class="attr">"color"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"make"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"price"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"integer"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"sold"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"date"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p></blockquote><h3 id="关于过滤-filtered"><a href="#关于过滤-filtered" class="headerlink" title="关于过滤 filtered"></a>关于过滤 filtered</h3><p>目前过滤的API已经不支持<code>filtered</code>的语法了。实现过滤使用<code>constant_score</code>或在<code>bool</code>子句下<code>filter</code>实现。这两者都不会计算文档得分,使查询更高效。</p><p>如只获取绿色的汽车</p><blockquote><p>POST /cars_new/sale/_search</p></blockquote><figure class="highlight json"><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><br><span class="line"><span class="attr">"query"</span>:{</span><br><span class="line"><span class="attr">"constant_score"</span>:{</span><br><span class="line"><span class="attr">"filter"</span>:{</span><br><span class="line"><span class="attr">"term"</span>:{</span><br><span class="line"><span class="attr">"color"</span>:<span class="string">"green"</span></span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight json"><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><br><span class="line"><span class="attr">"query"</span>:{</span><br><span class="line"><span class="attr">"bool"</span>:{</span><br><span class="line"><span class="attr">"filter"</span>:{</span><br><span class="line"><span class="attr">"term"</span>:{</span><br><span class="line"><span class="attr">"color"</span>:<span class="string">"green"</span></span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>一般<code>bool</code> 下的过滤往往结合其他查询进行,若只有一个过滤,使用<code>constant_score</code>即可,它会将每个文档的评分都置为1。</p><h2 id="三、其他变化"><a href="#三、其他变化" class="headerlink" title="三、其他变化"></a>三、其他变化</h2><ol><li>5.x中取消了<code>search_type = count</code>语法,使用 <code>size:0</code>的方式来代替。</li><li>添加了<code>profile</code>API,可以获取具体在查询时过滤,使可以有目的性的优化。</li><li>其他变化可参考<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes-5.0.html" target="_blank" rel="noopener">5.0ES重大变化</a></li></ol><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol><li><a href="http://www.cnblogs.com/zlslch/p/6619089.html" target="_blank" rel="noopener">Elasticsearch之elasticsearch5.x 新特性</a></li><li><a href="http://cwiki.apachecn.org/pages/viewpage.action?pageId=4260364" target="_blank" rel="noopener">Elasticsearch 5.4 中文文档</a></li></ol>]]></content>
<summary type="html">
<blockquote>
<p>写在前面:去年写的有关Elastic的一些知识是基于2.x版本的,目前最新的版本是5.6(2017-10),一些重要的API与用法已经发生改变。这篇文章在之前系列的基础上,重点从API角度讲讲变化的部分。</p>
</blockquote>
<h2
</summary>
<category term="elasticsearch" scheme="https://blog.wthfeng.com/categories/elasticsearch/"/>
<category term="elasticsearch" scheme="https://blog.wthfeng.com/tags/elasticsearch/"/>
<category term="elastic" scheme="https://blog.wthfeng.com/tags/elastic/"/>
</entry>
<entry>
<title>快速排序(QuickSort)实践</title>
<link href="https://blog.wthfeng.com/2017-10-01-%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%9E%E8%B7%B5/"/>
<id>https://blog.wthfeng.com/2017-10-01-快速排序实践/</id>
<published>2017-10-01T07:30:00.000Z</published>
<updated>2018-10-28T06:12:53.680Z</updated>
<content type="html"><![CDATA[<h2 id="算法简介"><a href="#算法简介" class="headerlink" title="算法简介"></a>算法简介</h2><p>快速排序(Quicksort)是对冒泡排序的一种改进算法。由C. A. R. Hoare在1960年提出。该算法使用广泛、效率很高,是最重要的排序算法之一。</p><p>该算法的实现基本可分为以下几步:</p><blockquote><ol><li>在数组中选一个基准数(通常为数组第一个)。</li><li>将数组中小于基准数的数据移到基准数左边,大于基准数的移到右边</li><li>对于基准数左、右两边的数组,不断重复以上两个过程,直到每个子集只有一个元素,即为全部有序。</li></ol></blockquote><p>示例有一数组为<code>4 1 8 3 7 5</code>,依上面的思路排序过程为</p><ol><li><p>选第一个基准元素</p><blockquote><p> <strong>4</strong> 1 3 6 7 5</p></blockquote></li><li><p>以基准元素为中心将数组分成两个子集</p><blockquote><p>3 1 <strong>4</strong> 6 7 5</p></blockquote></li><li><p>将两个子集重复以上操作,直到全部有序</p><blockquote><p>1 3</p><p>5 6 7</p></blockquote></li><li><p>得到有序的集合</p><blockquote><p>1 3 4 5 6 7 </p></blockquote></li></ol><p>以上是快速排序的基本思路,这里比较重要的是第2点,如何将一个数组以基准数为中心分为两部分呢?</p><p>快排是这样解决的,假设做正序排序:</p><blockquote><p>在数组的头部和尾部分别设置一个<code>哨兵</code>,同时向对方走去。尾部的哨兵如发现有比基准数小的数,停下。头部的哨兵如发现有比基准数大的数,停下。交换两个数。再重新走重复前面的交换过程。直到两个哨兵相遇,交换基准数和尾哨兵。</p></blockquote><p>有一数组为<code>6 1 2 7 9 3 4 5 10 8</code>,带着这样做为什么可以的态度来看一下演示。</p><ol><li>6为基准数,设i,j为两哨兵,目前指向首尾两个数(第一个数6即是基准数,又是哨兵i)。<blockquote><p> <strong>6</strong> 1 2 7 9 3 4 5 10 <strong>8</strong></p></blockquote></li><li><p>两哨兵分别走向对方,直到遇到交换条件,并做交换。</p><blockquote><p> 6 1 2 <strong>7</strong> 9 3 4 <strong>5</strong> 10 8</p><p> 6 1 2 <strong>5</strong> 9 3 4 <strong>7</strong> 10 8</p></blockquote></li><li><p>此时来观察交换后的队列,除去基准数,是不是哨兵走过的位置都已部分有序了呢? 左边<code>1 2 5</code>都比基准数小,右边<code>7 10 8</code>都比基准数大。</p><blockquote><p>1 2 <strong>5</strong> 9 3 4 <strong>7</strong> 10 8</p></blockquote></li><li><p>继续走直到相遇,基准数复位。</p><blockquote><p>6 1 2 5 <strong>9</strong> 3 <strong>4</strong> 7 10 8</p><p>6 1 2 5 <strong>4</strong> 3 <strong>9</strong> 7 10 8</p><p>6 1 2 5 4 <strong>3</strong> 9 7 10 8</p><p><strong>3</strong> 1 2 5 4 <strong>6</strong> 9 7 10 8</p></blockquote></li></ol><p>这样就完美地将一个数组以一个基准数为中心分为两个子集。然后重复这个过程即可实现快速排序。</p><p>有一点需特别注意:<strong>若以第一个元素为基准数(就如上面的示例),在哨兵互走过程需右边的哨兵先走。</strong> 原因很好理解,看上面过程解析就会明白:哨兵互走交换的过程就是不断排序的过程。若右边的哨兵先走,不管走多少次,最后相遇时的那个数是小于基准数的。这时与基准数交换,正好分为两个序列。可若是左边的先走,相遇在大于基准数上就不好办了。</p><h2 id="算法实践"><a href="#算法实践" class="headerlink" title="算法实践"></a>算法实践</h2><p>以java演示一遍</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">quickSort</span><span class="params">(<span class="keyword">int</span>[] arr, <span class="keyword">int</span> low, <span class="keyword">int</span> high)</span> </span>{</span><br><span class="line"> <span class="comment">// low,high 为每次处理数组时的首、尾元素索引</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//当low==high是表示该序列只有一个元素,不必排序了</span></span><br><span class="line"> <span class="keyword">if</span> (low >= high) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 选出哨兵元素和基准元素。这里左边的哨兵元素为第1个元素(也为基准元素)</span></span><br><span class="line"> <span class="keyword">int</span> i = low, j = high, base = arr[low];</span><br><span class="line"> <span class="keyword">while</span> (i < j) {</span><br><span class="line"> <span class="comment">//右边哨兵从后向前找</span></span><br><span class="line"> <span class="keyword">while</span> (arr[j] >= base && i < j) {</span><br><span class="line"> j--;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//左边哨兵从前向后找</span></span><br><span class="line"> <span class="keyword">while</span> (arr[i] <= base && i < j) {</span><br><span class="line"> i++;</span><br><span class="line"> }</span><br><span class="line"> swap(arr,i,j); <span class="comment">//交换元素</span></span><br><span class="line"> }</span><br><span class="line"> swap(arr,low,j); <span class="comment">//基准元素与右哨兵交换</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//递归调用,排序左子集合和右子集合</span></span><br><span class="line"> quickSort(arr,low,j-<span class="number">1</span>); </span><br><span class="line"> quickSort(arr,j+<span class="number">1</span>,high);</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">private</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span>[] arr, <span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> tmp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = tmp;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><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><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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">quickSortNotR</span><span class="params">(<span class="keyword">int</span>[] arr)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> low = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> high = arr.length - <span class="number">1</span>;</span><br><span class="line"> Stack<Integer> stack = <span class="keyword">new</span> Stack<>();</span><br><span class="line"> stack.push(low);</span><br><span class="line"> stack.push(high);</span><br><span class="line"> <span class="keyword">while</span> (!stack.empty()) {</span><br><span class="line"> <span class="keyword">int</span> j = stack.pop();</span><br><span class="line"> <span class="keyword">int</span> i = stack.pop();</span><br><span class="line"> <span class="keyword">int</span> k = partSort(arr, i, j);</span><br><span class="line"> <span class="keyword">if</span> (i < k - <span class="number">1</span>) {</span><br><span class="line"> stack.push(i);</span><br><span class="line"> stack.push(k - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (j > k + <span class="number">1</span>) {</span><br><span class="line"> stack.push(k + <span class="number">1</span>);</span><br><span class="line"> stack.push(high);</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">int</span> <span class="title">partSort</span><span class="params">(<span class="keyword">int</span>[] arr, <span class="keyword">int</span> low, <span class="keyword">int</span> high)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> base = arr[low];</span><br><span class="line"> <span class="keyword">int</span> i = low;</span><br><span class="line"> <span class="keyword">int</span> j = high;</span><br><span class="line"> <span class="keyword">while</span> (i < j) {</span><br><span class="line"> <span class="keyword">while</span> (arr[j] >= base && i < j) {</span><br><span class="line"> j--;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (arr[i] <= base && i < j) {</span><br><span class="line"> i++;</span><br><span class="line"> }</span><br><span class="line"> swap(arr, i, j);</span><br><span class="line"> }</span><br><span class="line"> swap(arr, low, j);</span><br><span class="line"> <span class="keyword">return</span> j;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里将一次排序过程抽象成一个方法<code>partSort()</code>。主要实现<code>quickSortNotR()</code>思路依旧和递归类似。只是将每次分成的小数组存入栈中(与递归压入方法栈类似效果),添加了一个辅助存储空间,但避免了方法栈溢出等问题。</p><h2 id="ForkJoinTask与快速排序"><a href="#ForkJoinTask与快速排序" class="headerlink" title="ForkJoinTask与快速排序"></a>ForkJoinTask与快速排序</h2><p>Fork/Join框架是Java7提供了的一个用于并行执行任务的框架。它可以充分利用多核CPU的优势,将一个大任务切割成多个足够小的任务并行执行(fork),等所有子任务执行完毕后合并结果(join),流程图类似下面:</p><p><img src="http://img.wthfeng.com/img/wthfeng/sort/20171001141112983.png" alt="这里写图片描述"></p><p> 图片来自文章<a href="http://www.infoq.com/cn/articles/fork-join-introduction" target="_blank" rel="noopener">聊聊并发(八)——Fork/Join框架介绍</a>。</p><p>这样来看,fork/join的处理模式与quickSort算法<br>倒很相似,都是不断分割任务以进行处理。而事实上,两者都是分治算法的实践者。我们可以将上述排序改成并发版的快速排序。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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="class"><span class="keyword">class</span> <span class="title">SortTask</span> <span class="keyword">extends</span> <span class="title">RecursiveAction</span></span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] arr;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> low;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> high;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">SortTask</span><span class="params">(<span class="keyword">int</span>[] arr,<span class="keyword">int</span> low,<span class="keyword">int</span> high)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.arr = arr;</span><br><span class="line"> <span class="keyword">this</span>.high = high;</span><br><span class="line"> <span class="keyword">this</span>.low = low;</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">protected</span> <span class="keyword">void</span> <span class="title">compute</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(low<high){</span><br><span class="line"> <span class="keyword">int</span> i=low,j=high,base = arr[low];</span><br><span class="line"> <span class="keyword">while</span> (i<j){</span><br><span class="line"> <span class="keyword">while</span> (arr[j]>=base && i<j){</span><br><span class="line"> j--;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (arr[i] <= base && i < j) {</span><br><span class="line"> i++;</span><br><span class="line"> }</span><br><span class="line"> swap(arr,i,j);</span><br><span class="line"> }</span><br><span class="line"> swap(arr,low,j);</span><br><span class="line"> SortTask leftTask =<span class="keyword">new</span> SortTask(arr,low,j-<span class="number">1</span>);</span><br><span class="line"> SortTask rightTask =<span class="keyword">new</span> SortTask(arr,j+<span class="number">1</span>,high);</span><br><span class="line"> <span class="comment">//分割成子任务并执行,join()方法会再次调用compute()方法。</span></span><br><span class="line"> leftTask.fork();</span><br><span class="line"> rightTask.fork();</span><br><span class="line"> leftTask.join();</span><br><span class="line"> leftTask.join();</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">private</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span>[] arr, <span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> tmp = arr[i];</span><br><span class="line"> arr[i] = arr[j];</span><br><span class="line"> arr[j] = tmp;</span><br><span class="line"> }</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><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="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span><span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> ForkJoinPool forkJoinPool = <span class="keyword">new</span> ForkJoinPool();</span><br><span class="line"> <span class="keyword">int</span>[] arr = {<span class="number">4</span>, <span class="number">1</span>, <span class="number">8</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">9</span>, <span class="number">3</span>, <span class="number">2</span>};</span><br><span class="line"> SortTask sortTask = <span class="keyword">new</span> SortTask(arr,<span class="number">0</span>,arr.length-<span class="number">1</span>);</span><br><span class="line"> ForkJoinTask<Void> task = forkJoinPool.submit(sortTask);</span><br><span class="line"> task.get();</span><br><span class="line"> Arrays.stream(arr).forEach(e->System.out.print(e+<span class="string">" "</span>));</span><br><span class="line"> <span class="comment">// 1 2 3 4 6 7 8 9</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="http://developer.51cto.com/art/201403/430986.htm" target="_blank" rel="noopener">坐在马桶上看算法:快速排序</a></li><li><a href="https://blog.csdn.net/qq_36528114/article/details/78667034" target="_blank" rel="noopener">快速排序(三种算法实现和非递归实现)</a></li></ol>]]></content>
<summary type="html">
<h2 id="算法简介"><a href="#算法简介" class="headerlink" title="算法简介"></a>算法简介</h2><p>快速排序(Quicksort)是对冒泡排序的一种改进算法。由C. A. R. Hoare在1960年提出。该算法使用广泛、效
</summary>
<category term="算法" scheme="https://blog.wthfeng.com/categories/%E7%AE%97%E6%B3%95/"/>
<category term="排序算法" scheme="https://blog.wthfeng.com/tags/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/"/>
<category term="算法" scheme="https://blog.wthfeng.com/tags/%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>HttpUrlConnection类体系解析</title>
<link href="https://blog.wthfeng.com/2017-10-01-HttpUrlConnection%E8%A7%A3%E6%9E%90/"/>
<id>https://blog.wthfeng.com/2017-10-01-HttpUrlConnection解析/</id>
<published>2017-10-01T07:00:00.000Z</published>
<updated>2018-10-16T13:41:30.045Z</updated>
<content type="html"><![CDATA[<h2 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h2><h3 id="关于HTTP协议"><a href="#关于HTTP协议" class="headerlink" title="关于HTTP协议"></a>关于HTTP协议</h3><p>HTTP 协议是目前 Internet 上使用得最多、最重要的协议。该协议为典型的请求-响应模型。客户端建立连接并发送请求,服务端接受并处理请求,再发送应答,再由客户端接受并处理应答。浏览器是最常见的一种客户端,它将用户的交互行为作为http请求发送,并接受服务端的应答,再将应答内容展示,一般应答都是html类型的超文本。</p><p>在某些情况下,我们会使用java程序来模拟浏览器发送请求。因此,在 JDK 的 java.net 包中已内置了访问 HTTP 协议的类:<strong>HttpURLConnection</strong>。</p><h3 id="关于继承关系"><a href="#关于继承关系" class="headerlink" title="关于继承关系"></a>关于继承关系</h3><p><code>HttpUrlConnection</code>类继承自<code>UrlConnection</code>。<code>UrlConnection</code>是一个抽象类,表示URL指向资源的连接。其子类包含诸如<code>HttpUrlConnection</code>、<code>FtpUrlConnection</code>、<code>FileUrlConnection</code>等各种协议的连接类。</p><blockquote><p>这些协议的连接类具体实现大都在<code>sun.net.www.protocol.http</code>包内,不是公开的接口,我们可不必关注。只需了解其继承关系即可。</p></blockquote><p><img src="http://img.blog.csdn.net/20170920100608978?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><h3 id="关于通信机制"><a href="#关于通信机制" class="headerlink" title="关于通信机制"></a>关于通信机制</h3><p><code>URLConnection</code>类本身依赖于Socket类实现网络连接。socket又称做套接字,是应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层(这里就是我们的Http连接)调用。</p><p>其所处的位置如下图所示</p><p><img src="http://img.blog.csdn.net/20170925085149368?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p>当我们进行Http通信时,每个请求连接最终都会绑定到一个具体的socket上,利用socket与底下的传输层等进行通信。具体通信机制可参考相关书籍。</p><h2 id="一个请求示例"><a href="#一个请求示例" class="headerlink" title="一个请求示例"></a>一个请求示例</h2><p>下面是用<code>HttpURLConnection</code>获取百度首页的示例。</p><p>具体步骤如下:</p><ol><li>根据连接地址创建<code>URL</code>实例。</li><li>调用<code>URL::openConnection()</code> 方法打开连接,将连接赋给<code>HttpURLConnection</code>对象。</li><li>操作连接。</li><li>关闭连接。</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><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="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> <span class="keyword">throws</span> IOException</span>{</span><br><span class="line"> URL url = <span class="keyword">new</span> URL(<span class="string">"http://www.baidu.com"</span>); <span class="comment">//构建一个URL资源对象</span></span><br><span class="line"> HttpURLConnection connection = (HttpURLConnection) url.openConnection();<span class="comment">//打开连接</span></span><br><span class="line"> connection.setRequestMethod(<span class="string">"GET"</span>); <span class="comment">//设置请求方法</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 建立连接并获取资源(指向百度首页的html内容)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> BufferedReader in = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(connection.getInputStream()));</span><br><span class="line"> StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> String line ;</span><br><span class="line"> <span class="keyword">while</span> ((line=in.readLine())!=<span class="keyword">null</span>){</span><br><span class="line"> sb.append(line);</span><br><span class="line"> }</span><br><span class="line"> System.out.println(sb.toString());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面我们就根据这个简单的示例来解析<code>HttpURLConnection</code>,看看它是如何发送请求并接受响应的。</p><p>根据示例,我们需要知道</p><ol><li><code>URL</code>类及<code>URL::openConnection</code>方法</li><li><code>HttpURLConnection</code> 建立连接的方法以及其他重要方法。</li></ol><h2 id="URL"><a href="#URL" class="headerlink" title="URL"></a>URL</h2><h3 id="URL格式简介"><a href="#URL格式简介" class="headerlink" title="URL格式简介"></a>URL格式简介</h3><p>首先看看有关<code>URL</code>(统一资源定位符)的内容。</p><p>URL表示的也就是我们通常说的<strong>网页地址</strong>。表示互联网上的资源,如网页或FTP地址等。</p><p>URL可分为以下几个部分</p><blockquote><p>protocol://host:port/path?query#fragment</p></blockquote><p>以Http协议为例,一个实例如下:</p><blockquote><p><a href="http://www.runoob.com/index.html?language=cn#j2se" target="_blank" rel="noopener">http://www.runoob.com/index.html?language=cn#j2se</a></p></blockquote><p>各部分含义:</p><ul><li>protocol : 协议名,如http、https、ftp等,示例中为http。</li><li>host : 主机地址,示例中为 <a href="http://www.runoob.com" target="_blank" rel="noopener">www.runoob.com</a></li><li>port : 端口号,没有标明则为默认端口号。如http协议默认的为80,ftp的为21。示例为http协议,其端口号为80</li><li>path : 路径,由<code>/</code>隔开的字符串,表示主机上的文件或目录,示例为<code>index.html</code></li><li>? :分割符,分割主机地址和查询参数</li><li>query : 查询参数,多个用<code>&</code>分割,示例中为<code>language=cn</code>。</li><li>fragment : 定位片段,定位到网页地址的某个id,示例中为<code>j2se</code></li></ul><h3 id="构建URL对象"><a href="#构建URL对象" class="headerlink" title="构建URL对象"></a>构建URL对象</h3><p>URL有多个构造函数,具体实现在<code>URL(URL, String,handler)</code>。构造函数的目的在:</p><ol><li>解析传来的url字符串,解析出的<code>protocol</code>、<code>host</code>等值并赋值给相应的类字段。</li><li>根据<code>protocol</code>字段得到<code>urlStreamHandler</code>实例。</li></ol><p>第1条很好理解,至于第二条的<code>urlStreamHandler</code>对象,则是具体处理连接请求的<code>handler</code>对象。在设计上,每一个协议(protocol)对应一个<code>handler</code>。</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="function"><span class="keyword">public</span> <span class="title">URL</span><span class="params">(URL context, String spec, URLStreamHandler handler)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> MalformedURLException</span>{</span><br><span class="line"> <span class="comment">// 为简洁见,已去掉解析url过程</span></span><br><span class="line"> <span class="comment">// 根据protocol得到urlStreamHandler实例</span></span><br><span class="line"> <span class="keyword">if</span> (handler == <span class="keyword">null</span> &&</span><br><span class="line"> (handler = getURLStreamHandler(protocol)) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> MalformedURLException(<span class="string">"unknown protocol: "</span>+protocol);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.handler = handler;</span><br><span class="line"> </span><br><span class="line"> handler.parseURL(<span class="keyword">this</span>, spec, start, limit);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span>(MalformedURLException e) {</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> } <span class="keyword">catch</span>(Exception e) {</span><br><span class="line"> <span class="comment">// 异常处理</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>从<code>getURLStreamHandler</code>中得到的handler,具体看看<code>getURLStreamHandler</code></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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">static</span> URLStreamHandler <span class="title">getURLStreamHandler</span><span class="params">(String protocol)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里应该是做了一个缓存Map,将已解析过的协议名(String,key值)和该协议的处理类(URLStreamHandler,value值)放于Map中。</span></span><br><span class="line"> URLStreamHandler handler = handlers.get(protocol);</span><br><span class="line"> <span class="keyword">if</span> (handler == <span class="keyword">null</span>) {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> checkedWithFactory = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若factory不为空,从factory获取</span></span><br><span class="line"> <span class="keyword">if</span> (factory != <span class="keyword">null</span>) {</span><br><span class="line"> handler = factory.createURLStreamHandler(protocol);</span><br><span class="line"> checkedWithFactory = <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">if</span> (handler == <span class="keyword">null</span>) {</span><br><span class="line"> String packagePrefixList = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> packagePrefixList</span><br><span class="line"> = java.security.AccessController.doPrivileged(</span><br><span class="line"> <span class="keyword">new</span> sun.security.action.GetPropertyAction(</span><br><span class="line"> protocolPathProp,<span class="string">""</span>));</span><br><span class="line"> <span class="keyword">if</span> (packagePrefixList != <span class="string">""</span>) {</span><br><span class="line"> packagePrefixList += <span class="string">"|"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// REMIND: decide whether to allow the "null" class prefix</span></span><br><span class="line"> <span class="comment">// or not.</span></span><br><span class="line"> packagePrefixList += <span class="string">"sun.net.www.protocol"</span>;</span><br><span class="line"></span><br><span class="line"> StringTokenizer packagePrefixIter =</span><br><span class="line"> <span class="keyword">new</span> StringTokenizer(packagePrefixList, <span class="string">"|"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (handler == <span class="keyword">null</span> &&</span><br><span class="line"> packagePrefixIter.hasMoreTokens()) {</span><br><span class="line"></span><br><span class="line"> String packagePrefix =</span><br><span class="line"> packagePrefixIter.nextToken().trim();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> String clsName = packagePrefix + <span class="string">"."</span> + protocol +</span><br><span class="line"> <span class="string">".Handler"</span>;</span><br><span class="line"> Class<?> cls = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> cls = Class.forName(clsName);</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> ClassLoader cl = ClassLoader.getSystemClassLoader();</span><br><span class="line"> <span class="keyword">if</span> (cl != <span class="keyword">null</span>) {</span><br><span class="line"> cls = cl.loadClass(clsName);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (cls != <span class="keyword">null</span>) {</span><br><span class="line"> handler =</span><br><span class="line"> (URLStreamHandler)cls.newInstance();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">// any number of exceptions can get thrown here</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="keyword">synchronized</span> (streamHandlerLock) {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略对多线程情况判断</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//将handler加到映射表中</span></span><br><span class="line"> <span class="keyword">if</span> (handler != <span class="keyword">null</span>) {</span><br><span class="line"> handlers.put(protocol, handler);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> handler;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从代码中可知,处理协议连接的handler前缀是<code>sun.net.www.protocol</code>,这样可根据协议名获取具体处理的handler。如处理http的为<code>sun.net.www.protocol.http.HttpURLConnection</code>,负责有关http相关的连接处理。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>其他具体的包括打开连接、发送请求、接受响应等都在<code>sun.net.www.protocol.http.HttpURLConnection</code>类内具体实现。这里就不展示了。如此,有关<code>HttpURLConnection</code>相关的类结构也就结束了。</p>]]></content>
<summary type="html">
<h2 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h2><h3 id="关于HTTP协议"><a href="#关于HTTP协议" class="headerlink" title="关
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="http" scheme="https://blog.wthfeng.com/tags/http/"/>
<category term="网络" scheme="https://blog.wthfeng.com/tags/%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>多级选择组件解决实践</title>
<link href="https://blog.wthfeng.com/2017-09-14-%E5%A4%9A%E7%BA%A7%E9%80%89%E6%8B%A9%E7%BB%84%E4%BB%B6%E8%A7%A3%E5%86%B3%E5%AE%9E%E8%B7%B5/"/>
<id>https://blog.wthfeng.com/2017-09-14-多级选择组件解决实践/</id>
<published>2017-09-14T13:32:00.000Z</published>
<updated>2018-10-16T13:41:30.044Z</updated>
<content type="html"><![CDATA[<h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>这里的多级选择组件问题,指的是存在一个多级的选择组件,当点击某个节点时,该节点及其下的所有节点都要选中,若该节点并列的所有兄弟节点都已选中,则其父节点也要勾选,依此到最顶端节点。反选也类似逻辑。</p><p>这个问题也符合平日的认知习惯。如下图所示:</p><p><img src="http://img.blog.csdn.net/20170914185555521?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><blockquote><p>如点<code>新华区</code>,则其下所有街道都要选中,再点击<code>桥西区</code>,<code>桥西区</code>下的街道要选中,同时<code>石家庄市</code>这个节点要选中。若再点击<code>廊坊市</code>这个节点,则整个<code>河北省</code>都应该选中。</p></blockquote><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>有一个问题是,我们并不知道这个选择组件有多少级。如上示例为3级,但实际中是不确定的。</p><p>从问题来看,设置目标节点(点击的节点)及其下的节点的选择状态比较容易办到,如使用<code>jquery</code>查询其下的所有子节点即可。比较困难的是,怎样设置父节点的状态(是否选中)?</p><p>可以想象的是,查询兄弟节点的状态,若兄弟节点有没有选中的,任务就结束了(直接返回),若兄弟节点都选中,说明其父节点也应该需要选中,好,设置父节点选中,这样还要判断父节点的兄弟节点…….这样一直循环到顶端节点。这就是所谓的递归的套路。那有没有简单的方法呢?</p><h2 id="利用冒泡的解决方法"><a href="#利用冒泡的解决方法" class="headerlink" title="利用冒泡的解决方法"></a>利用冒泡的解决方法</h2><p>有一个解决的方法。原理和上面提到的类似,不过向上递归的过程我们用冒泡实现。</p><p>这种方法有一定的限制条件,首先我们把每个节点都添加一个相同的样式(有同一个<code>class</code>),父节点需要包含子节点(即点击子节点时事件需能冒泡上传到父节点)。好了,这样我们只需处理一层父节点即可。</p><blockquote><p>处理父节点时,若其兄弟节点有未选中的,阻止冒泡事件即可。</p></blockquote><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>如上面的示例,解决方法实现如下:</p><p>页面<br><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>多级选择组件<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></span><br><span class="line"><span class="undefined"> .one-level {</span></span><br><span class="line"><span class="undefined"> margin-left: 30px;</span></span><br><span class="line"><span class="undefined"> }</span></span><br><span class="line"><span class="undefined"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcss.com/jquery/3.2.1/jquery.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">value</span>=<span class="string">""</span> /></span>河北省<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">value</span>=<span class="string">""</span> /></span>石家庄市<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>新华区<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>A街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>B街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>C街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>桥西区<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>1街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>2街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>3街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>廊坊市<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>广阳区<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>A街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>B街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>C街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>开发区<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>A街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>B街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>C街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>安次区<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>A街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>B街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"node one-level"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">type</span>=<span class="string">""</span>></span>选择<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> /></span>C街道<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure></p><p>js核心实现<br><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line">$(<span class="string">'.node'</span>).on(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 判断事件源,只响应按钮单击事件</span></span><br><span class="line"> <span class="keyword">var</span> tagName = event.target.tagName;</span><br><span class="line"> <span class="keyword">if</span>(tagName!==<span class="string">'BUTTON'</span>){</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">var</span> name = $(<span class="keyword">this</span>).children(<span class="string">'label'</span>).text();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'name:'</span> + name);</span><br><span class="line"> <span class="keyword">var</span> self = $(<span class="keyword">this</span>).children(<span class="string">'label'</span>).find(<span class="string">'input'</span>)[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">var</span> children = $(<span class="keyword">this</span>).children(<span class="string">'.node'</span>).find(<span class="string">'input'</span>);</span><br><span class="line"> <span class="keyword">if</span> (self.checked) { <span class="comment">//已选择,取消选择</span></span><br><span class="line"> self.checked = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">var</span> isAll = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < children.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (!children[i].checked) {</span><br><span class="line"> isAll = <span class="literal">false</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 class="keyword">if</span> (isAll) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < children.length; i++) {</span><br><span class="line"> children[i].checked = <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">//未选择,勾选</span></span><br><span class="line"> self.checked = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < children.length; i++) {</span><br><span class="line"> children[i].checked = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">var</span> sibs = $(<span class="keyword">this</span>).siblings().children(<span class="string">'label'</span>).find(<span class="string">'input'</span>);</span><br><span class="line"> <span class="keyword">var</span> isfull = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">//查看兄弟节点,看其是否有未选中的</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < sibs.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (!sibs[i].checked) {</span><br><span class="line"> isfull = <span class="literal">false</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 class="keyword">if</span> (!isfull) {</span><br><span class="line"> event.stopPropagation(); <span class="comment">// 阻止事件冒泡</span></span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><p>说明:</p><ol><li>这样做会使单击事件的区域变为块状(区域为div),所以需要在开头判断事件源。</li><li>对于取消选中,需要判断:若是子节点有未选中的,则不需要管子节点,只需把自身取消选中即可。</li><li>本文未对中间态(选中、未选中、半选中)做处理,有兴趣的你可以试试</li></ol>]]></content>
<summary type="html">
<h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>这里的多级选择组件问题,指的是存在一个多级的选择组件,当点击某个节点时,该节点及其下的所有节点都要选中,若该节点并列的所有兄
</summary>
<category term="javascript" scheme="https://blog.wthfeng.com/categories/javascript/"/>
<category term="javascript" scheme="https://blog.wthfeng.com/tags/javascript/"/>
<category term="解决方法" scheme="https://blog.wthfeng.com/tags/%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/"/>
</entry>
<entry>
<title>java代理模式与JDK代理</title>
<link href="https://blog.wthfeng.com/2017-05-24-java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/"/>
<id>https://blog.wthfeng.com/2017-05-24-java代理模式/</id>
<published>2017-05-24T04:58:00.000Z</published>
<updated>2018-10-16T13:41:30.044Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>代理模式是很常用的设计模式之一,一般可分为静态代理和动态代理两类。java利用反射也对动态代理提供了支持。今天我们就来学习学习。</p><h3 id="1-定义"><a href="#1-定义" class="headerlink" title="1. 定义"></a>1. 定义</h3><blockquote><p>给某一个对象提供一个代理,并由代理对象控制对原对象的引用,称为代理模式。它是一种对象结构型模式。</p></blockquote><p>即可理解为,某个对象实例(记为<code>Subject</code>)不方便直接引用,我们就提供一个代理实例(记为<code>Proxy</code>),让这个代理实例去调用实例对象。我们直接与<code>Proxy</code> 打交道,由<code>Proxy</code> 负责与<code>Subject</code> 沟通。</p><p>这样说来,<code>Proxy</code> 相当于一个中间人的角色,负责<strong>我们</strong>与<strong>实际对象</strong>的沟通。</p><h3 id="2-情景示例"><a href="#2-情景示例" class="headerlink" title="2. 情景示例"></a>2. 情景示例</h3><p>我们通过代码来描述这种模式。</p><p>假设一种情景,图片的加载工作。大图片加载很耗时,我们希望在大图片加载过程中先做一些操作(比如显示张小图片、或给个文字提示),等大图片加载完毕后再显示大图片。</p><p>有一点需要注意,在显示大图片前做的操作不确定,这样就不能写死在大图片加载中。让我们考虑下代理模式。</p><p><strong>需要有一个共同的接口类</strong>。</p><p>是的。代理对象(<code>Proxy</code>)对外要表现实际对象(<code>Subject</code>)的功能,所以它们应该有一个共同的接口。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ImageHandler</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">loadImage</span><span class="params">()</span></span>;</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><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">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ImageHandlerImpl</span> <span class="keyword">implements</span> <span class="title">ImageHandler</span> </span>{</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">loadImage</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//用休眠2秒表示图片加载过程</span></span><br><span class="line"> TimeUnit.SECONDS.sleep(<span class="number">2</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"> 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>该设计我们的代理类了。我们知道,代理类是要能执行我们的真实对象方法的。怎样执行?最简单的方式当然是把真实对象的实例传给代理。</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></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">ImageHandlerProxy</span> <span class="keyword">implements</span> <span class="title">ImageHandler</span></span>{</span><br><span class="line"> <span class="keyword">private</span> ImageHandler imageHandler;</span><br><span class="line"></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">ImageHandlerProxy</span><span class="params">(ImageHandler imageHandler)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.imageHandler = imageHandler;</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">loadImage</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//代理做一些预处理</span></span><br><span class="line"> System.out.println(<span class="string">"请等待,正在加载图片"</span>);</span><br><span class="line"><span class="comment">// System.out.println("加载小图片");</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//调用真实对象方法</span></span><br><span class="line"> imageHandler.loadImage();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//可以做一些收尾工作</span></span><br><span class="line"> }</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><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">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> ImageHandler handler = <span class="keyword">new</span> ImageHandlerImpl();</span><br><span class="line"> ImageHandlerProxy proxy = <span class="keyword">new</span> ImageHandlerProxy(handler);</span><br><span class="line"></span><br><span class="line"> proxy.loadImage();</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果先出现提示信息,过一会后图片加载完毕</p><blockquote><p>请等待,正在加载图片<br>图片加载完成。</p></blockquote><h3 id="3-动态代理"><a href="#3-动态代理" class="headerlink" title="3. 动态代理"></a>3. 动态代理</h3><p>在了解动态代理前,我们先思考一下,上面的例子有什么问题?</p><ol><li>动态代理只是实现对一种对象的代理,如上例<code>ImageHandler</code>。这样导致复用性很差,比如我想对视频也实现这样的代理,还要再写一个视频代理类。</li><li>代理方法也写死在接口中(如上例的<code>loadImage()</code>方法),这样我再代理一个方法就要在代理对象再实现一遍。</li></ol><blockquote><p>当然,这些讨论是建立在你有这些需求的基础上。比如你就只需要一个图片加载的代理,静态代理也就足够了。</p></blockquote><p>我们需要一种方法来解决上面的问题。上面例子中,代理类是事先写好的,运行时早已确定。而我们希望的是,能够在运行时动态生成某个类,这样就不必为每个真正需代理的对象都写一个代理类了。</p><p>JDK直接实现了这种需求。我们只需要做到:</p><ol><li>写一个动态代理类实现 <code>InvocationHandler</code>接口。</li><li>使用<code>Proxy</code> 类生成代理对象实例。</li></ol><p>下面通过代码演示一下</p><p>上例中的<code>ImageHandler</code>与<code>ImageHandlerImpl</code> 仍保留。</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="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyDynamicProxy</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 subject;</span><br><span class="line"></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">MyDynamicProxy</span><span class="params">(Object subject)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.subject = subject;</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"> System.out.println(<span class="string">"前置工作"</span>);</span><br><span class="line"> Object result = method.invoke(subject,args);</span><br><span class="line"> System.out.println(<span class="string">"收尾工作"</span>);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>MyDynamicProxy</code> 与前面的静态代理模式中<code>ImageHandlerProxy</code> 相似,都是传入真正对象,最后调用真实对象完成。不同的是,动态代理不耦合某个接口。</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"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDynamic</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//真实对象</span></span><br><span class="line"> ImageHandler realObject = <span class="keyword">new</span> ImageHandlerImpl();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//代理对象的处理器</span></span><br><span class="line"> InvocationHandler handler = <span class="keyword">new</span> MyDynamicProxy(realObject);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//生成代理对象</span></span><br><span class="line"> ImageHandler imageProxy = (ImageHandler) Proxy.newProxyInstance(handler.getClass().getClassLoader(),</span><br><span class="line"> <span class="keyword">new</span> Class[]{ImageHandler.class}, handler);</span><br><span class="line"></span><br><span class="line"> imageProxy.loadImage();</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这就是JDK的动态代理实例。其中最关键的莫过<code>newProxyInstance</code>方法<br><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"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">newProxyInstance</span><span class="params">(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)</span></span></span><br></pre></td></tr></table></figure></p><p>参数分别为类加载器、接口数组及实现了<code>InvocationHandler</code>接口的对象实例。<code>newProxyInstance()</code> 可根据这些信息创建代理对象实例。从而实现动态代理。</p>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>代理模式是很常用的设计模式之一,一般可分为静态代理和动态代理两类。java利用反射也对动态代理提供了支持。今天我们就来学习学习。</p>
<
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="设计模式" scheme="https://blog.wthfeng.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>ReentrantLock原理探究(二)</title>
<link href="https://blog.wthfeng.com/2017-05-24-ReentrantLock%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<id>https://blog.wthfeng.com/2017-05-24-ReentrantLock原理探究(二)/</id>
<published>2017-05-24T04:52:00.000Z</published>
<updated>2018-10-16T13:41:30.043Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>上篇<a href="http://blog.csdn.net/wthfeng/article/details/72510804" target="_blank" rel="noopener">ReentrantLock原理探究(一)</a>介绍了ReentrantLock类的使用说明,详细解析了关于非公平锁的<code>lock()</code>过程。这篇我们继续分析。</p><h2 id="三、源码解析"><a href="#三、源码解析" class="headerlink" title="三、源码解析"></a>三、源码解析</h2><h3 id="2-unlock-方法"><a href="#2-unlock-方法" class="headerlink" title="2.unlock()方法"></a>2.unlock()方法</h3><p>公平锁与非公平锁的<code>unlock()</code>方法相同,就不用区别了。<code>unlock()</code>方法调用了<code>release()</code>方法。</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></pre></td><td class="code"><pre><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="comment">//尝试释放锁</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>看看 <code>tryRelease()</code>,这个方法实现在<code>ReentrantLock</code>的内部类<code>Sync</code>而不是分别留在<code>FairSync</code>或<code>NonfairSync</code>中,这也说明了释放锁的过程与锁的公平性无关。</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="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="comment">//每次释放锁状态减1,若为0说明锁已释放完毕</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="comment">//若锁已释放,将表示占有锁的线程变量设为null</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><p>这个方法比较简单,仅仅是检查并设置状态。不过仍需注意的是,释放锁的方法必须由占有锁的线程调用,否则,则会抛出<code>IllegalMonitorStateException</code>异常。</p><p>假设线程1已执行完,调用<code>unlock()</code>执行到此,会将表示阻塞线程个数状态的<code>state</code>设置0。返回<code>true</code>,进if语句。</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">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></pre></td></tr></table></figure><p>此时<code>head</code>节点是没有线程的空节点,其后跟着表示线程2的线程节点。而<code>head</code>节点的<code>waitStatus</code>为<code>SIGNAL</code>,值为-1。</p><p>进入<code>unparkSuccessor(h)</code>方法,<code>h</code>是队列的头节点。</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="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">unparkSuccessor</span><span class="params">(Node node)</span> </span>{</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> 若节点`waitStatus`为负值,设置0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">int</span> ws = node.waitStatus;</span><br><span class="line"> <span class="keyword">if</span> (ws < <span class="number">0</span>)</span><br><span class="line"> compareAndSetWaitStatus(node, ws, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> 这里有JDK注释,大意是,解锁阻塞队列中的线程。一般情况下是下一个节点(head后面的节点),但若这个节点表示的线程被取消了,则往后遍历直到找到需释放的(waitStatus为负值的)。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Node s = node.next;</span><br><span class="line"> <span class="keyword">if</span> (s == <span class="keyword">null</span> || s.waitStatus > <span class="number">0</span>) {</span><br><span class="line"> s = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node t = tail; t != <span class="keyword">null</span> && t != node; t = t.prev)</span><br><span class="line"> <span class="keyword">if</span> (t.waitStatus <= <span class="number">0</span>)</span><br><span class="line"> s = t;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//解锁head节点后的第一个等待节点</span></span><br><span class="line"> <span class="keyword">if</span> (s != <span class="keyword">null</span>)</span><br><span class="line"> LockSupport.unpark(s.thread);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>LockSupport.unpark(s.thread)</code>方法应该不陌生,上篇中就是用<code>LockSupport.park(this)</code>方法将当前线程锁住的,底层调用的是<code>Unsafe</code>类方法。这里暂不研究。</p><p>这里和<code>lock()</code>的过程对接一下,当线程2完成<code>unlock()</code>过程。唤起线程1。</p><p>此时线程1在<code>acquireQueued()</code>的循环中,</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="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line"> <span class="keyword">if</span> (p == head && tryAcquire(arg)) {</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="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></pre></td></tr></table></figure><p>走循环的第一个if,执行完<code>tryAcquire()</code>后线程2已获取锁。这里进if,设置头节点</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="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setHead</span><span class="params">(Node node)</span> </span>{</span><br><span class="line"> head = node;</span><br><span class="line"> node.thread = <span class="keyword">null</span>;</span><br><span class="line"> node.prev = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>经过设置,阻塞队列重新恢复成只有一个空的头节点的状态。这样获取、释放锁的流程就走完了。其他类似。</p><h3 id="3-公平锁的获取方式"><a href="#3-公平锁的获取方式" class="headerlink" title="3. 公平锁的获取方式"></a>3. 公平锁的获取方式</h3><p>公平锁与非公平锁的解锁流程是一致的,区别只在锁的过程,即<code>FairSync</code>的<code>tryAcquire</code>。</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="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="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></pre></td></tr></table></figure><p>公平锁比非公平的只在<code>hasQueuedPredecessors()</code>方法。此方法会判阻塞队列前面是否有其他线程在等待。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">hasQueuedPredecessors</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// The correctness of this depends on head being initialized</span></span><br><span class="line"> <span class="comment">// before tail and on head.next being accurate if the current</span></span><br><span class="line"> <span class="comment">// thread is first in queue.</span></span><br><span class="line"> Node t = tail; <span class="comment">// Read fields in reverse initialization order</span></span><br><span class="line"> Node h = head;</span><br><span class="line"> Node s;</span><br><span class="line"> <span class="keyword">return</span> h != t &&</span><br><span class="line"> ((s = h.next) == <span class="keyword">null</span> || s.thread != Thread.currentThread());</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>其他步骤与非公平锁一致。</p><h3 id="4-线程取消"><a href="#4-线程取消" class="headerlink" title="4. 线程取消"></a>4. 线程取消</h3><p>前面一直提到线程取消,这里解析一下</p><p>在<code>acquireQueued()</code>方法中<code>finally</code>代码段是处理异常发送后取消线程的,主要调用了<code>cancelAcquire(node)</code>。node为要取消的线程表示的节点。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">cancelAcquire</span><span class="params">(Node node)</span> </span>{</span><br><span class="line"> <span class="comment">//节点为空直接返回</span></span><br><span class="line"> <span class="keyword">if</span> (node == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"> node.thread = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 跳过被取消的(即waitStatus大于0)节点</span></span><br><span class="line"> Node pred = node.prev;</span><br><span class="line"> <span class="keyword">while</span> (pred.waitStatus > <span class="number">0</span>)</span><br><span class="line"> node.prev = pred = pred.prev;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//node前置节点的后置节点,不一定是node</span></span><br><span class="line"> Node predNext = pred.next;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将节点状态改为取消状态</span></span><br><span class="line"> node.waitStatus = Node.CANCELLED;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//若节点是尾节点,直接设为null</span></span><br><span class="line"> <span class="keyword">if</span> (node == tail && compareAndSetTail(node, pred)) {</span><br><span class="line"> compareAndSetNext(pred, predNext, <span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//前置节点不是head,表明该节点前有阻塞的线程</span></span><br><span class="line"> <span class="keyword">int</span> ws;</span><br><span class="line"> <span class="keyword">if</span> (pred != head &&</span><br><span class="line"> ((ws = pred.waitStatus) == Node.SIGNAL ||</span><br><span class="line"> (ws <= <span class="number">0</span> && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&</span><br><span class="line"> pred.thread != <span class="keyword">null</span>) {</span><br><span class="line"> Node next = node.next;</span><br><span class="line"> <span class="keyword">if</span> (next != <span class="keyword">null</span> && next.waitStatus <= <span class="number">0</span>)</span><br><span class="line"> <span class="comment">//连接该节点的前置节点和后续节点,即去掉该节点</span></span><br><span class="line"> compareAndSetNext(pred, predNext, next);</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">//前置节点是head</span></span><br><span class="line"> unparkSuccessor(node);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> node.next = node; <span class="comment">// help GC</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注释都说差不多了,最后调用的<code>unparkSuccessor</code>前面解锁过程已经提过。可以发现,如果外界有异常导致当前线程被取消时,会设置状态,并根据该节点所在位置恰当退出队列。</p><h2 id="四、Condition研究"><a href="#四、Condition研究" class="headerlink" title="四、Condition研究"></a>四、Condition研究</h2><p>上篇示例中演示了<code>Condition()</code>的用法,这里简要分析一下它的底层。</p><h3 id="1-类结构"><a href="#1-类结构" class="headerlink" title="1. 类结构"></a>1. 类结构</h3><p>AQS内部类<code>ConditionObject</code>实现了<code>Condition</code>接口,主要有下列两个字段.</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="comment">//条件队列第一个节点</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> Node firstWaiter;</span><br><span class="line"></span><br><span class="line"><span class="comment">//条件队列最后一个节点 </span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> Node lastWaiter;</span><br></pre></td></tr></table></figure><h3 id="2-await-方法"><a href="#2-await-方法" class="headerlink" title="2.await()方法"></a>2.await()方法</h3><p><code>await()</code>方法需结合Lock类使用,上篇示例中已经提过,此方法与<code>Object.wait()</code>功能相近,阻塞当前线程,直到调用相同锁的<code>signal()</code>方法。</p><p><code>await()</code>方法有众多版本,如<code>awaitNanos(long nanosTimeout)、awaitUntil(Date date)、await(long time, TimeUnit unit)</code>。这里以<code>await()</code>为例。</p><blockquote><p>为表述方便,我们以<a href="https://github.com/wangtonghe/learn-sample/blob/master/learn/src/java/com/wthfeng/learn/thread/ConditionTest.java" target="_blank" rel="noopener">Condition测试用例</a> 为实例,有线程A、B两个线程。线程A遍历到20调用<code>await()</code>阻塞,线程B休眠2秒唤醒线程A。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="comment">//显式检查中断</span></span><br><span class="line"> <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line"> <span class="comment">//用当前线程构造等待条件节点</span></span><br><span class="line"> Node node = addConditionWaiter();</span><br><span class="line"> <span class="comment">//当前线程释放锁</span></span><br><span class="line"> <span class="keyword">int</span> savedState = fullyRelease(node);</span><br><span class="line"> <span class="keyword">int</span> interruptMode = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">//如果不在阻塞队列中,将自己锁住</span></span><br><span class="line"> <span class="keyword">while</span> (!isOnSyncQueue(node)) {</span><br><span class="line"> LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">if</span> ((interruptMode = checkInterruptWhileWaiting(node)) != <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//以下是被唤醒后重新竞争锁,与lock过程类似</span></span><br><span class="line"> <span class="keyword">if</span> (acquireQueued(node, savedState) && interruptMode != THROW_IE)</span><br><span class="line"> interruptMode = REINTERRUPT;</span><br><span class="line"> <span class="keyword">if</span> (node.nextWaiter != <span class="keyword">null</span>) <span class="comment">// clean up if cancelled</span></span><br><span class="line"> unlinkCancelledWaiters();</span><br><span class="line"> <span class="keyword">if</span> (interruptMode != <span class="number">0</span>)</span><br><span class="line"> reportInterruptAfterWait(interruptMode);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><blockquote><p><code>await()</code>方法为暂停该线程,等待唤醒。线程A调用后会阻塞在上面的<code>while</code>循环中。等待唤醒。</p></blockquote><p>首先会检查中断标志,这也是<code>await()</code>方法会响应中断的实现点。<code>addConditionWaiter()</code>以当前线程为节点,<code>fullyRelease()</code>释放当前线程持有的锁,内部调用的是<code>release()</code>。再判断节点是否在队列中,若不在,在循环中阻塞该线程。循环后面的语句是经<code>signal()</code>唤醒后竞争锁的过程。与<code>lock</code>就关联起来了。</p><h3 id="3-signal-方法"><a href="#3-signal-方法" class="headerlink" title="3.signal()方法"></a>3.signal()方法</h3><p><code>signal()</code>主要作用就是唤醒被<code>await</code>的线程。与<code>await()</code>一样,该方法必须在获取锁的情况下使用。</p><figure class="highlight plain"><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">public final void signal() {</span><br><span class="line"> //当前线程是否是锁的持有者</span><br><span class="line"> if (!isHeldExclusively())</span><br><span class="line"> throw new IllegalMonitorStateException();</span><br><span class="line"> Node first = firstWaiter;</span><br><span class="line"> if (first != null)</span><br><span class="line"> doSignal(first);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>主要处理逻辑在<code>doSignal(first)</code>,<code>first</code>是条件队列的首节点。</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="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doSignal</span><span class="params">(Node first)</span> </span>{</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">if</span> ( (firstWaiter = first.nextWaiter) == <span class="keyword">null</span>)</span><br><span class="line"> lastWaiter = <span class="keyword">null</span>;</span><br><span class="line"> first.nextWaiter = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">//遍历找到第一个可以释放的节点(不能是被取消的)</span></span><br><span class="line"> } <span class="keyword">while</span> (!transferForSignal(first) &&</span><br><span class="line"> (first = firstWaiter) != <span class="keyword">null</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看看<code>transferForSignal(first)</code></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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">transferForSignal</span><span class="params">(Node node)</span> </span>{</span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="keyword">if</span> (!compareAndSetWaitStatus(node, Node.CONDITION, <span class="number">0</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">// 将要唤醒的线程节点加入阻塞队列等待唤醒</span></span><br><span class="line"> Node p = enq(node);</span><br><span class="line"> <span class="keyword">int</span> ws = p.waitStatus;</span><br><span class="line"> <span class="comment">//该节点被取消,直接释放解锁</span></span><br><span class="line"> <span class="keyword">if</span> (ws > <span class="number">0</span> || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))</span><br><span class="line"> LockSupport.unpark(node.thread);</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><p>总结一下<code>signal()</code> 的流程。</p><p>线程A已被阻塞(处于<code>while</code>循环),此时在等待队列中(由Condition维护,不同于AQS的同步阻塞队列)。线程B调用<code>signal()</code>后线程A从等待队列取出,放到同步阻塞队列。此时正好破解了线程A的while循环。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// await()方法</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (!isOnSyncQueue(node)) <span class="comment">//是否处于阻塞队列</span></span><br></pre></td></tr></table></figure><p>这样一来流程就清晰了。等线程B执行完后(执行完<code>unlock()</code>释放锁),由于同步阻塞队列没有其他阻塞线程,线程A就可获取锁继续执行了。</p><p>相反,假设阻塞队列有排队的线程,即不止一个线程(这里只有线程A)等待释放,走<code>await()</code>后面的竞争锁过程,其实是<code>lock</code>阻塞的过程,这里就不提了。</p><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol><li><a href="http://ifeve.com/understand-condition/" target="_blank" rel="noopener">怎么理解Condition</a></li><li><a href="http://www.cnblogs.com/xrq730/p/4979021.html" target="_blank" rel="noopener">ReentrantLock实现原理深入探究</a></li><li><a href="http://ifeve.com/introduce-abstractqueuedsynchronizer/" target="_blank" rel="noopener">AbstractQueuedSynchronizer的介绍和原理分析</a></li><li><a href="http://blog.csdn.net/ghsau/article/details/7481142" target="_blank" rel="noopener"> Java线程(九):Condition-线程通信更高效的方式</a></li></ol>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>上篇<a href="http://blog.csdn.net/wthfeng/article/details/72510804" targ
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="并发与多线程" scheme="https://blog.wthfeng.com/tags/%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
<category term="AQS" scheme="https://blog.wthfeng.com/tags/AQS/"/>
</entry>
<entry>
<title>ReentrantLock原理探究(一)</title>
<link href="https://blog.wthfeng.com/2017-05-21-ReentrantLock%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<id>https://blog.wthfeng.com/2017-05-21-ReentrantLock原理探究(一)/</id>
<published>2017-05-21T02:21:00.000Z</published>
<updated>2018-10-16T13:41:30.042Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>ReentrantLock类是synchronized语义的替代品,可以实现与其相同的功能,了解其实现原理对并发编程无疑是很有帮助的。其次,ReentrantLock 的实现基础AQS(AbstractQueuedSynchronizer)也是Java并发编程中相当重要的一个类,所以无论如何,我们都要了解一番。</p><h2 id="一-用法及概念"><a href="#一-用法及概念" class="headerlink" title="一. 用法及概念"></a>一. 用法及概念</h2><h3 id="1-用法"><a href="#1-用法" class="headerlink" title="1. 用法"></a>1. 用法</h3><p><code>ReentrantLock</code>(可重入锁)由java1.5引入,被用来实现<code>synchronized</code>关键字语义。这样就可以从代码级别而不是语言层面实现锁的定义。<code>Lock</code> 是其接口,规范了若干作为锁必须实现的方法。</p><p>下面使用<code>lock</code> 来保证<code>i++</code> 操作的线程安全。</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="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">addUseLock</span><span class="params">()</span> </span>{</span><br><span class="line"> Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"> <span class="comment">//在进行操作前先锁定</span></span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> flag++;</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">//操作结束后一定要在finally中释放锁,否则可能导致死锁</span></span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然,我们也可以用<code>synchronized</code>语义实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">addUseSync</span><span class="params">()</span> </span>{</span><br><span class="line"> flag++;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>Lock</code> 可以创建<code>Condition</code>(条件变量),用来实现<code>wait() 、 notify()</code>语义。从而控制线程间的通信。<code>Condition</code> 中的<code>await</code>与<code>wait</code>类似,<code>signal</code>则相当于<code>notify</code>。两种机制使用也很相似,都<strong>必须在获取锁的情况下操作</strong>。</p><p>下面模拟了<code>Condition</code> 的使用,<code>CountDownLatch</code> 用来使主线程等待两个工作线程结束,可以暂不研究。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//创建一个条件</span></span><br><span class="line"> Condition condition = lock.newCondition();</span><br><span class="line"></span><br><span class="line"> CountDownLatch countDownLatch = <span class="keyword">new</span> CountDownLatch(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 线程A遍历到20后自我阻塞,等待线程B唤醒</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100</span>; i++) {</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">":"</span> + i);</span><br><span class="line"> <span class="keyword">if</span> (i == <span class="number">20</span>) {</span><br><span class="line"> condition.await();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> System.out.println(e.getMessage());</span><br><span class="line"> e.printStackTrace();</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"> countDownLatch.countDown();</span><br><span class="line"></span><br><span class="line"> }, <span class="string">"ThreadA"</span>).start();</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 线程B休眠2秒后唤醒线程A</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> lock.lock();</span><br><span class="line"> TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line"> System.out.println(<span class="string">"唤醒线程"</span>);</span><br><span class="line"> condition.signal();</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> countDownLatch.countDown();</span><br><span class="line"></span><br><span class="line"> },<span class="string">"ThreadB"</span>).start();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> countDownLatch.await();</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></pre></td></tr></table></figure><h3 id="2-公平锁与非公平锁"><a href="#2-公平锁与非公平锁" class="headerlink" title="2. 公平锁与非公平锁"></a>2. 公平锁与非公平锁</h3><p><code>ReentrantLock</code> 有一个很重要的概念就是锁的公平性。<strong>所谓公平锁,就是使线程按照请求锁的顺序依次获得锁</strong>,反之,就是非公平的。也就是说,如果锁是非公平的,有的线程可能一直不断获取锁,而有的线程可能一直获取不到。而公平锁则不会,它会按请求顺序依次分配。</p><p>那为什么要设计非公平锁呢?原因是效率问题。处于CPU调度考虑,采用公平锁会消耗一些性能保证线程调度公平和同步,而且对于重复获取锁的操作中,某个线程连续获取锁的概率是很高的,而公平锁则遏制了这一点。所以,如果线程的执行顺序对你的程序不重要的话,最好使用非公平锁。另外,<code>synchronized</code>内置锁也是非公平锁。</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="comment">//默认为非公平锁</span></span><br><span class="line">Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"></span><br><span class="line"><span class="comment">//通过构造函数创建公平锁 </span></span><br><span class="line">Lock lock2 = <span class="keyword">new</span> ReentrantLock(<span class="keyword">true</span>);</span><br></pre></td></tr></table></figure><h3 id="3-可重入性"><a href="#3-可重入性" class="headerlink" title="3. 可重入性"></a>3. 可重入性</h3><p>从名字就可以看出,<code>ReentrantLock</code> 是可重入锁。即一个线程获取该锁后可以在后续过程中多次获取该锁,以避免被自己锁死的情况。这个语义<code>synchronized</code>也支持。</p><h2 id="二、类结构解析"><a href="#二、类结构解析" class="headerlink" title="二、类结构解析"></a>二、类结构解析</h2><p>下面我们深入<code>ReentrantLock</code> 类结构分析一下</p><h3 id="1-ReentrantLock类结构"><a href="#1-ReentrantLock类结构" class="headerlink" title="1. ReentrantLock类结构"></a>1. ReentrantLock类结构</h3><p>我们刚才提到,<code>ReentrantLock</code> 实现了<code>Lock</code>接口。另外,它还有3个内部类。分别是<code>Sync</code>、<code>NonfairSync</code>、<code>FairSync</code>。<code>Sync</code> 是一个抽象的内部类,代表锁的基本底层实现。后两者分别是对非公平锁和公平锁的实现。下面来看该类的类继承关系和主要方法。</p><p><img src="http://img.blog.csdn.net/20170519115721880?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt></p><p>图中可以看到上面提到的三个内部类。深入<code>Sync</code>类结构,看看它的结构图</p><p><img src="http://img.blog.csdn.net/20170519125457341?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt></p><p>在<code>Sync</code>类结构中,<code>AbstractQueuedSynchronizer</code>类(简称AQS)有着相当重要的地位。不仅如此,AQS其实是整个java并发包的基础。各个并发工具类如<code>Semaphore、CountDownLatch、ReentrantLock、ReentrantReadWriteLock、FutureTask</code> 都是根据自己本身需要在AQS基础上做的定制实现。</p><p>这样一来,我们就了解了AQS与ReentrantLock的相互关系。这种实现模式其实是<strong>模板模式</strong>的典型应用。父类负责制定流程,留下若干接口交给子类具体实现。</p><h3 id="2-AQS类概览"><a href="#2-AQS类概览" class="headerlink" title="2. AQS类概览"></a>2. AQS类概览</h3><p>AbstractQueuedSynchronizer类结构比较复杂,具体源码工作我们在下面章节分析,这里先简单看看该类主要结构和方法。</p><p>AQS的内部类有2个,<code>ConditionObject</code>和<code>Node</code>。<code>ConditionObject</code> 实现<code>Condition</code>接口,用于实现前面提到的条件变量<code>await/singal</code>。</p><p><code>Node</code>为等待队列的节点类。AQS实现依赖一个先进先出的队列,而<code>Node</code>类即是这个队列的节点。用于保存阻塞的线程引用和线程状态。</p><p>Node类主要字段</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><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span> </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><span class="line"></span><br><span class="line"> <span class="comment">//前置节点</span></span><br><span class="line"> <span class="keyword">volatile</span> Node prev;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//后继节点</span></span><br><span class="line"> <span class="keyword">volatile</span> Node next;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//该节点保存的线程</span></span><br><span class="line"> <span class="keyword">volatile</span> Thread thread;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//该队列下一个等待者</span></span><br><span class="line"> Node nextWaiter;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>说完AQS的两个内部类,下面了解下AQS的主要字段</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="comment">//队列头结点</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head;</span><br><span class="line"></span><br><span class="line"><span class="comment">//队列尾结点</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail;</span><br><span class="line"></span><br><span class="line"><span class="comment">//同步状态,0表示未锁,>0表示某线程锁了多少次</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br><span class="line"></span><br><span class="line"><span class="comment">//用于保存独占模式下的当前线程</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> Thread exclusiveOwnerThread;</span><br></pre></td></tr></table></figure><p>没有实例理解起来比较吃力,先不研究这个,下步解析源码时再解析。先看看AQS主要方法。</p><blockquote><p>说明:获取、释放锁的若干方法中,带<code>Shared</code>的是共享方式,否则是以独占方式。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"></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="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquireShared</span><span class="params">(<span class="keyword">int</span> arg)</span></span>;</span><br><span class="line"></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="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">releaseShared</span><span class="params">(<span class="keyword">int</span> arg)</span></span>;</span><br><span class="line"></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">int</span> <span class="title">getState</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setState</span><span class="params">(<span class="keyword">int</span> newState)</span></span>;</span><br><span class="line"></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">compareAndSetState</span><span class="params">(<span class="keyword">int</span> expect, <span class="keyword">int</span> update)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">compareAndSetWaitStatus</span><span class="params">(Node node,<span class="keyword">int</span> expect,<span class="keyword">int</span> update)</span></span>;</span><br><span class="line"></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">compareAndSetHead</span><span class="params">(Node update)</span></span>;</span><br><span class="line"></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">compareAndSetTail</span><span class="params">(Node expect, Node update)</span></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="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> arg)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">int</span> <span class="title">tryAcquireShared</span><span class="params">(<span class="keyword">int</span> arg)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryReleaseShared</span><span class="params">(<span class="keyword">int</span> arg)</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> arg)</span></span>;</span><br></pre></td></tr></table></figure><p>AQS主要方法如上。以获取锁为例,<code>acquire()</code>方法定义了获取锁的主要流程,其中<code>tryAcquire()</code>由子类根据需要定制。</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="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="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>说明:</p><blockquote><ol><li><p><code>ReentrantLock</code>使用了独占的方式获取释放锁,主要用到了<code>tryAcquire</code>、<code>tryRelease</code>。</p></li><li><p>方法中的compareAndSetXXX(arg0,arg1)调用底层CAS原义。即为若内存中该值为arg0,则将其设为arg1。这个设置是原子的,不会中断。</p></li></ol></blockquote><h2 id="三、源码解析"><a href="#三、源码解析" class="headerlink" title="三、源码解析"></a>三、源码解析</h2><p>终于到解析源码阶段了。</p><h3 id="1-lock-方法"><a href="#1-lock-方法" class="headerlink" title="1.lock()方法"></a>1.lock()方法</h3><p>先分析非公平锁的<code>lock</code>方法。</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"><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="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line"> setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line"> <span class="keyword">else</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>流程很简单,若<code>state</code>为0(即当前没有线程争用),将其设为1。同时把当前线程写入<code>exclusiveOwnerThread</code>字段,表示该线程独占锁。</p><p>否则(此时表明<strong>这个锁已经被某个线程占了,我们假设为线程1,而当前线程为线程2</strong>),调用<code>acquire()</code> 。</p><blockquote><p>再往下分析前先想想,既然是独占锁且锁已经被占了,那处理流程应该怎样? 大致流程应该是把该线程放在某个阻塞队列中,等待前面线程执行完后(调用<code>unlock()</code>),通知该线程获取锁去执行。下面来验证一下。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><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="keyword">if</span> (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line"> selfInterrupt();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>先执行<code>tryAcquire()</code>,成功则说明获取锁成功了,不再执行。</li><li>若失败执行<code>acquireQueued()</code>,执行后返回一个值,用于判断是否需要自中断</li></ol><p>流程就这样,在非公平锁中,<code>tryAcquire()</code>调用<code>nonfairTryAcquire()</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="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">//获取状态</span></span><br><span class="line"> <span class="keyword">int</span> c = getState();</span><br><span class="line"> <span class="comment">//若为0,表明前面线程1已执行完,当前线程(线程2)执行获取锁操作</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">//表明获取锁的线程是当前线程,即锁重入,status即锁重入次数</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="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><p>如果线程1还持有锁,线程2执行<code>tryAcquire()</code>会直接返回<code>false</code>,执行<code>acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</code>判断条件。</p><blockquote><p>按猜想,这时应该把线程2(当前线程)加到一个阻塞队列存起来,等待线程1执行完后再尝试获取锁。观察代码,该执行<code>addWaiter()</code>方法了。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">addWaiter</span><span class="params">(Node mode)</span> </span>{</span><br><span class="line"> <span class="comment">//构建当前线程节点</span></span><br><span class="line"> Node node = <span class="keyword">new</span> Node(Thread.currentThread(), mode);</span><br><span class="line"> <span class="comment">// Try the fast path of enq; backup to full enq on failure</span></span><br><span class="line"> Node pred = tail;</span><br><span class="line"> <span class="keyword">if</span> (pred != <span class="keyword">null</span>) {</span><br><span class="line"> node.prev = pred;</span><br><span class="line"> <span class="keyword">if</span> (compareAndSetTail(pred, node)) {</span><br><span class="line"> pred.next = node;</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> enq(node);</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>以线程2(当前线程)创建一个节点,由于此时阻塞队列为空,直接走<code>enq(node)</code>入队操作。</p><blockquote><p>说明:参数mode表示队列的模式,这里传的值是<code>Node.EXCLUSIVE</code>,表示互斥锁,还有一个变量是<code>SHARED</code>,表示共享锁。</p></blockquote><p>看看<code>enq()</code>方法</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">enq</span><span class="params">(<span class="keyword">final</span> Node node)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> Node t = tail;</span><br><span class="line"> <span class="comment">// 判断尾节点是否为空,为空表示队列无值</span></span><br><span class="line"> <span class="keyword">if</span> (t == <span class="keyword">null</span>) { <span class="comment">// Must initialize</span></span><br><span class="line"> <span class="comment">//设置头节点</span></span><br><span class="line"> <span class="keyword">if</span> (compareAndSetHead(<span class="keyword">new</span> Node()))</span><br><span class="line"> <span class="comment">//设置尾节点</span></span><br><span class="line"> tail = head;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//尾节点不为空,将当前线程节点入队</span></span><br><span class="line"> node.prev = t;</span><br><span class="line"> <span class="keyword">if</span> (compareAndSetTail(t, node)) {</span><br><span class="line"> t.next = node;</span><br><span class="line"> <span class="keyword">return</span> t;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>方法里面是个for()循环。此时队列为空,走<code>if</code>逻辑,设置队列头、尾节点。然后再循环一次,此时队列不为空,走<code>else</code>,将当前线程节点加入队列,注意形成的是双向队列,而且是头节点为空节点的双向队列。</p><p>下面该是<code>acquireQueued()</code>方法了</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">final boolean acquireQueued(final Node node, int arg) {</span><br><span class="line"> boolean failed = true;</span><br><span class="line"> try {</span><br><span class="line"> boolean interrupted = false;</span><br><span class="line"> for (;;) {</span><br><span class="line"> //取出当前节点的前一节点,记为p</span><br><span class="line"> final Node p = node.predecessor();</span><br><span class="line"> //若p是头节点,再次尝试获取锁</span><br><span class="line"> if (p == head && tryAcquire(arg)) {</span><br><span class="line"> setHead(node);</span><br><span class="line"> p.next = null; // help GC</span><br><span class="line"> failed = false;</span><br><span class="line"> return interrupted;</span><br><span class="line"> }</span><br><span class="line"> //获取失败,执行`shouldParkAfterFailedAcquire()`方法</span><br><span class="line"> if (shouldParkAfterFailedAcquire(p, node) &&</span><br><span class="line"> parkAndCheckInterrupt())</span><br><span class="line"> interrupted = true;</span><br><span class="line"> }</span><br><span class="line"> } finally {</span><br><span class="line"> if (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><p>主要是一个循环两个<code>if</code>。第一个<code>if</code>里,判断当前线程节点前一个节点是否是<code>head</code>(因为head节点总指向空节点),若是表明该节点是阻塞队列第一个节点,这时再次尝试获取锁。成功重设队列<code>head</code>节点,返回。失败走第二个if。</p><blockquote><p>这里涉及<code>node.predecessor()、setHead(node)</code>两个方法。<code>node.predecessor()</code>返回的是node节点的<code>prev</code>节点。<code>setHead(node)</code>将node设为<code>head</code>节点,<code>thread、prev</code>属性置空。</p></blockquote><p>第二个if,执行<code>shouldParkAfterFailedAcquire()</code>。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">shouldParkAfterFailedAcquire</span><span class="params">(Node pred, Node node)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> ws = pred.waitStatus;</span><br><span class="line"> <span class="keyword">if</span> (ws == Node.SIGNAL)</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * This node has already set status asking a release</span></span><br><span class="line"><span class="comment"> * to signal it, so it can safely park.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (ws > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Predecessor was cancelled. Skip over predecessors and</span></span><br><span class="line"><span class="comment"> * indicate retry.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> node.prev = pred = pred.prev;</span><br><span class="line"> } <span class="keyword">while</span> (pred.waitStatus > <span class="number">0</span>);</span><br><span class="line"> pred.next = node;</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"> * waitStatus must be 0 or PROPAGATE. Indicate that we</span></span><br><span class="line"><span class="comment"> * need a signal, but don't park yet. Caller will need to</span></span><br><span class="line"><span class="comment"> * retry to make sure it cannot acquire before parking.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> compareAndSetWaitStatus(pred, ws, Node.SIGNAL);</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><code>waitStatus</code>表示节点状态,有以下几种状态</p><table><thead><tr><th>属性</th><th>值</th><th>说明</th></tr></thead><tbody><tr><td>CANCELLED</td><td>1</td><td>表示当前的线程被取消,处于这种状态的Node会被踢出队列,被GC回收</td></tr><tr><td>SIGNAL</td><td>-1</td><td>表示当前节点的后继节点表示的线程需要解除阻塞并执行</td></tr><tr><td>CONDITION</td><td>-2</td><td>表示这个节点在条件队列中,因为等待某个条件而被阻塞</td></tr><tr><td>PROPAGATE</td><td>-3</td><td>使用在共享模式可能处于此状态,表示后续节点能够得以执行</td></tr><tr><td>初始状态</td><td>0</td><td>表示当前节点在sync队列中,等待着获取锁。</td></tr></tbody></table><p>此方法中<code>prev</code>参数是<code>node</code>参数的前置节点。在我们的例子中,prev是<code>head</code>节点。因未赋值,<code>waitStatus</code>为0,走<code>compareAndSetWaitStatus(pred, ws, Node.SIGNAL)</code>, 将prev的<code>waitStatus</code>值设为<code>Node.SIGNAL</code>,即-1。返回<code>flase</code>。</p><p>继续循环,获取不到锁后仍走第二个if,此时<code>shouldParkAfterFailedAcquire()</code>返回<code>true</code>,进<code>parkAndCheckInterrupt()</code>。</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">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"> 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>调用 <code>LockSupport.park(this)</code>将当前线程锁住。最终调用的是<code>Unsafe</code>类的<code>park()</code>方法。<code>Unsafe</code>类提供了一些java底层硬件级别的原子操作。可以提供分配内存、修改对象内存位置、挂起恢复线程等操作。我们暂且不研究这个。</p><p>篇幅问题,这篇文章先到这里,等待下篇再来分析公平锁与非公平锁以及<code>unlock</code>过程。</p><p>——————未完待续————————</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://www.ibm.com/developerworks/cn/java/j-jtp10264/" target="_blank" rel="noopener">JDK 5.0 中更灵活、更具可伸缩性的锁定机制</a></li><li><a href="http://www.cnblogs.com/xrq730/p/4979021.html" target="_blank" rel="noopener">ReentrantLock实现原理深入探究</a></li><li><a href="http://ifeve.com/introduce-abstractqueuedsynchronizer/" target="_blank" rel="noopener">AbstractQueuedSynchronizer的介绍和原理分析</a></li><li><a href="http://www.blogjava.net/xylz/archive/2010/07/07/325410.html" target="_blank" rel="noopener">深入浅出 Java Concurrency (8): 锁机制 part 3</a></li></ol>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>ReentrantLock类是synchronized语义的替代品,可以实现与其相同的功能,了解其实现原理对并发编程无疑是很有帮助的。其次,
</summary>
<category term="java" scheme="https://blog.wthfeng.com/categories/java/"/>
<category term="java" scheme="https://blog.wthfeng.com/tags/java/"/>
<category term="并发与多线程" scheme="https://blog.wthfeng.com/tags/%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
<category term="AQS" scheme="https://blog.wthfeng.com/tags/AQS/"/>
</entry>
</feed>