-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
932 lines (449 loc) · 692 KB
/
search.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
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>redis学习笔记-持久化</title>
<link href="/2019-01-06-Redis%E6%8C%81%E4%B9%85%E5%8C%96/"/>
<url>/2019-01-06-Redis%E6%8C%81%E4%B9%85%E5%8C%96/</url>
<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>
<categories>
<category> redis </category>
</categories>
<tags>
<tag> redis </tag>
<tag> cache </tag>
<tag> db </tag>
</tags>
</entry>
<entry>
<title>flume简要说明</title>
<link href="/2018-11-18-flume%E7%AE%80%E8%A6%81%E8%AF%B4%E6%98%8E/"/>
<url>/2018-11-18-flume%E7%AE%80%E8%A6%81%E8%AF%B4%E6%98%8E/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> flume </tag>
<tag> kafka </tag>
</tags>
</entry>
<entry>
<title>netty架构浅析</title>
<link href="/2018-09-17-netty%E6%A6%82%E8%A6%81%E8%AE%B2%E8%A7%A3/"/>
<url>/2018-09-17-netty%E6%A6%82%E8%A6%81%E8%AE%B2%E8%A7%A3/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> io </tag>
<tag> nio </tag>
<tag> netty </tag>
</tags>
</entry>
<entry>
<title>java I/O体系总结(三) java NIO</title>
<link href="/2018-09-13-java%20IO%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%89%EF%BC%89java%20NIO/"/>
<url>/2018-09-13-java%20IO%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%89%EF%BC%89java%20NIO/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> io </tag>
<tag> 总结 </tag>
<tag> nio </tag>
</tags>
</entry>
<entry>
<title>java I/O体系总结</title>
<link href="/2018-09-10-java%E7%9A%84IO%E6%B5%81%E6%80%BB%E7%BB%93/"/>
<url>/2018-09-10-java%E7%9A%84IO%E6%B5%81%E6%80%BB%E7%BB%93/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> io </tag>
<tag> 总结 </tag>
</tags>
</entry>
<entry>
<title>使用注解方式构建dubbo服务</title>
<link href="/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/"/>
<url>/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/</url>
<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>
<categories>
<category> dubbo </category>
</categories>
<tags>
<tag> java </tag>
<tag> dubbo </tag>
<tag> 教程 </tag>
</tags>
</entry>
<entry>
<title>java8日期时间API讲解</title>
<link href="/2018-05-06-java8%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4API/"/>
<url>/2018-05-06-java8%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4API/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> java8 </tag>
<tag> API </tag>
</tags>
</entry>
<entry>
<title>3月java面试总结</title>
<link href="/2018-03-18-3%E6%9C%88java%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/"/>
<url>/2018-03-18-3%E6%9C%88java%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/</url>
<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>
<categories>
<category> 面试 </category>
</categories>
<tags>
<tag> java </tag>
<tag> 面试 </tag>
</tags>
</entry>
<entry>
<title>再谈生产者消费者模式与阻塞队列</title>
<link href="/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/"/>
<url>/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/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 设计模式 </tag>
</tags>
</entry>
<entry>
<title>百万英雄类答题游戏的程序员打开方式</title>
<link href="/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/"/>
<url>/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/</url>
<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>
<categories>
<category> python </category>
</categories>
<tags>
<tag> python </tag>
<tag> 游戏 </tag>
<tag> HQ </tag>
</tags>
</entry>
<entry>
<title>微信跳一跳辅助原理浅析</title>
<link href="/2018-01-07-%E8%B7%B3%E4%B8%80%E8%B7%B3%E8%BE%85%E5%8A%A9%E5%AE%9E%E7%8E%B0/"/>
<url>/2018-01-07-%E8%B7%B3%E4%B8%80%E8%B7%B3%E8%BE%85%E5%8A%A9%E5%AE%9E%E7%8E%B0/</url>
<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>
<categories>
<category> python </category>
</categories>
<tags>
<tag> python </tag>
<tag> 游戏 </tag>
<tag> wechat </tag>
</tags>
</entry>
<entry>
<title>AbstractQueuedSynchronizer整体解析</title>
<link href="/2017-12-10-AQS%E6%95%B4%E4%BD%93%E8%AE%B2%E8%A7%A3/"/>
<url>/2017-12-10-AQS%E6%95%B4%E4%BD%93%E8%AE%B2%E8%A7%A3/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 并发与多线程 </tag>
<tag> AQS </tag>
</tags>
</entry>
<entry>
<title>Wait/Notify通知机制解析</title>
<link href="/2017-12-09-wait%E4%B8%8Enotify%E6%9C%BA%E5%88%B6%E8%AE%B2%E8%A7%A3/"/>
<url>/2017-12-09-wait%E4%B8%8Enotify%E6%9C%BA%E5%88%B6%E8%AE%B2%E8%A7%A3/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> thread </tag>
</tags>
</entry>
<entry>
<title>ElasticSearch笔记二:5.x版本变化</title>
<link href="/2017-10-15-ElasticSearch%205.x%E5%8F%98%E5%8C%96/"/>
<url>/2017-10-15-ElasticSearch%205.x%E5%8F%98%E5%8C%96/</url>
<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>
<categories>
<category> elasticsearch </category>
</categories>
<tags>
<tag> elasticsearch </tag>
<tag> elastic </tag>
</tags>
</entry>
<entry>
<title>快速排序(QuickSort)实践</title>
<link href="/2017-10-01-%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%9E%E8%B7%B5/"/>
<url>/2017-10-01-%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%9E%E8%B7%B5/</url>
<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>
<categories>
<category> 算法 </category>
</categories>
<tags>
<tag> 排序算法 </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>HttpUrlConnection类体系解析</title>
<link href="/2017-10-01-HttpUrlConnection%E8%A7%A3%E6%9E%90/"/>
<url>/2017-10-01-HttpUrlConnection%E8%A7%A3%E6%9E%90/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> http </tag>
<tag> 网络 </tag>
</tags>
</entry>
<entry>
<title>多级选择组件解决实践</title>
<link href="/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/"/>
<url>/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/</url>
<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>
<categories>
<category> javascript </category>
</categories>
<tags>
<tag> javascript </tag>
<tag> 解决方法 </tag>
</tags>
</entry>
<entry>
<title>java代理模式与JDK代理</title>
<link href="/2017-05-24-java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/"/>
<url>/2017-05-24-java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 设计模式 </tag>
</tags>
</entry>
<entry>
<title>ReentrantLock原理探究(二)</title>
<link href="/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/"/>
<url>/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/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 并发与多线程 </tag>
<tag> AQS </tag>
</tags>
</entry>
<entry>
<title>ReentrantLock原理探究(一)</title>
<link href="/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/"/>
<url>/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/</url>
<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>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 并发与多线程 </tag>
<tag> AQS </tag>
</tags>
</entry>
<entry>
<title>java获取方法参数名的若干实践</title>
<link href="/2017-05-15-java%E8%8E%B7%E5%8F%96%E6%96%B9%E6%B3%95%E5%8F%82%E6%95%B0/"/>
<url>/2017-05-15-java%E8%8E%B7%E5%8F%96%E6%96%B9%E6%B3%95%E5%8F%82%E6%95%B0/</url>
<content type="html"><![CDATA[<blockquote><p>文章首发于我的个人博客网站<a href="http://img.wthfeng.com/" target="_blank" rel="noopener">梧桐和风的博客</a>,欢迎关注。</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>我们知道java可以通过反射得到方法名、参数类型等信息。但我们似乎不能直接得到方法的参数名。而在一些场景中,比如构建自己的MVC框架时,我们也想像Spring MVC一样,根据参数名获取用户传来的数据。下面就来总结一下,都有哪些方法可以获得方法的参数名。</p><h2 id="1-使用java8"><a href="#1-使用java8" class="headerlink" title="1. 使用java8"></a>1. 使用java8</h2><p>自java8开始,可以直接通过反射得到方法的参数名。取代了之前如<code>arg0、arg1</code> 等无含义的参数名称。不过这样有个条件:<strong>你必须手动在编译时开启<code>-parameters</code> 参数</strong>,否则还是获取不到。</p><p>以IDEA为例,你需要在<code>Preferences->Build,Execution,Deployment->Compiler->java Compiler</code> 页面添加该编译选项</p><p><img src="/2017-05-15-java获取方法参数/img/posts/java/method-param.png" alt="这里写图片描述"></p><p>下面就是代码了<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestMethodArg</span> </span>{</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">method1</span><span class="params">(String name,String email)</span></span>{</span><br><span class="line"> System.out.println(name+<span class="string">":"</span>+email);</span><br><span class="line"></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">test</span><span class="params">()</span></span>{</span><br><span class="line"> Class<TestMethodArg> clazz = TestMethodArg.class;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//得到方法实体</span></span><br><span class="line"> Method method = clazz.getMethod(<span class="string">"method1"</span>, String.class, String.class);</span><br><span class="line"> <span class="comment">//得到该方法参数信息数组</span></span><br><span class="line"> Parameter[] parameters = method.getParameters();</span><br><span class="line"> <span class="comment">//遍历参数数组,依次输出参数名和参数类型</span></span><br><span class="line"> Arrays.stream(parameters).forEach(p->{</span><br><span class="line"> System.out.println(p.getName()+<span class="string">" : "</span>+p.getType());</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchMethodException 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>输出结果:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">name : class java.lang.String</span><br><span class="line">email : class java.lang.String</span><br></pre></td></tr></table></figure></p><p>可见该方法很简单,重点在于<code>Parameter</code> 这个类,其保存了所代表的这个参数信息,包括这个参数类型、名称、注解等。可惜这个类是java8新加的,只能在jdk8及以后版本使用。</p><p>可以试试去掉<code>-parameters</code>参数,结果会看到参数名又变成<code>arg0、arg1</code>等名称了。</p><h2 id="2-使用javassist获取参数名"><a href="#2-使用javassist获取参数名" class="headerlink" title="2. 使用javassist获取参数名"></a>2. 使用javassist获取参数名</h2><p>使用java8方法有局限性,没办法,既然原生的只能帮我们到这,那我们就尝试使用第三方类库了。比较有名的java字节码操作类库如<code>javassist</code>、<code>asm</code>、<code>cglib</code> 都可以办到。据说<code>cglib</code> 底层使用<code>asm</code> 实现。我们重点研究前两个。</p><p>先看看<code>javassist</code>。<a href="http://jboss-javassist.github.io/javassist/" target="_blank" rel="noopener">javassist</a> 是一个处理java字节码的类库。现已加入<code>JBoss</code> 应用服务器项目。<code>JBoss</code> 就是使用它作为实现<code>AOP</code> 的框架。</p><p>可以看看这篇文章<a href="http://www.jianshu.com/p/43424242846b" target="_blank" rel="noopener">Javassist 使用指南(一)</a> 来了解。简单来说,<code>CtClass</code>表示类对象;<code>CtMethod</code> 表示方法;<code>ClassPool</code> 表示<code>CtClass</code>对象的容器,可从这里获取<code>CtClass</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></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">test3</span><span class="params">()</span> </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"> ClassPool pool = ClassPool.getDefault();</span><br><span class="line"> CtClass ctClass = pool.get(<span class="string">"com.wthfeng.learn.classtest.Sample"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取要操作的方法参数类型数组,为获取该方法代表的CtMethod做准备</span></span><br><span class="line"> Method method = Sample.class.getMethod(<span class="string">"start"</span>, String.class);</span><br><span class="line"> <span class="keyword">int</span> count = method.getParameterCount();</span><br><span class="line"> Class<?>[] paramTypes = method.getParameterTypes();</span><br><span class="line"> CtClass[] ctParams = <span class="keyword">new</span> CtClass[count];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> ctParams[i] = pool.getCtClass(paramTypes[i].getName());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> CtMethod ctMethod = ctClass.getDeclaredMethod(<span class="string">"start"</span>, ctParams);</span><br><span class="line"> <span class="comment">//得到该方法信息类</span></span><br><span class="line"> MethodInfo methodInfo = ctMethod.getMethodInfo();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取属性变量相关</span></span><br><span class="line"> CodeAttribute codeAttribute = methodInfo.getCodeAttribute();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取方法本地变量信息,包括方法声明和方法体内的变量</span></span><br><span class="line"> <span class="comment">//需注意,若方法为非静态方法,则第一个变量名为this</span></span><br><span class="line"> LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);</span><br><span class="line"> <span class="keyword">int</span> pos = Modifier.isStatic(method.getModifiers()) ? <span class="number">0</span> : <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> System.out.println(attr.variableName(i + pos));</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchMethodException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (NotFoundException 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><p>Sample是一个简单的测试类</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="class"><span class="keyword">class</span> <span class="title">Sample</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String name;</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">start</span><span class="params">(String tag)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> String abc = <span class="string">"abc"</span>;</span><br><span class="line"> System.out.println(i);</span><br><span class="line"> System.out.println(tag);</span><br><span class="line"> System.out.println(abc);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果输出<code>tag</code>。<br>控制循环的大小,还可以输出<code>i 、abc</code>等方法体内的变量,这里就不演示了。</p><h2 id="3-使用ASM获取方法参数名"><a href="#3-使用ASM获取方法参数名" class="headerlink" title="3. 使用ASM获取方法参数名"></a>3. 使用ASM获取方法参数名</h2><p>关于ASM的介绍,可以参考<a href="https://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html" target="_blank" rel="noopener">AOP 的利器:ASM 3.0 介绍</a>这篇文章。</p><p>ASM也是一个java字节码操作类库,不同的是它采用事件驱动模型。java class被描述为一棵树,使用<code>visitor</code> 模式遍历类结构,并在需要时进行修改。</p><p>其中主要的类有<code>ClassReader</code>,它可以通过字节数组或class文件获取字节码数据用于后面对字节码的操作。可以认为是字节码的生产者。其主要方法是<code>accept</code>,接受以<code>ClassVisitor、ClassAdapter</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></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 class="keyword">try</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 读取HelloTest的字节码信息到ClassReader中</span></span><br><span class="line"> ClassReader reader = <span class="keyword">new</span> ClassReader(HelloTest.class.getName());</span><br><span class="line"> ClassWriter cw = <span class="keyword">new</span> ClassWriter(ClassWriter.COMPUTE_MAXS);</span><br><span class="line"> <span class="comment">//accept接收了一个ClassAdapter的子类,想要操作什么,就在子类实现什么</span></span><br><span class="line"> reader.accept(<span class="keyword">new</span> ClassAdapter(cw) {</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="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> MethodVisitor <span class="title">visitMethod</span><span class="params">(<span class="keyword">final</span> <span class="keyword">int</span> access, <span class="keyword">final</span> String name, <span class="keyword">final</span> String desc,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">final</span> String signature, <span class="keyword">final</span> String[] exceptions)</span> </span>{</span><br><span class="line"> <span class="comment">//不需要操作的方法,直接返回,注意不要返回null,会把该方法删掉</span></span><br><span class="line"> <span class="keyword">if</span> (!name.equals(<span class="string">"test1"</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.visitMethod(access, name, desc, signature, exceptions);</span><br><span class="line"> }</span><br><span class="line"> MethodVisitor v = <span class="keyword">super</span>.visitMethod(access, name, desc,</span><br><span class="line"> signature, exceptions);</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 遍历该方法信息,比如参数、注解等,这里我们要操作参数,所以实现了参数方法</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MethodAdapter(v) {</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">visitLocalVariable</span><span class="params">(String name, String desc, String signature, Label start, Label end, <span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="comment">//如果是静态方法,第一个参数就是方法参数,非静态方法,则第一个参数是 this ,然后才是方法的参数</span></span><br><span class="line"> System.out.println(name + <span class="string">","</span> + index);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">super</span>.visitLocalVariable(name, desc, signature, start, end, index);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</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></pre></td></tr></table></figure><p><code>HelloTest</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="class"><span class="keyword">class</span> <span class="title">HelloTest</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test1</span><span class="params">(String userName)</span> </span>{</span><br><span class="line"> System.out.println(userName);</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">test2</span><span class="params">(String email)</span> </span>{</span><br><span class="line"> System.out.println(email);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们这里获取的是<code>test1</code> 这个方法的参数名,输出如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">this,0</span><br><span class="line">userName,1</span><br></pre></td></tr></table></figure><p>可见如愿获得了参数名,如果你不想获取this,可参考<code>javassist</code>的例子。</p><h2 id="4-Spring-MVC是怎样获取的"><a href="#4-Spring-MVC是怎样获取的" class="headerlink" title="4. Spring MVC是怎样获取的"></a>4. Spring MVC是怎样获取的</h2><p>一开始我们就提过,<code>spring mvc</code> 框架本身支持这个功能,那何不看看它是怎么实现的?</p><p>Spring MVC的<code>DefaultParameterNameDiscoverer</code> 负责实现这个功能。大体思路是:</p><ol><li>判断是否是java8(通过<code>Executable</code>这个java8引入的类判断,<code>Parameter</code> 许多方法使用了该类),若是,尝试使用java8的方法获取。</li><li>使用经spring封装的ASM获取,本质还是使用ASM。</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></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">DefaultParameterNameDiscoverer</span> <span class="keyword">extends</span> <span class="title">PrioritizedParameterNameDiscoverer</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">boolean</span> standardReflectionAvailable = ClassUtils.isPresent(</span><br><span class="line"><span class="string">"java.lang.reflect.Executable"</span>, DefaultParameterNameDiscoverer.class.getClassLoader());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">DefaultParameterNameDiscoverer</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//java8的方式</span></span><br><span class="line"><span class="keyword">if</span> (standardReflectionAvailable) {</span><br><span class="line">addDiscoverer(<span class="keyword">new</span> StandardReflectionParameterNameDiscoverer());</span><br><span class="line">}</span><br><span class="line"><span class="comment">//ASM的方式</span></span><br><span class="line">addDiscoverer(<span class="keyword">new</span> LocalVariableTableParameterNameDiscoverer());</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>———-全文完————</p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> javassist </tag>
<tag> asm </tag>
</tags>
</entry>
<entry>
<title>java多线程之线程通信</title>
<link href="/2017-05-11-java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8B%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1/"/>
<url>/2017-05-11-java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8B%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1/</url>
<content type="html"><![CDATA[<p>在多线程机制中,线程之间需要传输信息。一般有以下几种通信机制:</p><ol><li>共享对象:通过在共享对象中设置信号量,多个线程通过读取、修改该信号量来通信。</li><li>wait/notify()方法:线程之间通过调用wait()、notify()方法实现线程等待、唤醒状态,从而达到线程通信的目的。</li></ol><p>接下来我们分别看看这两种方法:</p><h2 id="通过共享对象通信"><a href="#通过共享对象通信" class="headerlink" title="通过共享对象通信"></a>通过共享对象通信</h2><p>在共享对象中设置信号量是最简单也是最常用的线程通信方法。共享变量需要使用<code>volatile</code> 修饰。我们知道,<code>volatile</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><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestShared</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">boolean</span> flag =<span class="keyword">false</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="keyword">private</span> 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="function"><span class="keyword">private</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"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>;;i++) {</span><br><span class="line"> <span class="keyword">if</span> (flag) { <span class="comment">//当共享变量为true时,停止循环</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> System.out.println(i+<span class="string">"-------->"</span>+Thread.currentThread().getName());</span><br><span class="line"> }</span><br><span class="line"> countDownLatch.countDown();</span><br><span class="line"> },<span class="string">"thread-one"</span>).start();</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">test2</span><span class="params">()</span></span>{</span><br><span class="line"></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"> Thread.sleep(<span class="number">3000</span>);</span><br><span class="line"> flag=<span class="keyword">true</span>; <span class="comment">//3秒后将共享变量设为true</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"> countDownLatch.countDown();</span><br><span class="line"></span><br><span class="line"> },<span class="string">"thread-two"</span>).start();</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> TestShared testShared = <span class="keyword">new</span> TestShared();</span><br><span class="line"> testShared.test();</span><br><span class="line"> testShared.test2();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> testShared.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><span class="line">}</span><br></pre></td></tr></table></figure><p>以上例子应该很好理解,利用共享变量<code>flag</code> 控制<code>thread-one</code>线程的停止。即完成了线程之间的通信。</p><blockquote><p>这里需要说明的是,你会发现,即使你的共享变量没有用<code>volatile</code>修饰,线程one也会在规定时间停止。是的。volatile的作用是保证修饰变量对其他线程的可见性。但这并不表明,你不用<code>volatile</code>修饰,其他线程就看不到这个变量了。你最终都会看到这个变化,只是<code>volatile</code>会让你立马看到修改的结果。</p></blockquote><p>有关<code>volatile</code>的介绍,请参看我的另一篇文章<a href="http://img.wthfeng.com/java/2017/02/05/java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/" target="_blank" rel="noopener">java内存模型与volatile详解</a></p><h2 id="wait、notify-方法"><a href="#wait、notify-方法" class="headerlink" title="wait、notify()方法"></a>wait、notify()方法</h2><p>java内置了一个等待机制实现线程之间的通信,在Object对象中有wait()、notify()、及notifyAll()3个方法。</p><p>在一个线程中调用了某对象<code>wait()</code>后,该线程就将处于等待状态。直到另一个线程调用同一对象的<code>notify()</code>才能继续运行。这样我们就可以据此实现线程通信。</p><p><strong>另外需特别注意的是:为了调用某对象的<code>wait、notify</code>方法,你需要获取这个对象的锁</strong>。这是必须的。在实现上,JVM会检查调用<code>wait</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><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WaitNotifyTest</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * wait、notify用法</span></span><br><span class="line"><span class="comment"> * 当执行一个对象的wait()、notify()方法时,必须持有这个对象的锁,即在同步块中调用方法</span></span><br><span class="line"><span class="comment"> * 在JVM实现中,当执行一个对象的wait()方法时,会首先检查它是否在同步块中,否则抛出异常</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> args args</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">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> NotifyObject notifyObject = <span class="keyword">new</span> NotifyObject();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> System.out.println(Thread.currentThread().getName()+<span class="string">"开始"</span>);</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"> <span class="keyword">if</span>(i==<span class="number">50</span>){</span><br><span class="line"> <span class="comment">//需获得这个对象的锁才能执行wait()方法</span></span><br><span class="line"> <span class="keyword">synchronized</span> (notifyObject){</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> notifyObject.wait();</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"> System.out.println( Thread.currentThread().getName()+<span class="string">":"</span>+i);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }).start();</span><br><span class="line"> <span class="keyword">new</span> Thread(()->{</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">500</span>;i++){</span><br><span class="line"> System.out.println( Thread.currentThread().getName()+<span class="string">":"</span>+i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">synchronized</span> (notifyObject){</span><br><span class="line"> notifyObject.notify();</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">NotifyObject</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当第一个线程运行到<code>i=50</code>后,进入阻塞状态。直到第二个线程执行完毕,线程一才开始继续执行。</p><p> wait、notify机制要记得在执行该方法时,必须获得了该对象的锁。所以,为了使用方便,我们可以对其进行一定封装。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></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">WaitNotify2Test</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> NotifyObject notifyObject = <span class="keyword">new</span> NotifyObject();</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">doWait</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (notifyObject) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> notifyObject.wait();</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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doNotify</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (notifyObject) {</span><br><span class="line"> notifyObject.notify();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> WaitNotify2Test waitNotify2Test = <span class="keyword">new</span> WaitNotify2Test();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</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"> <span class="keyword">if</span>(i==<span class="number">50</span>){</span><br><span class="line"> waitNotify2Test.doWait();</span><br><span class="line"> }</span><br><span class="line"> System.out.println( Thread.currentThread().getName()+<span class="string">":"</span>+i);</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">new</span> Thread(()->{</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">500</span>;i++){</span><br><span class="line"> System.out.println( Thread.currentThread().getName()+<span class="string">":"</span>+i);</span><br><span class="line"> }</span><br><span class="line"> waitNotify2Test.doNotify();</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>NotifyObject</code>类即是上面示例的内部类。通过对<code>wait、notify</code>的封装,我们可以使用更方便。</p><p>另外,还有一点需要注意:</p><blockquote><p>不要在字符串常量或全局变量中调用wait、notify()方法。</p></blockquote><p>这是因为,如果你的程序中有多套wait、notify线程,都使用一个字符串常量作为监视器的对象,则会出现假唤醒的情况。</p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> java多线程 </tag>
</tags>
</entry>
<entry>
<title>Kibana 5.4简明教程</title>
<link href="/2017-05-09-Kibana5.4%E7%AE%80%E6%98%8E%E6%95%99%E7%A8%8B/"/>
<url>/2017-05-09-Kibana5.4%E7%AE%80%E6%98%8E%E6%95%99%E7%A8%8B/</url>
<content type="html"><![CDATA[<blockquote><p>本文翻译自<a href="https://www.elastic.co/guide/en/kibana/5.4/getting-started.html" target="_blank" rel="noopener">Kibana5.4版本官方教程</a>。文章首发于我的个人博客网站<a href="http://img.wthfeng.com/" target="_blank" rel="noopener">梧桐和风的博客</a>,欢迎关注。</p></blockquote><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><p>想要得到一些关于Kibana的实际经验吗,下面教程会叫你怎样做。</p><ul><li>向Elasticsearch导入一些样本数据</li><li>定义一个索引模式</li><li>用<a href="https://www.elastic.co/guide/en/kibana/current/discover.html" target="_blank" rel="noopener">Discover</a>搜索样本数据</li><li>为样本数据建立<a href="https://www.elastic.co/guide/en/kibana/current/visualize.html" target="_blank" rel="noopener">visualizations</a>【可视化图表组件】</li><li>将可视化数据汇集到<a href="https://www.elastic.co/guide/en/kibana/current/dashboard.html" target="_blank" rel="noopener">Dashboard</a></li></ul><p>开始之前,你必须确保已<a href="https://www.elastic.co/guide/en/kibana/current/install.html" target="_blank" rel="noopener">成功安装Kibana</a>,并且能<a href="https://www.elastic.co/guide/en/kibana/current/connect-to-elasticsearch.html" target="_blank" rel="noopener">连上Elasticsearch</a></p><h2 id="加载样本数据"><a href="#加载样本数据" class="headerlink" title="加载样本数据"></a>加载样本数据</h2><p>本部分内容依赖下列数据</p><ul><li>莎士比亚全集,已适当分析成字段。点击下载<a href="https://download.elastic.co/demos/kibana/gettingstarted/shakespeare.json" target="_blank" rel="noopener">shakespeare.json</a></li><li>一组随机的账户信息。点击下载<a href="https://download.elastic.co/demos/kibana/gettingstarted/accounts.zip" target="_blank" rel="noopener">accounts.zip</a></li><li>一组随机生成的日志文件。点击下载<a href="https://download.elastic.co/demos/kibana/gettingstarted/logs.jsonl.gz" target="_blank" rel="noopener">logs.josnl.gz</a></li></ul><p>下载完成后,用下面的命令解压文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">unzip accounts.zip</span><br><span class="line">gunzip logs.jsonl.gz</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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"line_id"</span>: integer,</span><br><span class="line"> <span class="attr">"play_name"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"speech_number"</span>: integer,</span><br><span class="line"> <span class="attr">"line_number"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"speaker"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"text_entry"</span>: <span class="string">"String"</span>,</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><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="attr">"account_number"</span>:integer,</span><br><span class="line"> <span class="attr">"balance"</span>:integer,</span><br><span class="line"> <span class="attr">"firstname"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"lastname"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"age"</span>:integer,</span><br><span class="line"> <span class="attr">"gender"</span>: <span class="string">"M or F"</span>,</span><br><span class="line"> <span class="attr">"address"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"employer"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"email"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"city"</span>: <span class="string">"String"</span>,</span><br><span class="line"> <span class="attr">"state"</span>: <span class="string">"String"</span></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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"memory"</span>: integer,</span><br><span class="line"> <span class="attr">"geo.coordinates"</span>: <span class="string">"geo_point"</span>,</span><br><span class="line"> <span class="attr">"@timestamp"</span>:<span class="string">"date"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在我们加载莎士比亚数据和日志文档时,需要为这些字段建立映射(Mapping),映射将索引中的文档划分成逻辑组,并指定文档的特性。比如文档的可分析性或是否可被标记、可被分析等特性。</p><p>使用下面的终端命令(比如使用<code>bash</code>)设置莎士比亚数据的映射。</p><figure class="highlight bash"><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">curl -H <span class="string">'Content-Type: application/json'</span> -XPUT http://localhost:9200/shakespeare -d <span class="string">'</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> "mappings" : {</span></span><br><span class="line"><span class="string"> "_default_" : {</span></span><br><span class="line"><span class="string"> "properties" : {</span></span><br><span class="line"><span class="string"> "speaker" : {"type": "string", "index" : "not_analyzed" },</span></span><br><span class="line"><span class="string"> "play_name" : {"type": "string", "index" : "not_analyzed" },</span></span><br><span class="line"><span class="string"> "line_id" : { "type" : "integer" },</span></span><br><span class="line"><span class="string"> "speech_number" : { "type" : "integer" }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">'</span>;</span><br></pre></td></tr></table></figure><p>下面一一来分析该映射含义:</p><ul><li><code>speaker</code>字段为不能被分析字符串类型。表明该字段将被当做独立的单元处理,即使包含多个词。</li><li><code>play_name</code>处理同上。</li><li><code>line_id</code>和<code>speech_number</code>字段为数值类型。</li></ul><p>使用以下命令为日志文件创建<code>geo_point</code>类型的映射。</p><figure class="highlight bash"><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">curl -H <span class="string">'Content-Type: application/json'</span> -XPUT http://localhost:9200/logstash-2015.05.18 -d <span class="string">'</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> "mappings": {</span></span><br><span class="line"><span class="string"> "log": {</span></span><br><span class="line"><span class="string"> "properties": {</span></span><br><span class="line"><span class="string"> "geo": {</span></span><br><span class="line"><span class="string"> "properties": {</span></span><br><span class="line"><span class="string"> "coordinates": {</span></span><br><span class="line"><span class="string"> "type": "geo_point"</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">'</span>;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><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">curl -H <span class="string">'Content-Type: application/json'</span> -XPUT http://localhost:9200/logstash-2015.05.19 -d <span class="string">'</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> "mappings": {</span></span><br><span class="line"><span class="string"> "log": {</span></span><br><span class="line"><span class="string"> "properties": {</span></span><br><span class="line"><span class="string"> "geo": {</span></span><br><span class="line"><span class="string"> "properties": {</span></span><br><span class="line"><span class="string"> "coordinates": {</span></span><br><span class="line"><span class="string"> "type": "geo_point"</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">'</span>;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><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">curl -H <span class="string">'Content-Type: application/json'</span> -XPUT http://localhost:9200/logstash-2015.05.20 -d <span class="string">'</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> "mappings": {</span></span><br><span class="line"><span class="string"> "log": {</span></span><br><span class="line"><span class="string"> "properties": {</span></span><br><span class="line"><span class="string"> "geo": {</span></span><br><span class="line"><span class="string"> "properties": {</span></span><br><span class="line"><span class="string"> "coordinates": {</span></span><br><span class="line"><span class="string"> "type": "geo_point"</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">'</span>;</span><br></pre></td></tr></table></figure><p>有关账户信息的数据不需要建立映射。现在我们来把这些数据导入到Elasticsearch中。这里需要用到<a href="https://www.elastic.co/guide/en/elasticsearch/reference/5.4/docs-bulk.html" target="_blank" rel="noopener">Elasticsearch bulk API</a></p><figure class="highlight bash"><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">curl -H <span class="string">'Content-Type: application/x-ndjson'</span> -XPOST <span class="string">'localhost:9200/bank/account/_bulk?pretty'</span> --data-binary @accounts.json</span><br><span class="line"></span><br><span class="line">curl -H <span class="string">'Content-Type: application/x-ndjson'</span> -XPOST <span class="string">'localhost:9200/shakespeare/_bulk?pretty'</span> --data-binary @shakespeare.json</span><br><span class="line"></span><br><span class="line">curl -H <span class="string">'Content-Type: application/x-ndjson'</span> -XPOST <span class="string">'localhost:9200/_bulk?pretty'</span> --data-binary @logs.jsonl</span><br></pre></td></tr></table></figure><p>执行以下命令可能需要一些时间,至于多少就要看你的计算机性能水平了。可用下面命令验证是否导入成功:</p><blockquote><p>curl ‘localhost:9200/_cat/indices?v’</p></blockquote><p>若执行成功,你应该能看到类似下面的输出</p><figure class="highlight bash"><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">health status index pri rep docs.count docs.deleted store.size pri.store.size</span><br><span class="line">yellow open bank 5 1 1000 0 418.2kb 418.2kb</span><br><span class="line">yellow open shakespeare 5 1 111396 0 17.6mb 17.6mb</span><br><span class="line">yellow open logstash-2015.05.18 5 1 4631 0 15.6mb 15.6mb</span><br><span class="line">yellow open logstash-2015.05.19 5 1 4624 0 15.7mb 15.7mb</span><br><span class="line">yellow open logstash-2015.05.20 5 1 4750 0 16.4mb 16.4mb</span><br></pre></td></tr></table></figure><h2 id="定义你的索引模式"><a href="#定义你的索引模式" class="headerlink" title="定义你的索引模式"></a>定义你的索引模式</h2><p>每个加载到Elasticsearch的数据集需要一个索引模式(index pattern)。<br>在前面的章节里,莎士比亚数据集的索引名为<code>shakespeare</code>,账户数据集的索引名为<code>bank</code>。<strong>索引模式是一个含有可选通配符的字符串,它能匹配多个索引</strong>。举个例子,在常见的日志中,一个包含日期的索引名如<code>YYYY.MM.DD</code>,则对应5月的索引模式应该是<code>logstach-2915-05*</code>这样。</p><p>在该文档中,任何被匹配的索引都将被Kibana分析并处理。打开浏览器访问<code>localhost:5601</code>,【译者注:需保证同时开启Elasticsearch和Kibana】单击左侧面板的<code>Management</code>标签,会出现<code>Configure an index pattern</code>页面。在<code>Index name or pattern</code>填入<code>shakes*</code>以便能匹配莎士比亚数据集的数据。这时需注意不要勾选<code>Index contains time-based events</code>选项。随后点击<code>create</code>即可。照此再创建一个索引模式名为<code>ba*</code>,以能匹配账户信息。</p><p>日志文件数据集是基于时间的,所以再创建第3个索引模式,这时选中<code>Index contains time-based events</code>选项,选择<code>@timestamp</code>字段作为分析日期的依据。</p><h2 id="探索你的数据"><a href="#探索你的数据" class="headerlink" title="探索你的数据"></a>探索你的数据</h2><p>点击左侧面板中的<code>Discover</code>标签。即进入探索(Discover)数据页面。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-discover.png" alt></p><p>最上端是查询框,你可以输入<a href="https://www.elastic.co/guide/en/elasticsearch/reference/5.4/query-dsl-query-string-query.html#query-string-syntax" target="_blank" rel="noopener">Elasticsearch 查询语句</a>查询你的数据。你可以导出查询结果,根据保存结果创建可视化图表(visualizations)。</p><p>当前匹配的索引数据展示在搜索框下面。索引模式决定了在你搜索时哪些索引的数据会显示出来。</p><p>你可以直接用字段名或你感兴趣的任何值来构建你的查询。对应数值字段,你可以直接使用<code>></code>,<code><</code>,<code>=</code>等运算符。或者,你可以用 <code>AND</code>、<code>OR</code>,<code>NOT</code>等逻辑运算符连接你的查询元素。</p><p>比如,选择<code>ba*</code>模式匹配然后在查询框中输入下列查询</p><blockquote><p>account_number:<100 and balance:>47500</100></p></blockquote><p>该查询将返回账户在0至99之间的,余额在47500以上的所有数据。如下图所示,它返回了5条数据,账户分别是<code>8,32,78,85,97</code>。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-discover-2.png" alt></p><p>默认情况下,每条数据将显示所有字段。为了只让需要的字段显示,你可以在左侧展示字段的区域点击要显示字段右侧的<code>add</code>按钮。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-discover-3.png" alt></p><h2 id="可视化你的数据"><a href="#可视化你的数据" class="headerlink" title="可视化你的数据"></a>可视化你的数据</h2><p>点击左侧面板的<code>Visualize</code>按钮开始可视化数据。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-landing.png" alt></p><p>可视化工具能够以多种方式观察分析你的数据,下面我们就用饼状图来分析一下账户信息数据集。点击下图的<code>Create a visualization</code>开始我们的实践。</p><p>下面展示了几种分析数据的可视化类型,点击<code>pie</code>类型。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-wizard-step-1.png" alt></p><p>你可以用保存的数据完成可视化工作,或者重建一个搜索条件。由于我们要分析账户信息数据,所以选择<code>ba*</code>索引模式。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-wizard-step-2.png" alt></p><p>默认搜索匹配所有文档数据,现在,饼状图是单个的“一大块”</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-pie-1.png" alt></p><p>为了指定要在图表中显示哪些分片,我们需要使用Elasticsearch的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/5.4/search-aggregations.html" target="_blank" rel="noopener">buncket聚合</a>(即桶聚合)。桶聚合会将符合搜索条件的文档分为不同的类别。例如,帐户数据包括每个帐户的余额,使用桶聚合,可以建立多个帐户余额范围,并找出每个范围的账户数量。</p><p>下面是建立桶聚合的过程:</p><ol><li>点击<code>Split Slices</code>桶类型</li><li>在<code>Aggregation</code>下拉列表中选择<code>Range</code>聚合类型</li><li>在<code>Field</code>列表中选择<code>balance</code>字段</li><li>点击<code>Add Range</code>依次输入下列范围</li><li>点击<code>Apply changes</code>执行分析。</li></ol><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">0 999</span><br><span class="line">1000 2999</span><br><span class="line">3000 6999</span><br><span class="line">7000 14999</span><br><span class="line">15000 30999</span><br><span class="line">31000 50000</span><br></pre></td></tr></table></figure><p>现在你能看到每个范围内的具体分布。</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-pie-2.png" alt></p><p>让我们看看数据的另一个维度:帐户持有者的年龄。通过添加另一个桶聚合,您可以在每个余额范围内查看帐户持有人的年龄</p><ol><li>点击<code>Add sub-buckets</code>按钮添加一个子聚合。</li><li>点击<code>Split Slices</code>聚合类型</li><li>从聚合列表中选择<code>Terms</code></li><li>在字段列表选择<code>age</code>字段</li><li>点击<code>Apply changes</code>执行分析。</li></ol><p>现在你可以看到帐户持有人的年龄在上述各个范围的分布状况</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-pie-3.png" alt></p><p>若要保存此图表,以便以后使用它,单击“save”并输入”Pie Example”。</p><p>接下来,让我们来看看莎士比亚数据集的怎样在条形图中显示和分析。</p><ol><li>在“选择可视化类型”页面点击<code>Vertical Bar</code>类型。</li><li>选择<code>shakes*</code>索引模式。</li><li>为在Y轴显示每部戏中演讲者的数量,需要配置Y轴方向的度量值。选择<code>Unique Count</code>度量类型以及<code>speaker</code>字段。另外,你可以为该统计自定义一个标签名,比如这里称为<code>Speaking Parts</code>。</li><li>创建<code>X-Axis</code>聚合类型。选择<code>Terms</code>类型及<code>play_name</code>字段,可在自定义标签中写<code>Play Name</code>。</li><li>点击<code>Apply changes</code>执行分析。</li></ol><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-bar-2.png" alt></p><p>注意剧本名是如何表现出来的,而不是被分解成单个的单词。这是因为我们在开始就设置了<code>play_name</code>是不被分析的。</p><p>下面再来一点复杂的,你可能好奇每部戏中最大的对白台词是多少,你可以这样做:</p><ol><li>点击<code>metrics</code>再添加一个Y轴聚合</li><li>选择<code>Max</code>聚合类型及<code>speech_number</code>字段</li><li>选择<code>Options</code>选项卡,将<code>Bar Mode</code> 改为 <code>grouped</code>模式。【译者注:这里在我这的版本并没有看到该选项,不知是不是版本问题,若同此,选择<code>Metrics&Axes</code>,在相应聚合下将<code>Mode</code>的<code>stack</code>改为<code>mormal</code>】</li><li>点击<code>Apply changes</code>执行分析。</li></ol><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-bar-3.png" alt></p><p>保存图表并将其命名为 <code>Bar Example</code>.</p><p>接下来,我们将使用瓦片地图(Tile Map)来可视化我们日志文件示例数据中的地理信息。</p><ol><li>在“选择可视化类型”页面点击<code>Tile map</code>类型。</li><li>选择<code>logstash-*</code>索引模式</li><li>点击右上角时间按钮,在弹出的时间选择面板中选择<code>absolute</code>,然后将时间定为起始2015-05-18到2015-05-20区间。<br><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-timepicker.png" alt></li><li><p>选择<code>Geohash</code>聚合类型,点击<code>Apply changes</code>执行分析即可看到如下信息</p><p> <img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-map-2.png" alt></p></li></ol><p>你可以通过地图左上角的按钮对地图进行放大缩小、看清地图所有点及各种操作。</p><p> <img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-map-3.png" alt></p><p>最后可以点击<code>save</code>保存该结果,并命名为<code>Map Example</code>。</p><p>最后,可以使用<code>Markdown widget</code>来制作markdown编辑器。</p><ol><li>在“选择可视化类型”页面点击<code>Other</code>中的<code>MarkDown</code>类型。</li><li>接下来就可以编写markdown文档了。</li></ol><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-visualize-md-2.png" alt></p><h2 id="用Dashboards集中展示数据"><a href="#用Dashboards集中展示数据" class="headerlink" title="用Dashboards集中展示数据"></a>用Dashboards集中展示数据</h2><p>Dashboards(仪表盘)可将上部分的可视化图表组件集中起来。你可以通过以下步骤完成。</p><ol><li>点击左侧面板的<code>Dashboard</code>标签。</li><li>点击<code>Add</code>,可显示出你在<code>visualizations</code>保存的可视化图列表</li><li>选取要显示在仪表盘的可视化组件,这里我们选择<code>Markdown Example</code>, <code>Pie Example</code>, <code>Bar Example</code>, and <code>Map Example</code>。</li></ol><p>好了,仪表盘就这样做好了</p><p><img src="/2017-05-09-Kibana5.4简明教程/img/posts/kibana5.4/tutorial-dashboard.png" alt></p>]]></content>
<categories>
<category> ELK </category>
</categories>
<tags>
<tag> elastic </tag>
<tag> kibana </tag>
<tag> 译文 </tag>
</tags>
</entry>
<entry>
<title>latke源码解析(二)Ioc部分</title>
<link href="/2017-05-01-latke%E5%88%86%E6%9E%902ioc%E9%83%A8%E5%88%86/"/>
<url>/2017-05-01-latke%E5%88%86%E6%9E%902ioc%E9%83%A8%E5%88%86/</url>
<content type="html"><![CDATA[<p>上篇<a href="https://hacpai.com/article/1493267456529" target="_blank" rel="noopener"> Latke源码解析(一)Servlet部分</a>讲解了latke有关web 请求的servlet部分,这次深入了解一下它的Ioc部分内容。</p><p>备注: <strong>本人水平有限,若发现文章有误,请积极留言</strong>。</p><h2 id="一、从监听器开始"><a href="#一、从监听器开始" class="headerlink" title="一、从监听器开始"></a><strong>一、从监听器开始</strong></h2><p>同Spring一样,latke通过配置监听器初始化bean。在<a href="https://github.com/b3log/latke-demo" target="_blank" rel="noopener">latke-demo</a>的<code>web.xml</code>监听器部分如下</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">listener</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">listener-class</span>></span>org.b3log.latke.demo.hello.HelloServletListener<span class="tag"></<span class="name">listener-class</span>></span></span><br><span class="line"><span class="tag"></<span class="name">listener</span>></span></span><br></pre></td></tr></table></figure><p>从这里可以看出,该demo的监听器为<code>HelloServletListener</code>。下面看看<code>HelloServletListener</code></p><p>HelloServletListener.java<br><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="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloServletListener</span> <span class="keyword">extends</span> <span class="title">AbstractServletListener</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">contextInitialized</span><span class="params">(<span class="keyword">final</span> ServletContextEvent servletContextEvent)</span> </span>{</span><br><span class="line"> Latkes.setScanPath(<span class="string">"org.b3log.latke.demo.hello"</span>); <span class="comment">//设置项目扫描的包</span></span><br><span class="line"> <span class="keyword">super</span>.contextInitialized(servletContextEvent); <span class="comment">//调用父类的contextInitialized()方法</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="comment">//省略其他方法 </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>HelloServletListener</code> 继承了<code>AbstractServletListener</code> 。而<code>AbstractServletListener</code>是latke提供的默认监听器抽象类,在此类中已实现bean的查找注册等许多功能。此类也是我们稍后分析的重点,让我们先看一些它的定义。(为简洁,已去掉与ioc无关内容)</p><p>AbstractServletListener.java<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractServletListener</span> <span class="keyword">implements</span> <span class="title">ServletContextListener</span>, <span class="title">ServletRequestListener</span>, <span class="title">HttpSessionListener</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Servlet context.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ServletContext servletContext;</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">contextInitialized</span><span class="params">(<span class="keyword">final</span> ServletContextEvent servletContextEvent)</span> </span>{</span><br><span class="line"> <span class="comment">//省略其他配置项</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">final</span> Collection<Class<?>> beanClasses = Discoverer.discover(Latkes.getScanPath()); <span class="comment">//按需要加载项目中的类文件</span></span><br><span class="line"></span><br><span class="line"> Lifecycle.startApplication(beanClasses); <span class="comment">// 创建注册bean</span></span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Exception e) {</span><br><span class="line"> <span class="comment">//异常处理</span></span><br><span class="line"> }</span><br><span class="line"> CronService.start(); <span class="comment">//定时任务</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">//其他方法</span></span><br></pre></td></tr></table></figure></p><p>可以知道,<code>HelloServletListener</code> 通过继承<code>AbstractServletListener</code> 实现了<code>ServletContextListener</code>这个监听器,可以监听servlet容器创建销毁事件。当项目启动时,contextInitialized()方法执行,bean就可以被加载及初始化。从而为servlet部分的请求服务。</p><blockquote><p>须知:<code>HelloServletListener</code>还实现了<code>ServletRequestListener,ServletRequestListener</code>两个监听器,用于监听request、session变化,不过这里不是重点。</p></blockquote><h2 id="二、AbstractServletListener"><a href="#二、AbstractServletListener" class="headerlink" title="二、AbstractServletListener"></a><strong>二、AbstractServletListener</strong></h2><p>我们已经知道,此类的<code>contextInitialized</code> 方法在项目启动时执行,完成<code>bean</code>的创建工作。</p><p>具体的执行逻辑分为两步,对应方法内的两行代码</p><ol><li><p><code>Discoverer.discover(Latkes.getScanPath())</code> </p><p>扫描这些指定的类,选出其中包含特定注解的类(@RequestProcessor、@Service等),并加载这些特定类。</p></li><li><p><code>Lifecycle.startApplication(beanClasses);</code> </p><p>beanClasses为上步得到的类,解析这些类依赖并创建为bean。</p></li></ol><h3 id="discover方法"><a href="#discover方法" class="headerlink" title="discover方法"></a><strong>discover方法</strong></h3><p>discover接受字符串形式的类路径,一般是项目的根目录包,也好理解,扫描就要扫描整个项目,把能注册的bean的类先找到再说。如demo项目中,<code>HelloServletListener</code>传的就是<code>org.b3log.latke.demo.hello</code>。</p><p>在discover方法中,要知道:</p><ol><li>discover的主要任务是找到特定类并加载它们。</li><li>除传入的类路径外,discover还要扫描并处理一些内置的包,如<code>org.b3log.latke.remote</code> 这个包。</li><li>discover使用了javassist类库中的ClassFile,javassist是一个用来处理 Java 字节码的类库。详细请参考<a href="http://www.jianshu.com/p/7803ffcc81c8" target="_blank" rel="noopener">javassist使用指南</a></li></ol><p>具体的解析过程较为复杂繁琐。这里只看看大致流程的代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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"><span class="keyword">public</span> <span class="keyword">static</span> Collection<Class<?>> discover(<span class="keyword">final</span> String scanPath) <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="comment">//将传入类包与内置包组合</span></span><br><span class="line"> <span class="keyword">final</span> String[] paths = ArrayUtils.concatenate(splitPaths, BUILT_IN_COMPONENT_PKGS);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//遍历路径,将其转为磁盘绝对路径,便于javassist解析</span></span><br><span class="line"> <span class="keyword">for</span> (String path : paths) {</span><br><span class="line"> <span class="keyword">if</span> (!AntPathMatcher.isPattern(path)) {</span><br><span class="line"> path = path.replaceAll(<span class="string">"\\."</span>, <span class="string">"/"</span>) + <span class="string">"/**/*.class"</span>;</span><br><span class="line"> }</span><br><span class="line"> urls.addAll(ClassPathResolver.getResources(path));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (URL url : urls) {</span><br><span class="line"> <span class="comment">//javassist读取class文件,并将文件中类注解信息解析出来</span></span><br><span class="line"> <span class="comment">//遍历一个类下所有注解</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">final</span> Annotation annotation : annotations){</span><br><span class="line"> <span class="comment">//包含`@RequestProcessor,Service,Repository,Named`注解将maybeBeanClass设置true </span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (maybeBeanClass) { </span><br><span class="line"> clz = Thread.currentThread().getContextClassLoader().loadClass(className); <span class="comment">//符合条件,加载此类</span></span><br><span class="line"> ret.add(clz);</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> <span class="keyword">return</span> ret; <span class="comment">//返回Class<?>类型集合信息</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>###<strong>startApplication方法</strong></p><p>上步中,已经加载了这些bean类,下面就该解析这些bean的依赖关系并放在所谓的bean容器中以便于管理了。</p><p>startApplication的代码不多,我们来看看</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">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">startApplication</span><span class="params">(<span class="keyword">final</span> Collection<Class<?>> classes, <span class="keyword">final</span> BeanModule... beanModule)</span> </span>{</span><br><span class="line"> </span><br><span class="line"> beanManager = LatkeBeanManagerImpl.getInstance(); <span class="comment">//获得beanManager实例</span></span><br><span class="line"></span><br><span class="line"> applicationContext.setActive(<span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line"> beanManager.addContext(applicationContext); <span class="comment">//添加上下文</span></span><br><span class="line"> <span class="keyword">final</span> Configurator configurator = beanManager.getConfigurator();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != classes && !classes.isEmpty()) {</span><br><span class="line"> configurator.createBeans(classes); <span class="comment">//classes为discover()的解析值,据此创建bean</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//设置beanModule</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>其中最为关键的是<code>configurator.createBeans(classes);</code>这行代码,查看<code>ConfiguratorImpl</code>的<code>createBeans()</code>方法,为遍历调用<code>createBean()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <T> <span class="function">LatkeBean<T> <span class="title">createBean</span><span class="params">(<span class="keyword">final</span> Class<T> beanClass)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> (LatkeBean<T>) beanManager.getBean(beanClass); <span class="comment">//若存在直接返回bean</span></span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Exception e) {</span><br><span class="line"> LOGGER.log(Level.TRACE, <span class="string">"Not found bean [beanClass={0}], so to create it"</span>, beanClass);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!Beans.checkClass(beanClass)) { <span class="comment">//检查是否是符合条件的bean,不能是接口或抽象类</span></span><br><span class="line"> <span class="comment">//抛出异常</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">final</span> String name = Beans.getBeanName(beanClass);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == name) {</span><br><span class="line"> <span class="comment">//打印日志,返回</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//获取此bean@Qualifier注解信息</span></span><br><span class="line"> <span class="keyword">final</span> Set<Annotation> qualifiers = Beans.getQualifiers(beanClass, name); </span><br><span class="line"> <span class="comment">//获取此bean@Scope注解信息 </span></span><br><span class="line"> <span class="keyword">final</span> Class<? extends Annotation> scope = Beans.getScope(beanClass);</span><br><span class="line"> <span class="comment">//获取此bean父类及实现的接口信息</span></span><br><span class="line"> <span class="keyword">final</span> Set<Type> beanTypes = Beans.getBeanTypes(beanClass);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">final</span> Set<Class<? extends Annotation>> stereotypes = Beans.getStereotypes(beanClass);</span><br><span class="line"> <span class="comment">//创建bean实例,此为真正创建的方法</span></span><br><span class="line"> <span class="keyword">final</span> LatkeBean<T> ret = <span class="keyword">new</span> BeanImpl<T>(beanManager, name, scope, qualifiers, beanClass, beanTypes, stereotypes);</span><br><span class="line"></span><br><span class="line"> beanManager.addBean(ret); <span class="comment">//将bean添加到容器中</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">final</span> Type beanType : beanTypes) {</span><br><span class="line"> addTypeClassBinding(beanType, beanClass);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">final</span> Annotation qualifier : qualifiers) {</span><br><span class="line"> addClassQualifierBinding(beanClass, qualifier);</span><br><span class="line"> addQualifierClassBinding(qualifier, beanClass);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="三、深入bean创建过程"><a href="#三、深入bean创建过程" class="headerlink" title="三、深入bean创建过程"></a><strong>三、深入bean创建过程</strong></h2><p>BeanImpl的构造方法是创建bean的关键,来看看</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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"><span class="function"><span class="keyword">public</span> <span class="title">BeanImpl</span><span class="params">(<span class="keyword">final</span> LatkeBeanManager beanManager, <span class="keyword">final</span> String name, <span class="keyword">final</span> Class<? extends Annotation> scope,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">final</span> Set<Annotation> qualifiers, <span class="keyword">final</span> Class<T> beanClass, <span class="keyword">final</span> Set<Type> types,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">final</span> Set<Class<? extends Annotation>> stereotypes)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.beanManager = beanManager;</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.scope = scope;</span><br><span class="line"> <span class="keyword">this</span>.qualifiers = qualifiers;</span><br><span class="line"> <span class="keyword">this</span>.beanClass = beanClass;</span><br><span class="line"> <span class="keyword">this</span>.types = types;</span><br><span class="line"> <span class="keyword">this</span>.stereotypes = stereotypes;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.configurator = beanManager.getConfigurator();</span><br><span class="line"></span><br><span class="line"> javassistMethodHandler = <span class="keyword">new</span> JavassistMethodHandler(beanManager);</span><br><span class="line"> <span class="keyword">final</span> ProxyFactory proxyFactory = <span class="keyword">new</span> ProxyFactory();</span><br><span class="line"></span><br><span class="line"> proxyFactory.setSuperclass(beanClass);</span><br><span class="line"> proxyFactory.setFilter(javassistMethodHandler.getMethodFilter());</span><br><span class="line"> proxyClass = proxyFactory.createClass(); <span class="comment">//得到该bean的Class对象</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// ① 查看这个bean在构造器、方法、字段上的有无@Inject注解,有则存起来</span></span><br><span class="line"> annotatedType = <span class="keyword">new</span> AnnotatedTypeImpl<T>(beanClass); </span><br><span class="line"></span><br><span class="line"> constructorParameterInjectionPoints = <span class="keyword">new</span> HashMap<AnnotatedConstructor<T>, List<ParameterInjectionPoint>>();</span><br><span class="line"> constructorParameterProviders = <span class="keyword">new</span> ArrayList<ParameterProvider<?>>();</span><br><span class="line"> methodParameterInjectionPoints = <span class="keyword">new</span> HashMap<AnnotatedMethod<?>, List<ParameterInjectionPoint>>();</span><br><span class="line"> methodParameterProviders = <span class="keyword">new</span> HashMap<AnnotatedMethod<?>, List<ParameterProvider<?>>>();</span><br><span class="line"> fieldInjectionPoints = <span class="keyword">new</span> HashSet<FieldInjectionPoint>();</span><br><span class="line"> fieldProviders = <span class="keyword">new</span> HashSet<FieldProvider<?>>();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//根据①中结果,处理依赖</span></span><br><span class="line"> initFieldInjectionPoints(); </span><br><span class="line"> initConstructorInjectionPoints(); </span><br><span class="line"> initMethodInjectionPoints(); </span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>主要有两个过程,一是通过<code>javassist</code>得到这个bean的Class对象,以便后续操作。再者初始化该类依赖。上面注释了流程。我们来看看怎样初始化字段上的@Inject注解</p><p>BeanImpl.java<br><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">private</span> <span class="keyword">void</span> <span class="title">initFieldInjectionPoints</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//此bean中含有@Inject注解的set集合</span></span><br><span class="line"> <span class="keyword">final</span> Set<AnnotatedField<? <span class="keyword">super</span> T>> annotatedFields = annotatedType.getFields();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">final</span> AnnotatedField<? <span class="keyword">super</span> T> annotatedField : annotatedFields) {</span><br><span class="line"> <span class="keyword">final</span> Field field = annotatedField.getJavaMember();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (field.getType().equals(Provider.class)) { <span class="comment">// by provider</span></span><br><span class="line"> <span class="keyword">final</span> FieldProvider<T> provider = <span class="keyword">new</span> FieldProvider<T>(beanManager, annotatedField);</span><br><span class="line"></span><br><span class="line"> fieldProviders.add(provider);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> FieldInjectionPoint fieldInjectionPoint = <span class="keyword">new</span> FieldInjectionPoint(<span class="keyword">this</span>, annotatedField);</span><br><span class="line"></span><br><span class="line"> fieldInjectionPoints.add(fieldInjectionPoint);</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// 字段类型不是Provider走这个流程</span></span><br><span class="line"> <span class="comment">//构造一个FieldInjectionPoint对象,加入此bean的fieldInjectionPoints中</span></span><br><span class="line"> <span class="keyword">final</span> FieldInjectionPoint fieldInjectionPoint = <span class="keyword">new</span> FieldInjectionPoint(<span class="keyword">this</span>, annotatedField);</span><br><span class="line"></span><br><span class="line"> fieldInjectionPoints.add(fieldInjectionPoint);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>至此,bean的属性基本已构造完毕,以<code>HelloProcessor</code> 这个类对应的bean,我添加了一个Service依赖,如下</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="meta">@RequestProcessor</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloProcessor</span> </span>{</span><br><span class="line"> <span class="meta">@Inject</span></span><br><span class="line"> <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Before</span>(adviceClass = HelloAdvice.class)</span><br><span class="line"> <span class="meta">@RequestProcessing</span>(value = {<span class="string">"/"</span>, <span class="string">"/index"</span>, <span class="string">"/index.*"</span>, <span class="string">"/**/ant/*/path"</span>}, method = HTTPRequestMethod.GET)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">index</span><span class="params">(<span class="keyword">final</span> HTTPRequestContext context)</span> </span>{</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>则此bean到目前分析这,其属性构造如下图</p><p><img src="http://img.blog.csdn.net/20170501134048455?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p>亮的部分<code>fieldInjectionPoints</code> 属性表示该bean字段上的依赖集合,可以看到<code>userService</code>这个依赖。至此bean就算基本创建完毕了。其他部分诸如分析依赖的工作,等下次有时间再分析吧。</p><p>参考文章:</p><ol><li><a href="http://www.jianshu.com/p/43424242846b" target="_blank" rel="noopener">Javassist 使用指南(一)</a></li><li><a href="http://www.jianshu.com/p/7803ffcc81c8" target="_blank" rel="noopener">Javassist 使用指南(三)</a></li><li><a href="https://muyinchen.gitbooks.io/spring-framework-5-0-0-m3/content/3.11-jsr-330/3.11.3-jsr-330.html" target="_blank" rel="noopener">3.11.3 JSR-330标准注解的限制</a></li><li><a href="http://blog.csdn.net/dl88250/article/details/4838803" target="_blank" rel="noopener">Java 依赖注入标准(JSR-330)简介</a></li></ol>]]></content>
<categories>
<category> latke </category>
</categories>
<tags>
<tag> java </tag>
<tag> latke </tag>
<tag> ioc </tag>
</tags>
</entry>
<entry>
<title>Servlet工作机制解析</title>
<link href="/2017-04-30-Servlet%E5%92%8CServlet%E5%AE%B9%E5%99%A8/"/>
<url>/2017-04-30-Servlet%E5%92%8CServlet%E5%AE%B9%E5%99%A8/</url>
<content type="html"><![CDATA[<h1 id="Servlet工作机制解析"><a href="#Servlet工作机制解析" class="headerlink" title="Servlet工作机制解析"></a>Servlet工作机制解析</h1><p>Servlet是java Web技术的基础,也是学习Web 框架原理绕不过去的部分。本章我们来学习学习。</p><h2 id="1-Servlet和Servlet容器"><a href="#1-Servlet和Servlet容器" class="headerlink" title="1. Servlet和Servlet容器"></a>1. Servlet和Servlet容器</h2><p>什么时servlet?从概念上来说是这样的:</p><blockquote><p>Servlet是用java编写,遵守java servlet API的一些类,原则上这些类可以响应任何类型的请求,我们一般用它来响应web方面的请求。</p></blockquote><p>说的通俗一点就是,<strong>servlet就是专门处理请求的一些类,主要作用就是接受请求,处理后返回结果。</strong></p><p>而Servlet容器又是什么呢?各个请求总要找到对应的servlet吧?每个请求总有一些状态需要管理吧?好了,servlet容器就是干这个的,它负责初始化并创建这些servlet,将请求解析成具体的类等一系列操作。比如我们常见的tomcat就是使用范围最广的servlet容器,其他的还有Jetty、jboss等。</p><h3 id="一个例子"><a href="#一个例子" class="headerlink" title="一个例子"></a>一个例子</h3><p>我们先看一段简单的servlet请求响应的代码片段</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">//HelloServlet类</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloServlet</span> <span class="keyword">extends</span> <span class="title">HttpServlet</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">void</span> <span class="title">doGet</span><span class="params">(HttpServletRequest req, HttpServletResponse resp)</span> <span class="keyword">throws</span> ServletException, IOException </span>{</span><br><span class="line"> String name = req.getParameter(<span class="string">"name"</span>);</span><br><span class="line"> resp.setContentType(<span class="string">"text/html;charset=UTF-8"</span>);</span><br><span class="line"> PrintWriter printWriter = resp.getWriter();</span><br><span class="line"> printWriter.write(<span class="string">"<html><head><head><body><h1>"</span>);</span><br><span class="line"> printWriter.write(<span class="string">"Hello "</span>+name);</span><br><span class="line"> printWriter.write(<span class="string">"</h1></body></html>"</span>);</span><br><span class="line"> printWriter.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后在web.xml注册此servlet<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="utf-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">web-app</span> <span class="attr">version</span>=<span class="string">"3.0"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns</span>=<span class="string">"http://java.sun.com/xml/ns/javaee"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://java.sun.com/xml/ns/javaee</span></span></span><br><span class="line"><span class="tag"><span class="string"> http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">display-name</span>></span>wthfeng 的mvc练习项目<span class="tag"></<span class="name">display-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>hello<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-class</span>></span>com.wthfeng.mymvc.servlet.HelloServlet<span class="tag"></<span class="name">servlet-class</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">load-on-startup</span>></span>1<span class="tag"></<span class="name">load-on-startup</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">servlet</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>hello<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/hello<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">servlet-mapping</span>></span></span><br><span class="line"><span class="tag"></<span class="name">web-app</span>></span></span><br></pre></td></tr></table></figure></p><p>启动项目,效果如下图所示</p><p><img src="http://img.blog.csdn.net/20170429162918593?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p>这样我们就写完了一个简单的servlet,有关这个例子,我们需要知道:</p><ol><li>编写Servlet必须继承<code>HttpServlet</code>或<code>GenericServlet</code>或直接实现<code>Servlet</code>接口。<code>HttpServlet</code>已经为我们封装了有关http请求的相关参数,一般情况下我们直接继承此类即可。</li><li>此servlet重写了<code>doGet</code>方法,也就是它可以响应<code>/myprojectName/hello?name=xxx</code> 的请求,并在页面打印<code>xxx</code>的内容。</li></ol><h3 id="Servlet的创建和初始化"><a href="#Servlet的创建和初始化" class="headerlink" title="Servlet的创建和初始化"></a>Servlet的创建和初始化</h3><p>好了,这些逻辑符合上面所说的,浏览器发出请求<code>/hello</code>,一个名为<code>hello</code>的 servlet被命中,接受请求处理后返回。这里我们再深入一些,为什么要把servlet的定义在web.xml中?web.xml又和servlet容器有什么关系?</p><p><strong><code>web.xml</code>是web项目的入口文件</strong>。在项目启动过程中,servlet容器(如tomcat)会读取<code>web.xml</code>并进行相应配置以初始化该项目。</p><p>具体容器启动流程如下(以tomcat为例):</p><ol><li><p>先解析tomcat路径下的conf/web.xml等web.xml文件,它们是全局web配置文件,做一些基本配置工作。如注册了default、jsp等servlet。</p></li><li><p>解析web.xml,将其各个配置项(包括servlet、filter、listener)经处理包装后设在Tomcat的Context容器中,一个 Web 应用对应一个 Context 容器。</p></li><li><p>创建servlet并初始化。<code>load-on-startup</code>大于1的servlet会在此时初始化。初始化servlet就是调用servlet的<code>init</code>方法。<strong>注意:若不设置 此 值,init() 方法只在第一次 HTTP 请求命中时才被调用。</strong>此时servlet容器就算启动了。</p></li></ol><p><strong>简而言之就是,<code>web.xml</code>是项目和servlet容器(服务器)关联的桥梁。通过在<code>web.xml</code>注册servlet、filter、listener等,使得项目具有处理特定请求的功能。</strong></p><h2 id="2-filter和listener"><a href="#2-filter和listener" class="headerlink" title="2. filter和listener"></a>2. filter和listener</h2><p>在有关web servlet的配置中,常能在<code>web.xml</code>看到filter、listener的配置。实际上,filter是过滤器,listener是监听器。这两项配置都是servlet中的重要部分。我们一一来看。</p><h3 id="fliter-过滤器"><a href="#fliter-过滤器" class="headerlink" title="fliter(过滤器)"></a>fliter(过滤器)</h3><p>filter可用于拦截请求,在请求处理前进行一些预处理工作,一般用于处理编码问题,记录日志等。Filter类需实现<code>javax.servlet.Filter</code>接口。其中有3个方法<code>init()、、dofilter()、destroy()</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><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"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyFilter</span> <span class="keyword">implements</span> <span class="title">Filter</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String param; </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">init</span><span class="params">(FilterConfig filterConfig)</span> <span class="keyword">throws</span> ServletException </span>{</span><br><span class="line"> <span class="comment">//做一些初始化操作</span></span><br><span class="line"> param = filterConfig.getInitParameter(<span class="string">"myParam"</span>);</span><br><span class="line"> System.out.println(<span class="string">"filter:"</span>+param);</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">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IOException, ServletException </span>{</span><br><span class="line"> <span class="comment">//处理请求</span></span><br><span class="line"> chain.doFilter(request,response); <span class="comment">//调用下一个过滤器</span></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="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">destroy</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//在servlet销毁后销毁</span></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>web.xml</code>中添加<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="tag"><<span class="name">filter</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-name</span>></span>myFilter<span class="tag"></<span class="name">filter-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-class</span>></span>com.wthfeng.mymvc.filter.MyFilter<span class="tag"></<span class="name">filter-class</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-name</span>></span>myParam<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-value</span>></span>myValue<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line"><span class="tag"></<span class="name">filter</span>></span></span><br><span class="line"><span class="tag"><<span class="name">filter-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-name</span>></span>myFilter<span class="tag"></<span class="name">filter-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/*<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"><span class="tag"></<span class="name">filter-mapping</span>></span></span><br></pre></td></tr></table></figure></p><p>配置后filter就会拦截所有servlet请求,需注意:</p><ol><li>所有的filter在容器启动时即初始化。</li><li>filter的调用顺序为在web.xml中的定义顺序。若多余一个会形成过滤链依次处理。</li></ol><h3 id="Listener-监听器"><a href="#Listener-监听器" class="headerlink" title="Listener(监听器)"></a>Listener(监听器)</h3><p>servlet 监听器用于监听servlet容器中事件变化,当指定事件变化时,会触发注册该事件的监听器。监听器基于观察者模式。</p><p>Servlet监听器分为3类,分别用于监听<code>ServletContent</code>(Servlet上下文)、<code>HttpSession</code>(Session),<code>HttpRequest</code>(Request)。</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></pre></td><td class="code"><pre><span class="line">ServletContextListener <span class="comment">//监听Servlet容器创建销毁</span></span><br><span class="line">ServletContextAttributeListener <span class="comment">//监听Servlet容器级别属性的添加及删除</span></span><br><span class="line"></span><br><span class="line">HttpSessionListener <span class="comment">//监听Session创建销毁</span></span><br><span class="line">HttpSessionAttributeListener <span class="comment">//监听Session属性创建删除</span></span><br><span class="line"></span><br><span class="line">ServletRequestListener <span class="comment">//监听请求创建及销毁</span></span><br><span class="line">ServletRequestAttributeListener <span class="comment">//监听请求属性变化</span></span><br></pre></td></tr></table></figure><p>如我们常见的Spring项目中的如下片段,就是Spring的监听器。其实现了<code>ServletContextListener</code>,用于监听Servlet上下文,以便在项目初始化时加载Spring的必要配置。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> <span class="tag"><<span class="name">listener</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">listener-class</span>></span>org.springframework.web.context.ContextLoaderListener<span class="tag"></<span class="name">listener-class</span>></span></span><br><span class="line"><span class="tag"></<span class="name">listener</span>></span></span><br></pre></td></tr></table></figure><h3 id="Servlet-、Filter、Listerer-执行顺序"><a href="#Servlet-、Filter、Listerer-执行顺序" class="headerlink" title="Servlet 、Filter、Listerer 执行顺序"></a>Servlet 、Filter、Listerer 执行顺序</h3><p>分别了解了servlet、filter、listener后,来确定一下他们之间的执行顺序。我们已经知道,filter在servlet之前,那listener呢?</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//开启服务器并请求</span></span><br><span class="line">listener 容器初始化开始 <span class="comment">//servletContent 监听器 contextInitialized()</span></span><br><span class="line">filter初始化 <span class="comment">//filter init()</span></span><br><span class="line">初始化servlet <span class="comment">//servlet init()</span></span><br><span class="line">listener request初始化 <span class="comment">// request 监听器requestInitialized()</span></span><br><span class="line">开始filter <span class="comment">//fiter doFilter() chain.doFilter前</span></span><br><span class="line">执行servlet <span class="comment">//servlet service()</span></span><br><span class="line">结束filter <span class="comment">//fiter doFilter() chain.doFilter后</span></span><br><span class="line">listener request销毁 <span class="comment">//request 监听器 requestInitialized()</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//关闭服务器</span></span><br><span class="line">servlet销毁</span><br><span class="line">filter销毁</span><br><span class="line">listener 容器销毁</span><br></pre></td></tr></table></figure><p>从此我们可以得出结论:</p><ol><li>对于涉及3者的部分,顺序为 listener - filter - servlet</li><li>filter和servlet的初始化部分,先filter后servlet</li><li>销毁或结束顺序为加载顺序的反序</li></ol><p>##3. 有关Servlet的注解</p><p>Servlet3后,添加了若干关于<code>servlet</code>,<code>filter</code>,<code>listener</code>的注解支持。分别为<code>@WebServlet</code>,<code>@WebFilter</code>,<code>@Webistener</code>。也就是说,可以不用<code>web.xml</code> 配置文件了。注册相关类型可直接在类上添加相应注解。</p><p>如,添加一个servlet上下文的监听器。可使用如下方式。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@WebListener</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ContentListener</span> <span class="keyword">implements</span> <span class="title">ServletContextListener</span> </span>{</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextInitialized</span><span class="params">(ServletContextEvent sce)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"listener 容器初始化开始"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">contextDestroyed</span><span class="params">(ServletContextEvent sce)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"listener 容器销毁"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>相当方便有木有。。。</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h2><p>总结来说,狭义上servlet是指<code>javax.servlet</code> 这个servlet API类库。各大服务器厂商分别实现了这个标准并推出了诸如<code>Tomcat</code>、<code>jetty</code>等众多服务器。这些服务器已经帮我们实现了处理连接、协议,并根据servlet 标准帮我们封装好了请求及响应。总的这一套,称之为servlet的工作机制。</p><p>有了这些基础,我们就可以根据写自己的web框架了,下次见。</p><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><ol><li><a href="https://www.ibm.com/developerworks/cn/java/j-lo-servlet/" target="_blank" rel="noopener">Servlet 工作原理解析</a></li><li><a href="http://www.importnew.com/17025.html" target="_blank" rel="noopener">Java Servlet工作原理问答</a></li><li><a href="http://www.importnew.com/14621.html" target="_blank" rel="noopener">Java Servlet完全教程</a></li><li><a href="http://blog.csdn.net/zs234/article/details/8832343" target="_blank" rel="noopener">Filter与Servlet的区别和联系</a></li></ol>]]></content>
<categories>
<category> servlet </category>
</categories>
<tags>
<tag> java </tag>
<tag> servlet </tag>
<tag> javaweb </tag>
</tags>
</entry>
<entry>
<title>latke源码解析(一)Servlet部分</title>
<link href="/2017-04-27-latke%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89Servlet%E9%83%A8%E5%88%86/"/>
<url>/2017-04-27-latke%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89Servlet%E9%83%A8%E5%88%86/</url>
<content type="html"><![CDATA[<p>最近研究java Web的MVC,发现一款轻量级的框架,官网描述为类似 Spring 但以 JSON 为主的 Java Web 框架。具体详情见<a href="https://github.com/b3log/latke" target="_blank" rel="noopener">latke github</a>。由于此框架的mvc部分基于Servlet且是对servlet的轻量封装,相对Spring MVC较为简单,就以此框架来学习MVC。</p><p>官网提供了一个demo,在<a href="https://github.com/b3log/latke-demo" target="_blank" rel="noopener">latke-demo github</a></p><h2 id="基于Servlet"><a href="#基于Servlet" class="headerlink" title="基于Servlet"></a><strong>基于Servlet</strong></h2><p>同Spring MVC类似,latke的web部分基于servlet,在demo项目中的web.xml中找到配置servlet的部分如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">servlet</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>DispatcherServlet<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-class</span>></span>org.b3log.latke.servlet.DispatcherServlet<span class="tag"></<span class="name">servlet-class</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">load-on-startup</span>></span>1<span class="tag"></<span class="name">load-on-startup</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet</span>></span></span><br><span class="line"><span class="tag"><<span class="name">servlet-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>DispatcherServlet<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/*<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet-mapping</span>></span></span><br></pre></td></tr></table></figure><p>可以看到,latke配置的servlet拦截了所有请求,处理请求的类同Spring MVC一样,也叫<code>DispatcherServlet</code>,所以,我们重点看<code>org.b3log.latke.servlet.DispatcherServlet</code>这个类。</p><h2 id="DispatcherServlet"><a href="#DispatcherServlet" class="headerlink" title="DispatcherServlet"></a><strong>DispatcherServlet</strong></h2><p>此类继承了<code>HttpServlet</code>。并重写了<code>init</code>和<code>service</code>方法。我们知道,<code>init</code>方法是servlet的初始化方法,在项目启动时执行。<code>service</code>是用来处理请求的方法,当有请求到来时执行。先来看<code>init</code>。</p><h3 id="init"><a href="#init" class="headerlink" title="init"></a><strong>init</strong></h3><p>init用于初始化web的资源和配置,源码如下,这里初始化了与web请求相关的几个<code>handler</code>。对应处理静态资源、注解、请求url和参数解析等。具体在service分析。</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">SYS_HANDLER.add(<span class="keyword">new</span> StaticResourceHandler(getServletContext()));</span><br><span class="line">SYS_HANDLER.add(<span class="keyword">new</span> RequestPrepareHandler());</span><br><span class="line">SYS_HANDLER.add(<span class="keyword">new</span> RequestDispatchHandler());</span><br><span class="line">SYS_HANDLER.add(<span class="keyword">new</span> ArgsHandler());</span><br><span class="line">SYS_HANDLER.add(<span class="keyword">new</span> AdviceHandler());</span><br><span class="line">SYS_HANDLER.add(<span class="keyword">new</span> MethodInvokeHandler());</span><br></pre></td></tr></table></figure><h3 id="service"><a href="#service" class="headerlink" title="service"></a><strong>service</strong></h3><p>真正处理请求的部分就是遍历上述注册的handler并依次执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">service</span><span class="params">(<span class="keyword">final</span> HttpServletRequest req, <span class="keyword">final</span> HttpServletResponse resp)</span> <span class="keyword">throws</span> ServletException, IOException </span>{</span><br><span class="line"> <span class="keyword">final</span> HTTPRequestContext httpRequestContext = <span class="keyword">new</span> HTTPRequestContext();</span><br><span class="line"> httpRequestContext.setRequest(req);</span><br><span class="line"> httpRequestContext.setResponse(resp);</span><br><span class="line"> <span class="keyword">final</span> HttpControl httpControl = <span class="keyword">new</span> HttpControl(SYS_HANDLER.iterator(), httpRequestContext);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> httpControl.nextHandler(); <span class="comment">//遍历handler并执行</span></span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Exception e) {</span><br><span class="line"> httpRequestContext.setRenderer(<span class="keyword">new</span> HTTP500Renderer(e));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> result(httpRequestContext); <span class="comment">//处理响应</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="StaticResourceHandler"><a href="#StaticResourceHandler" class="headerlink" title="StaticResourceHandler"></a>StaticResourceHandler</h4><p>第一个注册的handlar为StaticResourceHandler,用于判断是否为静态资源。若是,直接返回,若否,进入下一个handler处理。</p><p>StaticResourceHandler.handle部分代码:<br><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="keyword">if</span> (StaticResources.isStatic(request)) { <span class="comment">//判断是否静态资源</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == requestDispatcher) {</span><br><span class="line"> <span class="comment">//抛出异常,代码就不贴了</span></span><br><span class="line"> }</span><br><span class="line"> context.setRenderer(<span class="keyword">new</span> StaticFileRenderer(requestDispatcher));</span><br><span class="line"> <span class="keyword">return</span>; <span class="comment">//返回,直接返回给前台</span></span><br><span class="line"> }</span><br><span class="line">httpControl.nextHandler(); <span class="comment">//传给下个handler处理</span></span><br></pre></td></tr></table></figure></p><h3 id="RequestPrepareHandler"><a href="#RequestPrepareHandler" class="headerlink" title="RequestPrepareHandler"></a>RequestPrepareHandler</h3><p>请求预处理,目前只是加了个时间戳,略过。</p><h3 id="RequestDispatchHandler"><a href="#RequestDispatchHandler" class="headerlink" title="RequestDispatchHandler"></a>RequestDispatchHandler</h3><p>这是处理请求路径的handler,目的是找到与请求路径对应的处理方法(在项目中注解为@RequestProcessor等同于Spring MVC的@Controller,@RequestProcessing等同于@RequestMapping)。</p><p>处理过程:</p><ol><li>在RequestDispatchHandler构造器函数中,已将项目所有标注@RequestProcessing的方法连同url等信息保存于list中。</li><li>请求路径(在注解中)与上述list进行遍历对比,如发现有对应,就说明找到了处理方法。另外可在请求路径添加类似restful格式的路径参数。如<code>@RequestProcessing(value = "/a/{b}/c")</code>样式,在方法声明中需写明此参数,类似<code>void test(String b)</code>。<br>若没有找到处理请求的参数,直接404。</li></ol><p>需注意:<br>所有 <code>@RequestProcessor</code> 类已被注册为bean。这步是latke的Ioc部分完成的。暂时先不研究。</p><h3 id="ArgsHandler"><a href="#ArgsHandler" class="headerlink" title="ArgsHandler"></a>ArgsHandler</h3><p>用于处理方法参数。在上一步中得到了处理请求的方法,这里进一步处理其参数。此handler最重要的任务是提供了若干参数转化器,按先后顺序,每个参数遍历这些转化器,若匹配成功立即返回。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">registerConverters(<span class="keyword">new</span> ContextConvert());</span><br><span class="line">registerConverters(<span class="keyword">new</span> RequestConvert());</span><br><span class="line">registerConverters(<span class="keyword">new</span> ResponseConvert());</span><br><span class="line">registerConverters(<span class="keyword">new</span> RendererConvert());</span><br><span class="line">registerConverters(<span class="keyword">new</span> JSONObjectConvert());</span><br><span class="line">registerConverters(<span class="keyword">new</span> PathVariableConvert());</span><br></pre></td></tr></table></figure><p>前4个设置了<code>HTTPRequestContext</code>、<code>HttpServletRequest</code>、<code>HttpServletResponse</code> 、<code>RendererConvert</code>这4个类的值,分别对应应用上下文(latke自建类)、请求、响应、模板。到时可在参数上直接使用这些类。</p><p><code>JSONObjectConvert</code> 是将请求数据转为json。</p><p><code>PathVariableConvert</code> 匹配除上述参数类型外的其他参数类型。到这里的直接返回匹配成功。再进行相应转化。</p><h3 id="AdviceHandler"><a href="#AdviceHandler" class="headerlink" title="AdviceHandler"></a>AdviceHandler</h3><p>可以理解类似AOP的handler,主要用于处理<code>@Before</code>和<code>@After</code>这两个注解。<code>@Before</code> 用于在方法执行前做一些处理,<code>@After</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><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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handle</span><span class="params">(<span class="keyword">final</span> HTTPRequestContext context, <span class="keyword">final</span> HttpControl httpControl)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 获取在前面handler得到的匹配方法和参数信息</span></span><br><span class="line"> <span class="keyword">final</span> MatchResult result = (MatchResult) httpControl.data(RequestDispatchHandler.MATCH_RESULT);</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> <span class="keyword">final</span> Map<String, Object> args = (Map<String, Object>) httpControl.data(ArgsHandler.PREPARE_ARGS);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> Method invokeHolder = result.getProcessorInfo().getInvokeHolder();</span><br><span class="line"> <span class="keyword">final</span> Class<?> processorClass = invokeHolder.getDeclaringClass();</span><br><span class="line"> <span class="keyword">final</span> List<AbstractHTTPResponseRenderer> rendererList = result.getRendererList();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> LatkeBeanManager beanManager = Lifecycle.getBeanManager();</span><br><span class="line"> <span class="comment">//获取匹配方法上的@Before信息</span></span><br><span class="line"> <span class="keyword">final</span> List<Class<? extends BeforeRequestProcessAdvice>> beforeAdviceClassList = getBeforeList(invokeHolder, processorClass);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> BeforeRequestProcessAdvice binstance = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">//遍历执行@Before类(中的doAdvice方法)</span></span><br><span class="line"> <span class="keyword">for</span> (Class<? extends BeforeRequestProcessAdvice> clz : beforeAdviceClassList) {</span><br><span class="line"> binstance = beanManager.getReference(clz);</span><br><span class="line"> binstance.doAdvice(context, args);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> RequestReturnAdviceException re) {</span><br><span class="line"> <span class="comment">//省略异常处理</span></span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> RequestProcessAdviceException e) {</span><br><span class="line"> <span class="comment">//省略异常处理</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (AbstractHTTPResponseRenderer renderer : rendererList) {</span><br><span class="line"> renderer.preRender(context, args);</span><br><span class="line"> }</span><br><span class="line"> httpControl.nextHandler(); <span class="comment">//执行下一个handler,也就是执行真正匹配的方法体</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//下面就是找`@After`注解信息并执行</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = rendererList.size() - <span class="number">1</span>; j >= <span class="number">0</span>; j--) {</span><br><span class="line"> rendererList.get(j).postRender(context, httpControl.data(MethodInvokeHandler.INVOKE_RESULT));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> List<Class<? extends AfterRequestProcessAdvice>> afterAdviceClassList = getAfterList(invokeHolder, processorClass);</span><br><span class="line"> AfterRequestProcessAdvice instance;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Class<? extends AfterRequestProcessAdvice> clz : afterAdviceClassList) {</span><br><span class="line"> instance = beanManager.getReference(clz);</span><br><span class="line"> instance.doAdvice(context, httpControl.data(MethodInvokeHandler.INVOKE_RESULT));</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>### MethodInvokeHandler</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> Method invokeHolder = result.getProcessorInfo().getInvokeHolder(); <span class="comment">//得到方法体</span></span><br><span class="line"><span class="keyword">final</span> LatkeBeanManager beanManager = Lifecycle.getBeanManager();</span><br><span class="line"><span class="keyword">final</span> Object classHolder = beanManager.getReference(invokeHolder.getDeclaringClass()); <span class="comment">//该方法类</span></span><br><span class="line"><span class="keyword">final</span> Object ret = invokeHolder.invoke(classHolder, args.values().toArray()); <span class="comment">//执行</span></span><br></pre></td></tr></table></figure><h3 id="返回响应"><a href="#返回响应" class="headerlink" title="返回响应"></a>返回响应</h3><p>说完这几个处理器,请求基本是处理完了,下面该返回响应了。让我们回到<code>DispatcherServlet</code>中。看看最后的<code>result</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">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">result</span><span class="params">(<span class="keyword">final</span> HTTPRequestContext context)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">final</span> HttpServletResponse response = context.getResponse();</span><br><span class="line"> <span class="keyword">if</span> (response.isCommitted()) { <span class="comment">// Response sends redirect or error</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> AbstractHTTPResponseRenderer renderer = context.getRenderer(); <span class="comment">//得到响应模板</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == renderer) {</span><br><span class="line"> renderer = <span class="keyword">new</span> HTTP404Renderer();</span><br><span class="line"> }</span><br><span class="line"> renderer.render(context); <span class="comment">//返回响应</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> servlet </category>
</categories>
<tags>
<tag> java </tag>
<tag> latke </tag>
<tag> servlet </tag>
</tags>
</entry>
<entry>
<title>java注解解析</title>
<link href="/2017-04-22-java%E6%B3%A8%E8%A7%A3/"/>
<url>/2017-04-22-java%E6%B3%A8%E8%A7%A3/</url>
<content type="html"><![CDATA[<h1 id="java注解解析"><a href="#java注解解析" class="headerlink" title="java注解解析"></a>java注解解析</h1><h3 id="什么是注解"><a href="#什么是注解" class="headerlink" title="什么是注解"></a>什么是注解</h3><p>什么是注解,注解就是一种描述源码的元数据。我们可以通过注解给类、方法或字段提供额外的信息以便了解更多信息。</p><p>举个例子,java中常见的<code>@Override</code>就是一个注解。它的作用是提示由它修饰的方法是一个<strong>重写方法</strong>,如果父类没有这个方法编译器会报错。这样这个注解就给我们传达了<code>重写方法</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="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"this is my string implements"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>又如,在Spring体系中使用的注解,如<code>Service</code>、<code>Controller</code>等,告知Spring这是一个bean,并进行相应处理。</p><h3 id="怎样自定义注解"><a href="#怎样自定义注解" class="headerlink" title="怎样自定义注解"></a>怎样自定义注解</h3><p>了解了注解的含义和用途,下面就该编写自己的注解了。</p><p>在深入之前,先要知道4种元注解,这4种注解用于定义修饰其他注解。</p><ol><li><code>@Target</code> 描述注解用于哪里,如类、方法或是字段。</li><li><code>@Retention</code> 指明注解的声明周期</li><li><code>@Documented</code> 注解是否包含在javaDoc中</li><li><code>@Inherited</code> 是否允许子类继承该注解</li></ol><p><code>@Documented</code>和<code>@Inherited</code>好理解一些,下面我们看看前两个注解:</p><h4 id="Target"><a href="#Target" class="headerlink" title="@Target"></a>@Target</h4><p>表明该注解是用于修饰类还是方法或字段。取值是一个ElementType枚举类。主要有以下值:</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">ElementType.TYPE //描述类、接口或enum声明</span><br><span class="line">ElementType.FIELD //描述实例变量</span><br><span class="line">ElementType.METHOD //方法</span><br><span class="line">ElementType.PARAMETER //参数</span><br><span class="line">ElementType.CONSTRUCTOR //构造函数</span><br><span class="line">ElementType.LOCAL_VARIABLE //本地变量</span><br><span class="line">ElementType.ANNOTATION_TYPE //另一个注释</span><br><span class="line">ElementType.PACKAGE //用于记录java文件的package信息</span><br></pre></td></tr></table></figure><h4 id="Retention"><a href="#Retention" class="headerlink" title="@Retention"></a>@Retention</h4><p>定义该注解信息在什么期间有效,取值为RetentionPolicy枚举类,共有3个值:</p><blockquote><p>RetentionPolicy.SOURCE<br>在编译期就丢弃,仅存在于源码中。如<code>@Override</code>等就属于此类</p></blockquote><blockquote><p>RetentionPolicy.CLASS<br>在类加载期丢弃,即注解将存在于字节码中。<strong>默认注解为此类型</strong>。</p></blockquote><blockquote><p>RetentionPolicy.RUNTIME<br>注解信息一直保留,不会丢弃。这意味着可以反射获取到注解信息。一般自定义注解大都使用此方式。</p></blockquote><h3 id="注解定义"><a href="#注解定义" class="headerlink" title="注解定义"></a>注解定义</h3><p>注解使用以下<code>@interface</code>关键字定义:如定义一个名为DAO的注解:</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">@Target</span>(ElementType.TYPE)</span><br><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> DAO {</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由上可知,该<code>@DAO</code>用于修饰类、接口或枚举类,注解信息一直存在不会擦除。</p><p>下面再自定义一个名为<code>Hello</code>注解,用于修饰方法,可生产在javaDoc中,可通过反射拿到信息的注解。</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="meta">@Target</span>(ElementType.METHOD)</span><br><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Hello {</span><br><span class="line"> <span class="function">String <span class="title">value</span><span class="params">()</span> <span class="keyword">default</span> "Hi !"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面注解体内添加了value方法。并指定了默认值。表明该注解的一个属性,使用时可以这样:</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></pre></td><td class="code"><pre><span class="line">@Hello(value = "world")</span><br><span class="line">public String hello() {</span><br><span class="line"> return "hello";</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然也可在注解内添加多个方法,对应使用时的多个属性。这里就不提了。</p><h3 id="让注解起作用"><a href="#让注解起作用" class="headerlink" title="让注解起作用"></a>让注解起作用</h3><p>看过前面的定义和使用,你可能会疑惑,我没有定义任何逻辑,注解怎么起作用啊?是啊,我们知道,<code>@Override</code>是java的注解,它靠编译期检查父类方法就能实现,而我们自定义的注解怎么办呢?</p><p>事实上,定义注解只是编写注解的第一步。我们需要在合适的时机给她赋予逻辑。还记得前面提过,自定义的注解大都是<code>RetentionPolicy.RUNTIME</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></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">TestAnno</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Hello</span>(value = <span class="string">"world"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hello"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">callHello</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TestAnno testAnno = <span class="keyword">new</span> TestAnno();</span><br><span class="line"> Method method = testAnno.getClass().getMethod(<span class="string">"hello"</span>, <span class="keyword">null</span>);</span><br><span class="line"> Hello todo = method.getAnnotation(Hello.class); <span class="comment">//获取注解信息</span></span><br><span class="line"> String str = method.invoke(testAnno, <span class="keyword">null</span>).toString();</span><br><span class="line"> System.out.println(str + <span class="string">","</span> +todo.value() ); <span class="comment">//将注解信息用在方法中</span></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><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"> TestAnno testAnno = <span class="keyword">new</span> TestAnno();</span><br><span class="line"> testAnno.callHello();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果 :</p><blockquote><p>hello,world</p></blockquote><p>以上只是一个小小的例子,并没有使用价值。但能说明可以在运行时获取注解信息并在方法运行时相结合。其他地方也是用了反射,大同小异。</p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
</tags>
</entry>
<entry>
<title>node学习笔记</title>
<link href="/2017-03-26-node%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2017-03-26-node%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h2 id="node笔记"><a href="#node笔记" class="headerlink" title="node笔记"></a>node笔记</h2><ol><li>每个文件就是一个模块,文件路径就是模块名,每个模块都有<code>require</code>、<code>exports</code>、<code>module</code>三个预先定义好的变量。</li><li><code>require</code>函数用于在当期模块加载使用其他模块,js后缀可省略。</li></ol><p>模块路径解析规则</p><ol><li>内置模块,直接在require中写模块名,如<code>require('fs')</code>.</li><li>node_modules目录,node项目有一个专门储存模块的目录,为node_modules。如<code>require('express')</code>,需保证项目node_modules目录有express模块。</li></ol><h3 id="路由定义"><a href="#路由定义" class="headerlink" title="路由定义"></a>路由定义</h3><blockquote><p>app.METHOD(path,[callback],callback)</p></blockquote><p>METHOD是一个HTTP请求方法。path是服务器路径,callback是当路由匹配时要执行的函数。如<code>app.post('/',function(){});</code>将匹配请求根目录的post请求。</p><p>app.all()是一个特殊路由方法,会匹配所有请求。用于在某一路径加载中间件。</p><h4 id="路由路径"><a href="#路由路径" class="headerlink" title="路由路径"></a>路由路径</h4><p>可使用字符串或正则匹配</p><h4 id="路由处理函数"><a href="#路由处理函数" class="headerlink" title="路由处理函数"></a>路由处理函数</h4><p>可以为请求提供多个处理函数,只有最后一个处理函数可以返回结果(调用res方法向客户端返回响应),其他需使用next()调用其他路由。</p><p>也可直接指定路由处理函数数组处理。</p><h4 id="响应方法"><a href="#响应方法" class="headerlink" title="响应方法"></a>响应方法</h4><p>中间件是一个函数。用于对请求进行处理。如果中间件没有终结请求,则必须调用next()函数</p><ol><li>应用级中间件<br>绑定到app对象,使用app.use或app.METHOD,</li></ol> <figure class="highlight js"><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><br><span class="line"> <span class="keyword">var</span> app = express();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 没有挂载路径的中间件,应用的每个请求都会执行该中间件</span></span><br><span class="line"> app.use(<span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Time:'</span>, <span class="built_in">Date</span>.now());</span><br><span class="line"> next();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它</span></span><br><span class="line">app.use(<span class="string">'/user/:id'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Request Type:'</span>, req.method);</span><br><span class="line"> next();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求</span></span><br><span class="line">app.get(<span class="string">'/user/:id'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> res.send(<span class="string">'USER'</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p> 使用 next(‘route’)方法跳过中间件栈跳过剩余中间件。可将控制权交给下一个路由。注意: 它只对 <code>app.VERB()</code>和<code>router.VERB()</code>有用。</p><h3 id="错误处理中间件"><a href="#错误处理中间件" class="headerlink" title="错误处理中间件"></a>错误处理中间件</h3><p>错误处理中间件必须使用4个参数,否则会被识别为普通中间件。</p><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span>(<span class="params">err, req, res, next</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.error(err.stack);</span><br><span class="line"> res.status(<span class="number">500</span>).send(<span class="string">'Something broke!'</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>如果向<code>next()</code>传入参数,除‘route’外,Express会认为当前请求存在错误,会忽略所有中间件函数,去寻找异常处理的中间件。</p><p>next()和next(err) 类似于Promise.resolve()和Promise.reject()。</p><h3 id="内置中间件"><a href="#内置中间件" class="headerlink" title="内置中间件"></a>内置中间件</h3><p>express.static(root,[options])</p><p>Express唯一内置中间件。负责托管静态资源。其他中间件均为第三方中间件。</p>]]></content>
<categories>
<category> node </category>
</categories>
<tags>
<tag> node </tag>
</tags>
</entry>
<entry>
<title>Linux与Mac系统用SCP互传文件</title>
<link href="/2017-03-26-Linux%E4%B8%8EMac%E4%BA%92%E4%BC%A0%E6%96%87%E4%BB%B6/"/>
<url>/2017-03-26-Linux%E4%B8%8EMac%E4%BA%92%E4%BC%A0%E6%96%87%E4%BB%B6/</url>
<content type="html"><![CDATA[<h1 id="Linux与Mac系统用SCP互传文件"><a href="#Linux与Mac系统用SCP互传文件" class="headerlink" title="Linux与Mac系统用SCP互传文件"></a>Linux与Mac系统用SCP互传文件</h1><p>linux系统之间,或者linux与mac之间可以使用scp命令互传文件(即上传或下载)。下面就以实例介绍一下这个命令。</p><p>使用scp需要两台服务器都开启ssh服务,具体详见<a href="http://www.cnblogs.com/fengbeihong/p/3307575.html" target="_blank" rel="noopener">Linux 开启ssh服务</a></p><h3 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h3><p>scp用法如下</p><blockquote><p>基本格式:<br>scp [可选参数] file_source file_target </p><p>传输文件到其他服务器<br>scp local_file remote_username@remote_ip:remote_file </p><p>从其他服务器下载文件<br>scp remote_username@remote_ip:remote_file local_file</p></blockquote><h3 id="实例使用"><a href="#实例使用" class="headerlink" title="实例使用"></a>实例使用</h3><p>如将本地文件<code>node.md</code>传到局域网内IP为<code>192.168.0.162</code>的Linux服务器上,命令如下:</p><blockquote><p>scp node.md <a href="mailto:[email protected]" target="_blank" rel="noopener">[email protected]</a>:/home/wthfeng/</p></blockquote><p>就可将<code>node.md</code>文件传到162服务器的<code>/home/wthfeng</code>目录。如需传输文件夹,可加 <code>-r</code>选项。</p><blockquote><p>将js文件夹传到远程<br>scp -r js/ <a href="mailto:[email protected]" target="_blank" rel="noopener">[email protected]</a>:/home/wthfeng/</p></blockquote><p>再将远程的js文件夹下载下来,重命名为js2</p><blockquote><p>scp -r <a href="mailto:[email protected]" target="_blank" rel="noopener">[email protected]</a>:/home/wangtonghe/js js2/</p></blockquote><p>OK,基本使用就是这样,scp用法如下:</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">usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]</span><br><span class="line"> [-l limit] [-o ssh_option] [-P port] [-S program]</span><br><span class="line"> [[user@]host1:]file1 ... [[user@]host2:]file2</span><br></pre></td></tr></table></figure><p>如上可知,指定端口号参数为<code>-P</code> ,如ssh默认端口22禁用,可使用此参数重新指定。</p>]]></content>
<categories>
<category> linux </category>
</categories>
<tags>
<tag> linux </tag>
<tag> scp </tag>
<tag> mac </tag>
</tags>
</entry>
<entry>
<title>使用秘钥登录AWS</title>
<link href="/2017-01-04-AWS%E4%BD%BF%E7%94%A8/"/>
<url>/2017-01-04-AWS%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<h3 id="登录亚马逊服务器AWS"><a href="#登录亚马逊服务器AWS" class="headerlink" title="登录亚马逊服务器AWS"></a>登录亚马逊服务器AWS</h3><p>为更安全,亚马逊的服务器需要使用一个*.pem的秘钥文件进行登录,而不是以往的账号密码形式。</p><p>在创建服务器时,会生成一个*.pem的秘钥文件,我们利用这个文件登录服务器。</p><h4 id="使用命令行登录"><a href="#使用命令行登录" class="headerlink" title="使用命令行登录"></a>使用命令行登录</h4><p>使用命令行登录十分简单,切换到含有秘钥文件的目录中,执行:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -i xxx.pem username@IP(域名)</span><br></pre></td></tr></table></figure><p>即可登录。</p><h4 id="使用文件上传工具登录"><a href="#使用文件上传工具登录" class="headerlink" title="使用文件上传工具登录"></a>使用文件上传工具登录</h4><p>有时候,我们希望传文件给服务器,可以使用跨平台的开源软件FileZilla。</p><ol><li><p>选择<code>设置</code>-><code>SFTP</code>,点击<code>添加秘钥文件</code>,选择秘钥文件后确定。<br><img src="/2017-01-04-AWS使用/img/tag-bg.jpg" alt="添加秘钥"></p></li><li><p>打开站点管理器,添加一个站点,填写用户名、服务器地址等,密码为空,点击连接即可。<br><img src="http://img.blog.csdn.net/20170104101452210?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="链接"></p></li></ol>]]></content>
<categories>
<category> linux </category>
</categories>
<tags>
<tag> linux </tag>
</tags>
</entry>
<entry>
<title>java HashMap解析</title>
<link href="/2017-02-09-java8%20HashMap%E8%A7%A3%E6%9E%90/"/>
<url>/2017-02-09-java8%20HashMap%E8%A7%A3%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="java-HashMap解析"><a href="#java-HashMap解析" class="headerlink" title="java HashMap解析"></a>java HashMap解析</h1><p>HashMap是java中常用且相对重要的类之一。了解此类的数据结构及储存原理对我们写程序有莫大帮助。java8中又对此类底层实现进行了优化,比如引入了红黑树的结构以解决哈希碰撞。今天我们就从底层解析一下HashMap,希望对大家有所帮助。</p><h2 id="HashMap的数据结构"><a href="#HashMap的数据结构" class="headerlink" title="HashMap的数据结构"></a>HashMap的数据结构</h2><h3 id="1-HashMap整体结构"><a href="#1-HashMap整体结构" class="headerlink" title="1. HashMap整体结构"></a>1. HashMap整体结构</h3><p>Map是java中的储存键(key)、值(value)对数据结构。而HashMap即是通过key的hash值确定value的储存位置。在理想情况下,仅需要O(1)的时间就可以通过key定位到value值。不过,这里一个显著的问题是,不同的key也可能有相同的哈希值,HashMap采用<strong>数组+链表</strong>解决。</p><p><img src="http://img.blog.csdn.net/20170209082753880?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p>如图,HashMap的主结构类似于一个数组,添加值时通过key确定储存位置。每个位置是一个Node<v>(图中黑点)的数据结构,该结构可组成链表。当发生冲突时,相同hash值的键值对会组成链表。</v></p><p>这种<strong>数组+链表</strong>的组合形式大部分情况下都能有不错的性能效果,java6、7就是这样设计的。然而,在极端情况下,一组(比如经过精心设计的)键值对都发生了冲突,这时的哈希结构就会退化成一个链表,使HashMap性能急剧下降。</p><p>所以在java8中,HashMap的结构实现变为<strong>数组+链表+红黑树</strong>。如图:</p><p><img src="http://img.blog.csdn.net/20170209082825542?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p>当链表达到一定长度,会将链表转为红黑树。我们知道链表的查询时间为O(n),而红黑树的查询时间为O(logN)。当长度大到一定程度时,红黑树的优势会更加明显。</p><h3 id="2-类概览"><a href="#2-类概览" class="headerlink" title="2. 类概览"></a>2. 类概览</h3><p>在具体实现上,HashMap有许多内部类、方法及字段。下面列举一些比较重要的。</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><br><span class="line"><span class="comment">//默认Map容量</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_INITIAL_CAPACITY = <span class="number">1</span> << <span class="number">4</span>; <span class="comment">// aka 16</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="keyword">final</span> <span class="keyword">float</span> DEFAULT_LOAD_FACTOR = <span class="number">0.75f</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="keyword">final</span> <span class="keyword">int</span> TREEIFY_THRESHOLD = <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//数组,HashMap的主要储存结构</span></span><br><span class="line"><span class="keyword">transient</span> Node<K,V>[] table;</span><br><span class="line"></span><br><span class="line"><span class="comment">//节点,即HashMap的键值对的储存结构</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span><<span class="title">K</span>,<span class="title">V</span>> <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>> </span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">//红黑树节点</span></span><br><span class="line"><span class="class"><span class="title">static</span> <span class="title">final</span> <span class="title">class</span> <span class="title">TreeNode</span><<span class="title">K</span>,<span class="title">V</span>> <span class="keyword">extends</span> <span class="title">LinkedHashMap</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">//用于计算<span class="title">key</span>的哈希值</span></span><br><span class="line"><span class="class"><span class="title">static</span> <span class="title">final</span> <span class="title">int</span> <span class="title">hash</span>(<span class="title">Object</span> <span class="title">key</span>)</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">//添加新键值对</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">V</span> <span class="title">put</span>(<span class="title">K</span> <span class="title">key</span>, <span class="title">V</span> <span class="title">value</span>)</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">//删除键值对</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">boolean</span> <span class="title">remove</span>(<span class="title">Object</span> <span class="title">key</span>, <span class="title">Object</span> <span class="title">value</span>)</span></span><br></pre></td></tr></table></figure><h3 id="3-Node-lt-K-V-gt-结构"><a href="#3-Node-lt-K-V-gt-结构" class="headerlink" title="3. Node<K,V>结构"></a>3. Node<K,V>结构</h3><p>Node<K,V>是HashMap的内部类,也是其键值对的底层实现。类声明如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span><<span class="title">K</span>,<span class="title">V</span>> <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>> </span>{</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> hash;</span><br><span class="line"> <span class="keyword">final</span> K key;</span><br><span class="line"> V value;</span><br><span class="line"> Node<K,V> next; <span class="comment">//指向该链表的下一个node</span></span><br><span class="line"></span><br><span class="line"> Node(<span class="keyword">int</span> hash, K key, V value, Node<K,V> next) {}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> K <span class="title">getKey</span><span class="params">()</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">getValue</span><span class="params">()</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">toString</span><span class="params">()</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">setValue</span><span class="params">(V newValue)</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">equals</span><span class="params">(Object o)</span> </span>{}</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如此,HashMap的数组+链表结构就大致成形了,Node[]为数组,而Node又可连成链表。</p><h3 id="4-TreeNode-lt-K-V-gt-红黑树结构"><a href="#4-TreeNode-lt-K-V-gt-红黑树结构" class="headerlink" title="4. TreeNode<K,V> 红黑树结构"></a>4. TreeNode<K,V> 红黑树结构</h3><p>TreeNode<K,V> 是红黑树的结构实现,类声明如下:</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="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TreeNode</span><<span class="title">K</span>,<span class="title">V</span>> <span class="keyword">extends</span> <span class="title">LinkedHashMap</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>> </span>{</span><br><span class="line"> TreeNode<K,V> parent; <span class="comment">// red-black tree links</span></span><br><span class="line"> TreeNode<K,V> left;</span><br><span class="line"> TreeNode<K,V> right;</span><br><span class="line"> TreeNode<K,V> prev; <span class="comment">// needed to unlink next upon deletion</span></span><br><span class="line"> <span class="keyword">boolean</span> red;</span><br><span class="line"> TreeNode(<span class="keyword">int</span> hash, K key, V val, Node<K,V> next) {}</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//以下省略其他方法</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>红黑树结构包含前、后、左、右节点,以及标志是否为红黑树的字段。此结构是java8新加的。</p><h2 id="HashMap的实现"><a href="#HashMap的实现" class="headerlink" title="HashMap的实现"></a>HashMap的实现</h2><h3 id="Put的实现"><a href="#Put的实现" class="headerlink" title="Put的实现"></a>Put的实现</h3><p>某一键值对<K,V>,添加到map中。</p><p>工作流程可概括为以下几点:</p><ol><li>根据K的哈希算法确定该键值对在数组(HashMap)中的索引位置x。</li><li>若索引位置x为空,将<K,V>添加于此,结束。若x不为空,转向3</li><li>判断x处的值是否等于V,若等于,用V覆盖原值。结束。否则,转向4</li><li>在x处遍历链表,并在尾部插入<K,V>。判断链表长度是否大于<code>TREEIFY_THRESHOLD</code>,若小于,结束。若大于,将该链表转为红黑树结构,结束。</li></ol><p>下面我们结合代码详细分析一下此过程。</p><h3 id="1-通过hash值定位元素位置"><a href="#1-通过hash值定位元素位置" class="headerlink" title="1. 通过hash值定位元素位置"></a>1. 通过hash值定位元素位置</h3><p>对于通过hash定位储存的Map,哈希算法对其性能有很大影响。好的哈希算法可以尽可能避免冲突的发生,使读取效率保持在O(1),下面是HashMap的哈希过程。</p><p>为表述方面,键值对设为(“hello”,”world”)。put方法源码为</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><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> putVal(hash(key), key, value, <span class="keyword">false</span>, <span class="keyword">true</span>);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>由此可见,先对<code>hello</code>进行哈希操作。hash()源码为</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">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hash</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> h;</span><br><span class="line"> <span class="keyword">return</span> (key == <span class="keyword">null</span>) ? <span class="number">0</span> : (h = key.hashCode()) ^ (h >>> <span class="number">16</span>);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>随后,put()过程中有一步异或操作。</p><blockquote><p> i = (n - 1) & hash</p></blockquote><p>n是HashMap底层数组的长度,当n为2的次方时,<code>(n-1)&hash</code>等价于<code>hash%n</code>,可确保得到的值落在数组索引范围内。</p><p>例如,对<code>hello</code>进行哈希计算为<code>99163451</code>。进行索引计算为<code>11</code>,即(<code>hello</code>,<code>world</code>)会落在数组索引为11的位置。</p><h3 id="2-put过程"><a href="#2-put过程" class="headerlink" title="2. put过程"></a>2. put过程</h3><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">boolean</span> onlyIfAbsent,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">boolean</span> evict)</span> </span>{</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="keyword">int</span> n, i;</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> n = (tab = resize()).length; <span class="comment">//若底层数组还没有元素,先扩容</span></span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="keyword">null</span>) <span class="comment">//这就是前面提到的索引的计算,判断此位置是否有值。</span></span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="keyword">null</span>); <span class="comment">//若此位置无值,添加节点,对应步骤2</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node<K,V> e; K k;</span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> e = p; <span class="comment">//若此位置有值,且与要添加的值相等,覆盖,对应步骤3</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode) <span class="comment">//这里查看节点类型,若是TreeNode,说明已经是红黑树,调用红黑树添加节点即可。</span></span><br><span class="line"> e = ((TreeNode<K,V>)p).putTreeVal(<span class="keyword">this</span>, tab, hash, key, value);</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">//仍是链表,遍历,若发现有值相同的,覆盖,否则直接将节点加在链表最后。</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> binCount = <span class="number">0</span>; ; ++binCount) {</span><br><span class="line"> <span class="keyword">if</span> ((e = p.next) == <span class="keyword">null</span>) { <span class="comment">//若其后无值了,在后面添加要添加的节点</span></span><br><span class="line"> p.next = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line"> treeifyBin(tab, hash); <span class="comment">//判断链表长度是否足够转为红黑树</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">break</span>; <span class="comment">//若遍历过程中发现有与添加的值相同,覆盖</span></span><br><span class="line"> p = e;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) { <span class="comment">// existing mapping for key</span></span><br><span class="line"> V oldValue = e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="keyword">null</span>)</span><br><span class="line"> e.value = value;</span><br><span class="line"> afterNodeAccess(e);</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ++modCount;</span><br><span class="line"> <span class="keyword">if</span> (++size > threshold)</span><br><span class="line"> resize(); <span class="comment">//若长度超过扩容阈值,进行扩容。</span></span><br><span class="line"> afterNodeInsertion(evict);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="3-扩容"><a href="#3-扩容" class="headerlink" title="3. 扩容"></a>3. 扩容</h3><p>当初始化数组或数组大小到达一定程度时,都会引发扩容机制。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> Node<K,V>[] resize() {</span><br><span class="line"> Node<K,V>[] oldTab = table;</span><br><span class="line"> <span class="keyword">int</span> oldCap = (oldTab == <span class="keyword">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line"> <span class="keyword">int</span> oldThr = threshold;</span><br><span class="line"> <span class="keyword">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">//根据情况判断新数组大小</span></span><br><span class="line"> <span class="keyword">if</span> (oldCap > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (oldCap >= MAXIMUM_CAPACITY) { <span class="comment">//若容量已超过最大值,已无法扩容</span></span><br><span class="line"> threshold = Integer.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span> oldTab;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap << <span class="number">1</span>) < MAXIMUM_CAPACITY &&</span><br><span class="line"> oldCap >= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line"> newThr = oldThr << <span class="number">1</span>; <span class="comment">//否则,扩大为原来2倍</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (oldThr > <span class="number">0</span>) <span class="comment">// initial capacity was placed in threshold</span></span><br><span class="line"> newCap = oldThr;</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// oldCap、oldThr为0时默认为初始值</span></span><br><span class="line"> newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line"> newThr = (<span class="keyword">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (newThr == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">float</span> ft = (<span class="keyword">float</span>)newCap * loadFactor;</span><br><span class="line"> newThr = (newCap < MAXIMUM_CAPACITY && ft < (<span class="keyword">float</span>)MAXIMUM_CAPACITY ?</span><br><span class="line"> (<span class="keyword">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"> threshold = newThr;</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>({<span class="string">"rawtypes"</span>,<span class="string">"unchecked"</span>})</span><br><span class="line"> Node<K,V>[] newTab = (Node<K,V>[])<span class="keyword">new</span> Node[newCap]; <span class="comment">//构建新数组</span></span><br><span class="line"> table = newTab;</span><br><span class="line"> <span class="keyword">if</span> (oldTab != <span class="keyword">null</span>) { <span class="comment">//将旧的值移到新数组中</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < oldCap; ++j) {</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="keyword">if</span> ((e = oldTab[j]) != <span class="keyword">null</span>) {</span><br><span class="line"> oldTab[j] = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (e.next == <span class="keyword">null</span>) <span class="comment">//若该位置有值且只有一个(不是链表或红黑树)</span></span><br><span class="line"> newTab[e.hash & (newCap - <span class="number">1</span>)] = e;</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode) <span class="comment">//若是红黑树</span></span><br><span class="line"> ((TreeNode<K,V>)e).split(<span class="keyword">this</span>, newTab, j, oldCap);</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// 若是链表</span></span><br><span class="line"> Node<K,V> loHead = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</span><br><span class="line"> Node<K,V> hiHead = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</span><br><span class="line"> Node<K,V> next;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> next = e.next;</span><br><span class="line"> <span class="keyword">if</span> ((e.hash & oldCap) == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (loTail == <span class="keyword">null</span>)</span><br><span class="line"> loHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> loTail.next = e;</span><br><span class="line"> loTail = e;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (hiTail == <span class="keyword">null</span>)</span><br><span class="line"> hiHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hiTail.next = e;</span><br><span class="line"> hiTail = e;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span> ((e = next) != <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">if</span> (loTail != <span class="keyword">null</span>) {</span><br><span class="line"> loTail.next = <span class="keyword">null</span>;</span><br><span class="line"> newTab[j] = loHead;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (hiTail != <span class="keyword">null</span>) {</span><br><span class="line"> hiTail.next = <span class="keyword">null</span>;</span><br><span class="line"> newTab[j + oldCap] = hiHead;</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="keyword">return</span> newTab;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>有关红黑树及链表重新扩容的算法在下篇文章中会有介绍,HashMap扩容的大致流程如上面注解那样,需考虑当前容量及数据结构。</p><h3 id="4-java8的性能优化"><a href="#4-java8的性能优化" class="headerlink" title="4.java8的性能优化"></a>4.java8的性能优化</h3><p>HashMap经java8的优化后,解决了哈希碰撞的问题。在哈希均匀分布的情况下,java7和java8对HashMap的性能测试中表现类似,而在哈希极端分布的情况下,java8的HashMap具有明显的性能优势。所以,如果可以的话,应选用java8的HashMap。</p><p>————–全文完——————</p><p>参考文章</p><ol><li><a href="http://tech.meituan.com/java-hashmap.html" target="_blank" rel="noopener">Java 8系列之重新认识HashMap</a></li><li><a href="http://www.importnew.com/14417.html" target="_blank" rel="noopener">Java 8:HashMap的性能提升</a></li></ol>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> Map </tag>
</tags>
</entry>
<entry>
<title>java内存模型与volatile详解</title>
<link href="/2017-02-05-java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/"/>
<url>/2017-02-05-java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/</url>
<content type="html"><![CDATA[<p>由于各种硬件及操作系统的内存访问差异,java虚拟机使用java内存模型(java Memory Model,JMM)来规范java对内存的访问。这套模型在jdk 1.2中开始建立,经jdk 1.5的修订,现已逐步完善起来。</p><p>###什么是java内存模型</p><p>什么是java内存模型,为什么会有这个模型?关于这个问题,就不得不从并发的问题讲起。在多核系统中,处理器一般设置缓存来加速数据的读取,缓存大大提升了程序性能,却也带来了“缓存一致性”的新问题。比如,当多个处理器写同一块主内存时,以谁的缓存数据为准?读取、写入内存的变量需遵循怎样保证线程安全?针对这些问题,java设计了一套内存模型以用来定义程序中各个变量的访问规则。</p><p>java的内存模型采用的是共享内存的线程通信机制。<strong>线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存</strong>,本地内存存储了共享变量的副本。<br><img src="http://img.blog.csdn.net/20170204163623732?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p><code>图片来自《深入理解java虚拟机 第2版》</code></p><p>关于共享变量,可以对应为存储在堆内存的实例变量、类变量及数组元素(堆内存是线程共享的)。私有变量可对应虚拟机栈中的局部变量。事实上,他们是java内存不同层次的划分,并没有一定联系。</p><h3 id="内存间的交互操作"><a href="#内存间的交互操作" class="headerlink" title="内存间的交互操作"></a>内存间的交互操作</h3><p>要完成主内存与工作内存的交互操作,需遵守一定的规则。java内存模型定义了相当严谨而复杂的访问规则。主要有8种原子性的操作。分别是:<strong>lock(锁定)、unlock(解锁)、read(读取)、load(载入)、use(使用)、assign(赋值)、store(存储)、write(写入)</strong>。</p><p>内存交互时,必须使用以上几种操作搭配完成,且这8种操作要满足一定规则。如read和load,store和write必须成对出现;对变量实施use、store时,必须先执行assign和load操作。</p><p>幸好,这些难以记忆的规则有一个等效判定的原则,即<strong>先行发生原则。</strong></p><ol><li>程序次序规则:在一个线程中,程序控制流前面的操作先行发生于后面的操作。</li><li>监视器锁规则:一个unlock操作先行发生于对同一个锁的lock操作。</li><li>volatile变量规则:对于一个volatile变量,写操作先行发生于对这个变量的读操作。</li><li>传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,则操作A先行发生于操作C。</li></ol><h3 id="一个内存交互的例子"><a href="#一个内存交互的例子" class="headerlink" title="一个内存交互的例子"></a>一个内存交互的例子</h3><p>我们知道java的多线程通信采用共享内存的方式。线程对变量的所有操作都要在工作内存中进行,不能直接访问主内存。线程间变量传递均需主内存间接完成。</p><p><img src="http://img.blog.csdn.net/20170204173422120?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p>则,线程A要与线程B通信(比如B线程要读取A线程经操作后的值),需要:</p><ol><li>线程A修改本地内存A的值,并将其写入主内存的共享变量。</li><li>线程B到主内存读取线程A修改后的值。</li></ol><h3 id="内存模型的3个重要特征"><a href="#内存模型的3个重要特征" class="headerlink" title="内存模型的3个重要特征"></a>内存模型的3个重要特征</h3><h4 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h4><p>前面我们提到的8种原子操作都是原子性的,这样可以保证对基本数据类型的访问读写是原子性的。这里有个例外是JVM没有强制规定long、double一定是原子操作。但几乎所有的商业JVM都实现了long、double的原子操作。</p><h4 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h4><p>可见性是指,当一个线程修改了共享变量的值,其他变量能得知这个修改。</p><p>这里需要引出本文第二个关键点:volatile。volatile有两个语义。这里用其可见性语义。经volatile修饰的变量保证新值能立即同步到主内存中,每次使用前立即从主内存刷新。保证了多线程操作时变量的可见性。后面会有更详细解释。</p><p>除volatile外,synchronized和final也能实现可见性。<br>synchronized的可见性由“对一个变量执行unlock前,必须先把此变量同步回主内存”。获得。</p><p>final关键字的可见性指:被final修饰的字段在构造器中初始完成,则其他线程就能看到final的值。</p><h4 id="有序性"><a href="#有序性" class="headerlink" title="有序性"></a>有序性</h4><p>java程序本身具有的有序性可以总结为:如果在同一线程观察,所有操作都是有序的。而如果在一个线程观察另一线程,所有操作都是无序的。前部分指在单线程环境中程序的顺序性,后部分说的无序是指“指令的重排序”和“工作内存与主内存的同步延迟”。</p><h5 id="指令重排序"><a href="#指令重排序" class="headerlink" title="指令重排序"></a>指令重排序</h5><p>编译器能够自由的以优化的名义去改变指令顺序。在特定的环境下,处理器可能会次序颠倒的执行指令。是为指令的重排序。在单线程环境中,程序执行结果不会受到指令重排序的影响。</p><p>但有时,我们在多线程情况下,并不希望发生指令重排序来影响并发结果。</p><p>java提供了volatile和synchronized来保证线程之间操作的有序性。volatile含有禁止指令重排序的语义(即它的第二个语义),synchronized规定一个变量在同一时刻只允许一条线程对其lock操作,也就是说同一个锁的两个同步块只能串行进入。禁止了指令的重排序。</p><p>关于指令重排序,下文还有更多解释。</p><h3 id="volatile语义"><a href="#volatile语义" class="headerlink" title="volatile语义"></a>volatile语义</h3><p>介绍完java内存模型的3个特征,现在来详细介绍volatile及它代表的语义。</p><p>准确来说,volatile是java提供的轻量的同步机制。它有两个特性:</p><ol><li>保证修饰的变量对所有线程的可见性。</li><li>禁止指令的重排序优化。</li></ol><p>根据上面的介绍,我们对可见性及禁止重排序背后的顺序性都不陌生。下面我们来详细说明下。</p><h4 id="验证volatile具有可见性"><a href="#验证volatile具有可见性" class="headerlink" title="验证volatile具有可见性"></a>验证volatile具有可见性</h4><p>volatile变量对所有线程是立即可见的,对volatile变量的写操作都能立即反应到其他线程中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="keyword">boolean</span> flag;</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">shundown</span><span class="params">()</span></span>{</span><br><span class="line"> flag = <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doWork</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">while</span>(!flag){</span><br><span class="line"> doSomething();</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的例子即是volatile的典型应用。任一线程调用了shundown()方法,都能保证所有线程执行doWork()时doSomething()方法不执行。</p><p>假设<code>flag</code> 不是由volatile修饰,则不能保证内存可见性,当某个线程修改了<code>flag</code>的值后,其他线程不一定会马上看到或根本看不到,就会引起错误。</p><p>需注意的是,volatile变量保证可见性时,需满足以下规则:</p><ol><li>运算结果不依赖变量的当前值,或保证只有单一线程修改变量值。(如i++,运算依赖当前值,就不满足)</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><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestThread2</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">volatile</span> <span class="keyword">int</span> race = <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="keyword">static</span> <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span></span>{</span><br><span class="line"> race++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREADS_COUNT =<span class="number">20</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"> Thread[] threads = <span class="keyword">new</span> Thread[THREADS_COUNT];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i<THREADS_COUNT;i++){</span><br><span class="line"> threads[i] = <span class="keyword">new</span> Thread(()->{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j<<span class="number">1000</span>;j++){</span><br><span class="line"> increase();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> threads[i].start();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(race);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如上例,若正确并发,则最后应输出<code>20*1000=20000</code>,可结果总输出小于<code>20000</code>的结果,且每次都不相同。原因就在于volatile不能保证 <code>race++</code>的可见性。<code>race++</code> 操作实际上有<code>1.读取race的值;2.对race加1;3.修改race的值</code>3步操作,而volatile显然不能保证这些操作的原子性。</p><h3 id="volatile禁止指令重排序"><a href="#volatile禁止指令重排序" class="headerlink" title="volatile禁止指令重排序"></a>volatile禁止指令重排序</h3><p>指令重排序的语句需遵守一个规则,即as-if-serial语义:</p><p><strong>所有操作都可以为了优化而重排序,但必须保证重排序的结果和程序执行结果一致。</strong></p><p>这里给出重排序的例子</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> x = <span class="number">0</span>, y = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> a = <span class="number">0</span>, b =<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="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span>(<span class="keyword">true</span>) {</span><br><span class="line"> x = <span class="number">0</span>; y = <span class="number">0</span>;</span><br><span class="line"> a = <span class="number">0</span>; b = <span class="number">0</span>;</span><br><span class="line"> i++;</span><br><span class="line"> Thread first = <span class="keyword">new</span> Thread(()->{a = <span class="number">1</span>;x = b;});</span><br><span class="line"> Thread second = <span class="keyword">new</span> Thread(()->{b = <span class="number">1</span>;y = a;});</span><br><span class="line"> first.start();second.start();</span><br><span class="line"> first.join();second.join();</span><br><span class="line"> String result = <span class="string">"第"</span> + i + <span class="string">"次 ("</span> + x + <span class="string">","</span> + y + <span class="string">")"</span>;</span><br><span class="line"> <span class="keyword">if</span>(x == <span class="number">0</span> && y == <span class="number">0</span>) {</span><br><span class="line"> System.err.println(result);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> System.out.println(result);</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>a = 1;x = b;</code>,另一个线程执行<code>b = 1;y = a;</code>,由于a、x,b、y不存在依赖关系,所以有可能发生先执行<code>x=b</code>,然后<code>a=1</code>的指令重排序,经试验,在多次循环后出现<code>x=b;b=1;y=a;a=1;</code>的线程交替执行结果。即<code>x=0;y=0</code>。</p><p><img src="http://img.blog.csdn.net/20170205100144748?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><p> 这说明发生了指令重排序,将a,b,x,y用volatile修饰后,运行多次也没有出现重排序情况。<br> <img src="http://img.blog.csdn.net/20170205100758703?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3RoZmVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述"></p><h4 id="一个单例模式的例子"><a href="#一个单例模式的例子" class="headerlink" title="一个单例模式的例子"></a>一个单例模式的例子</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></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">SingletonTest</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> SingletonTest instance = <span class="keyword">null</span>;</span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">SingletonTest</span><span class="params">()</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SingletonTest <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(instance == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">synchronized</span> (SingletonTest.class){</span><br><span class="line"> <span class="keyword">if</span>(instance == <span class="keyword">null</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> SingletonTest(); <span class="comment">//非原子操作</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面代码大家都不陌生,可为什么<code>instance</code>一定要volatile修饰呢?这是由于<code>instance = new SingletonTest();</code>并不是一个原子操作。可分解为:</p><ol><li>memory =allocate(); //分配对象的内存空间 </li><li>ctorInstance(memory); //初始化对象 </li><li>instance =memory; //设置instance指向刚分配的内存地址 </li></ol><p>2操作依赖1操作,但3操作并不依赖2操作,也就是说,上述操作的顺序可能为1-2-3,也可能为1-3-2,若是后者,当<code>instance</code>不为空时也可能没有正确初始化对象,而导致错误。</p><p>参考</p><ol><li>《深入理解java虚拟机 第2版》</li><li><a href="http://ifeve.com/jmm-faq/" target="_blank" rel="noopener"> java内存模型FAQ</a></li><li><a href="http://www.infoq.com/cn/articles/java-memory-model-1" target="_blank" rel="noopener">深入理解Java内存模型(一)——基础</a></li><li><a href="http://tech.meituan.com/java-memory-reordering.html" target="_blank" rel="noopener">Java内存访问重排序的研究</a></li></ol>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
</tags>
</entry>
<entry>
<title>ElasticSearch笔记一:基础知识</title>
<link href="/2016-10-24-ElasticSearch%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
<url>/2016-10-24-ElasticSearch%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</url>
<content type="html"><![CDATA[<p><strong>写在前面:</strong>因工作需要涉及ES,于是有了这篇笔记摘要。本笔记适合有一定ES基础但又不特别了解的程序员。在读此文章前,您需要有下面基础</p><ol><li>ES的安装</li><li>head插件的使用</li><li>索引、类型、文档的概念</li></ol><p>参考文章</p><ol><li><a href="http://es.xiaoleilu.com/" target="_blank" rel="noopener">Elasticsearch权威指南</a></li><li>Elasticsearch服务器开发(第2版)</li></ol><p>##1. 集群与节点<br><img src="http://img.blog.csdn.net/20161024180100979" alt></p><p>一个集群可包括多个节点,如上图有3个节点,一个集群须有一个节点是主节点,图中Xorn(带星标的)为主节点。</p><p>启动一个节点后,它尝试寻找与它同集群名的主节点,找到则一起组成集群,没找到则自己成为主节点。</p><p><strong>主节点(master)</strong>任务:<strong>处理请求,管理集群。新建或删除索引、增加或移除节点等</strong>。主节点不参与文档级别的变更或搜索,</p><p>我们访问的节点负责收集各节点返回的数据,最后一起返回给客户端。</p><p>当主节点与其它节点断开,则其它节点自动选择一个新的主节点。<br><img src="http://img.blog.csdn.net/20161024182258220" alt></p><p>将刚才的主节点服务器停掉,自动从其余2个节点选出主节点,Ringer。</p><h2 id="2-分片与副本"><a href="#2-分片与副本" class="headerlink" title="2. 分片与副本"></a>2. 分片与副本</h2><p>分片的概念与索引有关。我们知道</p><ul><li>索引(index)相当于关系型数据库中的数据库.</li><li>类型(type)相当于表</li><li>文档(document)相等于一行数据</li></ul><p><strong>分片</strong>:有个问题是若索引的数据过大了,性能会有影响。所以将索引数据分成几份,每份就是一个分片。而将这些分片都放在同一个节点(服务器)上没有实际意义,性能还是上不去,所以将它们分散到各个节点(即整个集群中),当查询某些数据时,这些节点共同计算,算好了合起来再返回。这样多台节点(服务器)算比一台性能要好不少。这是分布式的意义,也是分片的原因所在。</p><p>另外还有一个概念是<strong>副本</strong>,就是分片的复制品。为了防止某个节点突然宕机导致数据丢失,加了分片的拷贝和分片一起分散在集群中。有了副本后,分片称为主分片。<strong>默认一个索引有5个分片,1个副本。</strong> <strong>主分片负责所有的写操作</strong>,如新建、删除和更新,<strong>操作完成后再将结果同步到相应的副本上。</strong></p><p><img src="http://img.blog.csdn.net/20161024182258220" alt></p><blockquote><p>以上图posts索引为例。posts索引有5个分片。0,1,2,3,4代表这5个分片。分散在Korvus和Ringer这两个节点中。图中主节点用加粗的绿框表示。其余为副本。如Korvue节点中3、4分片为主分片,Ringer节点中0、1、2分片为主分片,其余为副本。</p></blockquote><p>你也可以这样理解:<strong>索引数据太大,将索引分成了5份(分片),为防止丢失又对每份进行了备份(副本)。</strong> 这样一个索引就有了2套一样的数据,每套5份分散在集群中。</p><p><strong>当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整。</strong><br>设置分片及副本的请求</p><blockquote><p> curl -XPUT localhost:9200/newnode?pretty -d ‘{“settings”:{“number_of_shards”:3,”number_of_replicas”:1}}’</p></blockquote><p>新建一个索引,newnode,设置分片数为3,副本为1(意味着每个分片有一个副本)<br><img src="http://img.blog.csdn.net/20161024203020125" alt></p><p>图中用红笔标注的即是我们新建的索引,可以看到它与其他三个索引不太一样。因为它只有3个分片,一个副本,加起来就是6分分片的数据。这6分分散到了3个节点中。</p><h2 id="3-路由"><a href="#3-路由" class="headerlink" title="3. 路由"></a>3. 路由</h2><p>路由也是ES中的重要概念。上节提到分片,现在的问题是,当索引(即新建)或检索一个文档,ES如何知道去哪个分片查数据?索引可是由多个分片组成,每个分片还分散到各个节点上,就如上图所见。</p><p>不难想到,要想定位到文档,当我们创建文档时,必定以某种规则放到哪个分片中,取的时候再以这种规则取才可以。</p><p>这种规则就是路由的定位,具体如下:</p><blockquote><p>shard = hash(routing) % number_of_primary_shards</p></blockquote><p>公式没有难度,计算出哪个shard就放到哪个shard中。其中的routing默认是该文档的主键_id。这也印证了创建的文档会随机分到某个分片中。不过,<strong>我们可以指定routing值,让文档放到固定的分片中</strong>。</p><blockquote><p>PUT -XPUT localhost:9200/newnode/city/1?routing=beijing -d ‘{“area”:”chaoyang”}’</p></blockquote><p>我们指定了路由值为beijing,并添加了一条数据。<br>查询时需指定路由值:</p><blockquote><p> curl ‘localhost:9200/newnode/city/1?pretty&routing=beijing’</p></blockquote><p>返回:<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">"_index"</span> : <span class="string">"newnode"</span>,</span><br><span class="line"> <span class="attr">"_type"</span> : <span class="string">"city"</span>,</span><br><span class="line"> <span class="attr">"_id"</span> : <span class="string">"1"</span>,</span><br><span class="line"> <span class="attr">"_version"</span> : <span class="number">1</span>,</span><br><span class="line"> <span class="attr">"_routing"</span> : <span class="string">"beijing"</span>,</span><br><span class="line"> <span class="attr">"found"</span> : <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"_source"</span> : {</span><br><span class="line"> <span class="attr">"area"</span> : <span class="string">"chaoyang"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>当查询时我们若不指定路由则会查不到,因为不指定则用文档的_id即1做路由,而我们创建时指定的路由是beijing。</p><h2 id="4-关于索引、查询过程"><a href="#4-关于索引、查询过程" class="headerlink" title="4. 关于索引、查询过程"></a>4. 关于索引、查询过程</h2><p>本节不是讲关于索引或查询的用法,而是其所经历的过程,只有了解了原理再学用法才游刃有余。</p><ul><li><strong>索引、删除操作属于写请求,需要在主分片操作。</strong></li></ul><p>一般来说,索引(新建)文档较为简单。以新建一个文档为例:</p><ol><li>请求会发送到某一节点,该节点称为请求节点。</li><li>请求节点将请求转发到相应主分片所在节点,并在此分片完成创建文档工作。</li><li>主分片创建完成后,将请求分发至分片的副本</li><li>副本完成操作后,主分片给请求节点消息,请求节点再返回给客户端。</li></ol><p><img src="http://img.blog.csdn.net/20161024220320509" alt="这里写图片描述"></p><p>图片来自<a href="http://es.xiaoleilu.com/040_Distributed_CRUD/15_Create_index_delete.html" target="_blank" rel="noopener">elasticsearch权威指南</a>,侵删。</p><blockquote><p>replication参数指定索引操作是同步或是异步,当值为sync(也是默认值)时,同步,即当主分片和副本都完成后才返回结果。若为async时,主分片完成后即返回,不等副本完成与否。</p></blockquote><ul><li><strong>搜索过程主要分为两步:发散过程和收集过程</strong></li><li>发散过程:请求节点将搜索请求分发给各相应分片以副本(同一份分片副本发送一份即可)。</li><li>收集过程 :每个分片完成自己搜索过程,并返回自己的结果给请求节点,请求节点再处理后返回结果给客户端。</li></ul><blockquote><p>这里涉及一个问题: 分页。默认查询10条数据,而在收集过程中,是每个分片都会返回自己的前10条数据,若有5个分片,在收集过程中时已有50条数据。然后请求节点再从这50条数据查询前10条返回。</p></blockquote>]]></content>
<categories>
<category> elasticsearch </category>
</categories>
<tags>
<tag> elasticsearch </tag>
<tag> elastic </tag>
</tags>
</entry>
</search>