-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
828 lines (399 loc) · 660 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Spring源码之核心容器</title>
<link href="/2019/08/09/Spring%E6%BA%90%E7%A0%81%E4%B9%8B%E6%A0%B8%E5%BF%83%E5%AE%B9%E5%99%A8/"/>
<url>/2019/08/09/Spring%E6%BA%90%E7%A0%81%E4%B9%8B%E6%A0%B8%E5%BF%83%E5%AE%B9%E5%99%A8/</url>
<content type="html"><![CDATA[<h1 id="Spring核心容器体系"><a href="#Spring核心容器体系" class="headerlink" title="Spring核心容器体系"></a>Spring核心容器体系</h1><h2 id="BeanFactory"><a href="#BeanFactory" class="headerlink" title="BeanFactory"></a>BeanFactory</h2><p>SpringBean的创建是典型的工厂模式,这一系列的Bean工厂,也即IOC容器为开发者管理对象间的依赖关系提供了很多便利和基础服务,在Spring中有许多的IOC容器的实现供用户选择和使用,其相互关系如下:<br><img src="/img/post-img/19-8-9-1.png" alt="BeanFactory继承体系"></p><p>其中BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory有三个子类:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是最终的默认实现类是DefaultListableBeanFactory,他实现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有他使用的场合,它主要是为了区分在Spring内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。例如ListableBeanFactory接口表示这些Bean是可列表的,而HierarchicalBeanFactory表示的是这些Bean是有继承关系的,也就是每个Bean有可能有父Bean。AutowireCapableBeanFactory接口定义Bean的自动装配规则。这三个接口共同定义了Bean的集合、Bean之间的关系、以及Bean的行为。</p><p>BeanFactory源码如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BeanFactory</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,</span> <span class="token comment" spellcheck="true">//如果需要得到工厂本身,需要转义</span> String FACTORY_BEAN_PREFIX <span class="token operator">=</span> <span class="token string">"&"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//根据bean的名字,获取在IOC容器中得到bean实例</span> Object <span class="token function">getBean</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。</span> <span class="token operator"><</span>T<span class="token operator">></span> T <span class="token function">getBean</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> <span class="token annotation punctuation">@Nullable</span> Class<span class="token operator"><</span>T<span class="token operator">></span> requiredType<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException<span class="token punctuation">;</span> Object <span class="token function">getBean</span><span class="token punctuation">(</span>String name<span class="token punctuation">.</span> Object<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>args<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException<span class="token punctuation">;</span> <span class="token operator"><</span>T<span class="token operator">></span> T <span class="token function">getBean</span><span class="token punctuation">(</span>Class<span class="token operator"><</span>T<span class="token operator">></span> requiredType<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException<span class="token punctuation">;</span> <span class="token operator"><</span>T<span class="token operator">></span> T <span class="token function">getBean</span><span class="token punctuation">(</span>Class<span class="token operator"><</span>T<span class="token operator">></span> requiredType<span class="token punctuation">,</span> Object<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//提供对bean的检索,看看是否在IOC容器有这个名字的bean</span> <span class="token keyword">boolean</span> <span class="token function">corrtainsBean</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//根据bean名字得到bean实例,并同时判断这个bean是不是单例</span> <span class="token keyword">boolean</span> <span class="token function">isSingleton</span><span class="token punctuation">(</span>String nane<span class="token punctuation">)</span> <span class="token keyword">throws</span> NoSuchBeanDefinitionException<span class="token punctuation">;</span> <span class="token keyword">boolean</span> <span class="token function">isPrototype</span><span class="token punctuation">(</span>String nane<span class="token punctuation">)</span> <span class="token keyword">throws</span> NoSuchBeanDefinitionException<span class="token punctuation">;</span> <span class="token keyword">boolean</span> <span class="token function">isTypeMatch</span><span class="token punctuation">(</span>String nane<span class="token punctuation">,</span> ResolvableType typeToMatch<span class="token punctuation">)</span> <span class="token keyword">throws</span> NoSuchBeanDefinitionException<span class="token punctuation">;</span> <span class="token keyword">boolean</span> <span class="token function">isTypeMatch</span><span class="token punctuation">(</span>String nane<span class="token punctuation">,</span> <span class="token annotation punctuation">@Nullable</span> Class<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span> typeToMatch<span class="token punctuation">)</span> <span class="token keyword">throws</span> NoSuc hBeanDefinit ionExcept ion<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//得到bean实例的Class类型</span> <span class="token annotation punctuation">@Nullable</span> Class<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span> <span class="token function">getType</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token keyword">throws</span> NoSuchBeanDefinitionException<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来</span> String<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getAliases</span><span class="token punctuation">(</span>String nane<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>在BeanFactory里只对IOC容器的基本行为作了定义,根本不关心你的Bean是如何定义怎样加载的。正如我们只关心从工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口并不关心。<br>而要知道工厂是如何产生对象的,需要看具体的IOC容器实现,Spring提供了许多IOC容器的实现。比如XmlBeanFactory,ClasspathXmlApplicationContext等。其中XmlBeanFactory就是针对最基本的IOC容器的实现,这个IOC容器可以读取XML文件定义的BeanDefinition(XML文件中对bean的描述),如果说XmlBeanFactory是容器中的屌丝,ApplicationContext应该算容器中的高帅富。ApplicationContext是Spring提供的一个高级的IOC容器,它除了能够提供IOC容器的基本功能外,还为用户提供了以下的附加服务:</p><blockquote><ol><li>支持信息源,可以实现国际化。(实现MessageSource接口)</li><li>访问资源。(实现ResourcePatternResolver接口)</li><li>支持应用事件。(实现ApplicationEventPublisher接口)</li></ol></blockquote><h2 id="BeanDefinition"><a href="#BeanDefinition" class="headerlink" title="BeanDefinition"></a>BeanDefinition</h2><p>SpringIOC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的,其继承体系如下:<br><img src="/img/post-img/19-8-9-2.png" alt="BeanDefinition继承体系"><br>Bean的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过下图中的类完成:<br><img src="/img/post-img/19-8-9-3.png" alt="配置文件解析"></p><h1 id="IOC容器的初始化"><a href="#IOC容器的初始化" class="headerlink" title="IOC容器的初始化"></a>IOC容器的初始化</h1><p>IOC容器的初始化包括BeanDefinition的<strong>Resource定位</strong>、<strong>载入</strong>和<strong>注册</strong>这三个基本的过程。以ApplicationContext为例,ApplicationContext系列容器是最常用的,因为Web项目中使用的XmlWebApplicationContext就属于这个继承体系,还有ClasspathXmlApplicationContext等,其继承体系如下图所示:<br><img src="/img/post-img/19-8-9-4.png" alt="ApplicationContext继承体系"></p><p>ApplicationContext允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于Bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring应用提供了一个共享的Bean定义环境。</p><p>以FileSystemXmlApplicationContext为例,看一下初始化流程:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token function">FileSystemXmlApplicationContext</span><span class="token punctuation">(</span>String configLocation<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>configLocation<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">FileSystemXmlApplicationContext</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> configLocations<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">(</span>configLocations<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//实际都是在调用这个构造函数</span> <span class="token keyword">public</span> <span class="token function">FileSystemXmlApplicationContext</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> configLocations<span class="token punctuation">,</span> <span class="token keyword">boolean</span> refresh<span class="token punctuation">,</span> ApplicationContext parent<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setConfigLocations</span><span class="token punctuation">(</span>configLocations<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>refresh<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">refresh</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>通过分析FileSystemXmlApplicationContext的源代码可以知道,在创建FileSystemXmlApplicationContext容器时,构造方法做以下两项重要工作:</p><ul><li>首先,调用父类容器(一直传递到AbstractApplicationContext)的构造方法(super(parent)方法)<strong>为容器设置好Bean资源加载器</strong>。<br>然后,再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法<strong>设置Bean定义资源文件的定位路径</strong>。<br>通过追踪FileSystemXmlApplicationContext的继承体系,发现其父类的父类AbstractApplicationContext中初始化IOC容器所做的主要源码如下:</li></ul><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">AbstractApplicationContext</span> <span class="token keyword">extends</span> <span class="token class-name">DefaultResourceLoader</span> <span class="token keyword">implements</span> <span class="token class-name">ConfigurableApplicationContext</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//静态初始化块,在整个容器创建过程中只执行一次</span> <span class="token keyword">static</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 为了避免应用程序在Weblogic8.1关闭时出现类加载异常加载问题,加载IOC容</span> <span class="token comment" spellcheck="true">// 器关闭事件(ContextClosedEvent)类</span> ContextClosedEvent<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//super方法一直传递到这里</span> <span class="token keyword">public</span> <span class="token function">AbstractApplicationContext</span><span class="token punctuation">(</span>ApplicationContext parent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setParent</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 第一步调用空构造,进一步调用getResourcePatternResolver方法</span> <span class="token keyword">public</span> <span class="token function">AbstractApplicationContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>resourcePatternResolver <span class="token operator">=</span> <span class="token function">getResourcePatternResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//获取一个Spring Source的加载器用于读入Spring Bean定义资源文件</span> <span class="token keyword">protected</span> ResourcePatternResolver <span class="token function">getResourcePatternResolver</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//AbstractApplicationContext 继承 DefaultResourceLoader,因此也是一个资源加载器</span> <span class="token comment" spellcheck="true">//Spring资源加载器,其getResource(String location)方法用于载入资源</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">PathMatchingResourcePatternResolver</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** 省略很多代码*/</span> <span class="token punctuation">}</span></code></pre><p>AbstractApplicationContext构造方法中调用PathMatchingResourcePatternResolver的构造方法创建Spring资源加载器:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token function">PathMatchingResourcePatternResolver</span><span class="token punctuation">(</span>ResourceLoader resourceLoader<span class="token punctuation">)</span> <span class="token punctuation">{</span> Assert<span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span>resourceLoader<span class="token punctuation">,</span> <span class="token string">"ResourceLoader must not be null"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//设置Spring的资源加载器</span> <span class="token keyword">this</span><span class="token punctuation">.</span>resourceLoader <span class="token operator">=</span> resourceLoader<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>在设置完容器的资源加载器之后,接下来FileSystemXmlApplicationContext执行setConfigLocations方法,通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位,该方法的源码如下:</p><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">//处理单个资源文件路径为一个字符串的情况</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setConfigLocation</span><span class="token punctuation">(</span>String location<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//String CONFIG_LOCATION_DELIMITERS = ", ; \t\n”;</span> <span class="token comment" spellcheck="true">//即多个资源文件路径之间用", ; \t\n”分隔,解析成数组形式</span> <span class="token function">setConfigLocations</span><span class="token punctuation">(</span>StringUtils<span class="token punctuation">.</span><span class="token function">tokenizeToStringArray</span><span class="token punctuation">(</span>location<span class="token punctuation">,</span> CONFIG_LOCATION_DELIMITERS<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//解析Bean定义资源文件的路径,处理多个资源文件字符串数组</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setConfigLocations</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> locations<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>locations <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> Assert<span class="token punctuation">.</span><span class="token function">noNullElements</span><span class="token punctuation">(</span>locations<span class="token punctuation">,</span> <span class="token string">"Config locations must not be null"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configLocations <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span>locations<span class="token punctuation">.</span>length<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> locations<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// resolvePath 同一个类中将字符串解析为路径的方法</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configLocations<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">resolvePath</span><span class="token punctuation">(</span>locations<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configLocations <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>也就是说既可以使用一个字符串来配置多个SpringBean定义资源文件,也可以使用字符串数组,即下面两种方式都是可以的:<br>ClasspathResource res=new ClasspathResource(“a.xml,b.xml,……”);多个资源文件路径之间可以是用”,;\t\n”等分隔。<br>ClasspathResource res=new ClasspathResource(newString[]{“a.xml”,”b.xml”,……});<br>至此,SpringIOC容器在初始化时将配置的Bean定义资源文件定位为Spring封装的Resource。</p><p>接下来,FileSystemXmlApplicationContext执行refresh函数,SpringIOC容器对Bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建IOC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IOC容器。refresh的作用类似于对IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。</p><p>FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()函数启动整个IOC容器对Bean定义的载入过程:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> refresh <span class="token keyword">throws</span> BeansExceptlon<span class="token punctuation">,</span> <span class="token function">IllegalStateException</span> <span class="token punctuation">(</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>startupShutdownMonitor<span class="token punctuation">)</span> <span class="token punctuation">(</span> <span class="token comment" spellcheck="true">//调用容器准备刷新的方法,获取容器的当时时间.同时给容器设置同步标识</span> <span class="token function">prepareRefresh</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从</span> <span class="token comment" spellcheck="true">//子类的refreshBeanFactory()方法启动</span> ConfigurableListableBeanFactory beanFactory <span class="token operator">=</span> <span class="token function">obtalnFreshBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//为BeanFactory配置容器特性,例如类加载器、事件处理器等</span> <span class="token function">prepareBeanFactory</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//为容器的某些于类指定特殊的BeanPost事件处理器</span> <span class="token function">postProcessBeanFactory</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//调用所有注册的 BeanFactoryPostProcessor 的 Bean</span> <span class="token function">InvokeBeanFactoryPostProcessors</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//为BeanFactory注朋BeanPost事件处理悬.</span> <span class="token comment" spellcheck="true">//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件</span> <span class="token function">registerBeanPostProcessors</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//初始化信息源,和国际化相关.</span> <span class="token function">lnitMessageSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//初始化容器事件传播器.</span> <span class="token function">InitAppllcationEventHulticaster</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//调用子类的某些特殊Bean初始化方法</span> <span class="token function">onRefresh</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//为事件传播器注明事件监听器.</span> <span class="token function">registerListeners</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//初始化所有剩余的单例Bean</span> <span class="token function">flnishBeanFactorylnitialization</span><span class="token punctuation">(</span>beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//初始化容器的生命周期事件处理器,并发布容器的生命周期事件</span> <span class="token function">finishRefresh</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">BeansException</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>logger<span class="token punctuation">.</span><span class="token function">isMamEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">wam</span><span class="token punctuation">(</span><span class="token string">"Exception encountered during context initialization - "</span> <span class="token operator">+</span> <span class="token string">"cancelling refresh attempt: "</span> <span class="token operator">+</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//x销毁已创建的Bean</span> <span class="token function">destroySeans</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 取消refresh 操作,重置容器的同步标识.</span> cancelR«<span class="token function">fresh</span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> ex<span class="token punctuation">;</span> } <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token function">resetComonCaches</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> }}</code></pre><p>refresh()方法主要为IOC容器Bean的生命周期管理提供条件,SpringIOC容器载入Bean定义资源文件从其子类容器的refreshBeanFactory()方法启动,所以整个refresh()中“ConfigurableListableBeanFactory beanFactory=obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">protected</span> ConfigurableListableBeanFactory <span class="token function">obtainFreshBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">refreshBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ConfigurableListableBeanFactory beanFactory <span class="token operator">=</span> <span class="token function">getBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>logger<span class="token punctuation">.</span><span class="token function">isDebugEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Bean factory for "</span> <span class="token operator">+</span> <span class="token function">getDisplayName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">": "</span> <span class="token operator">+</span> beanFactory<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> beanFactory<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre>]]></content>
<categories>
<category> java </category>
<category> 框架 </category>
<category> Spring </category>
</categories>
<tags>
<tag> java </tag>
<tag> Spring </tag>
<tag> 源码 </tag>
</tags>
</entry>
<entry>
<title>JVM总结</title>
<link href="/2019/08/08/JVM%E6%80%BB%E7%BB%93/"/>
<url>/2019/08/08/JVM%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>首先,java是一种跨平台语言,Java 源文件通过编译器,能够生产相应的.Class 文件,也就是字节码文件,而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码 。而这个解释器就是JVM的一部分,也就是:<br>① Java 源文件→编译器→字节码文件<br>② 字节码文件→JVM→机器码<br>每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 能够跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享。<br><img src="/img/post-img/19-8-8-1.png" alt="JVM架构图"></p><h2 id="JVM内存模型"><a href="#JVM内存模型" class="headerlink" title="JVM内存模型"></a>JVM内存模型</h2><p>JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】和 直接内存。<br><img src="/img/post-img/19-8-8-3.png" alt="JVM内存模型"><br>直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用,在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作, 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。</p><h3 id="程序计数器(线程私有)"><a href="#程序计数器(线程私有)" class="headerlink" title="程序计数器(线程私有)"></a>程序计数器(线程私有)</h3><p>一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。<br> 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。</p><h3 id="虚拟机栈(线程私有)"><a href="#虚拟机栈(线程私有)" class="headerlink" title="虚拟机栈(线程私有)"></a>虚拟机栈(线程私有)</h3><p>是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。<br>栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。</p><h3 id="本地方法区(线程私有)"><a href="#本地方法区(线程私有)" class="headerlink" title="本地方法区(线程私有)"></a>本地方法区(线程私有)</h3><p>本地方法区和虚拟机栈作用类似, 区别是虚拟机栈为执行 Java 方法服务的, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。</p><h3 id="堆(线程共享)"><a href="#堆(线程共享)" class="headerlink" title="堆(线程共享)"></a>堆(线程共享)</h3><p>是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。</p><h3 id="方法区(线程共享)"><a href="#方法区(线程共享)" class="headerlink" title="方法区(线程共享)"></a>方法区(线程共享)</h3><p>即常说的永久代, 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)。<br>运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。</p><h3 id="JAVA8与元数据"><a href="#JAVA8与元数据" class="headerlink" title="JAVA8与元数据"></a>JAVA8与元数据</h3><p>在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native<br>memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。<br> <img src="/img/post-img/19-8-8-2.png" alt="总结"></p><h2 id="垃圾回收与算法"><a href="#垃圾回收与算法" class="headerlink" title="垃圾回收与算法"></a>垃圾回收与算法</h2><p>垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。<br> <img src="/img/post-img/19-8-8-4.png" alt="垃圾回收架构"></p><h3 id="如何确定垃圾"><a href="#如何确定垃圾" class="headerlink" title="如何确定垃圾"></a>如何确定垃圾</h3><h4 id="引用计数法"><a href="#引用计数法" class="headerlink" title="引用计数法"></a>引用计数法</h4><p>引用计数算法(Reachability Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,则说明对象不太可能再被用到,那么这个对象就是可回收对象。<br>引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的”Stop-The-World”的垃圾收集机制。<br>但是引用计数法的一个很严重的问题是,无法解决对象间的循环引用问题,导致尽管不会再用到的对象,由于互相引用着彼此,导致它们的引用计数永远都不会为0,通过引用计数算法,也就永远无法通知GC收集器回收它们。</p><h4 id="可达性分析法"><a href="#可达性分析法" class="headerlink" title="可达性分析法"></a>可达性分析法</h4><p>可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为根节点(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。<br>通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些对象可以作为 GC Root。</p><blockquote><p>在 Java 语言中,可作为 GC Root 的对象包括以下4种:</p><ul><li><p><strong>虚拟机栈(栈帧中的本地变量表)中引用的对象</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">StackLocalParameter</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">StackLocalParameter</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">testGC</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> StackLocalParameter s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StackLocalParameter</span><span class="token punctuation">(</span><span class="token string">"localParameter"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> s <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>此时的 s,即为 GC Root,当s置空时,localParameter 对象也断掉了与 GC Root 的引用链,将被回收。</p></li><li><p><strong>方法区中类静态属性引用的对象</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MethodAreaStaicProperties</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> MethodAreaStaicProperties m<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MethodAreaStaicProperties</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">testGC</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> MethodAreaStaicProperties s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MethodAreaStaicProperties</span><span class="token punctuation">(</span><span class="token string">"properties"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> s<span class="token punctuation">.</span>m <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MethodAreaStaicProperties</span><span class="token punctuation">(</span><span class="token string">"parameter"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> s <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>s 为 GC Root,s 置为 null,经过 GC 后,s 所指向的 properties 对象由于无法与 GC Root 建立关系被回收。<br>而 m 作为类的静态属性,也属于 GC Root,parameter 对象依然与 GC root 建立着连接,所以此时 parameter 对象并不会被回收。</p></li><li><p><strong>方法区中常量引用的对象</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MethodAreaStaicProperties</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> MethodAreaStaicProperties m <span class="token operator">=</span> <span class="token function">MethodAreaStaicProperties</span><span class="token punctuation">(</span><span class="token string">"final"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MethodAreaStaicProperties</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">testGC</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> MethodAreaStaicProperties s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MethodAreaStaicProperties</span><span class="token punctuation">(</span><span class="token string">"staticProperties"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> s <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>m 即为方法区中的常量引用,也为 GC Root,s 置为 null 后,final 对象也不会因没有与 GC Root 建立联系而被回收。</p></li><li><p><strong>本地方法栈中 JNI(Native 方法)引用的对象</strong><br>任何 Native 接口都会使用某种本地方法栈,实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。</p></li></ul></blockquote><h3 id="怎么回收垃圾"><a href="#怎么回收垃圾" class="headerlink" title="怎么回收垃圾"></a>怎么回收垃圾</h3><p>在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器。</p><h4 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h4><p>标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。<br> <img src="/img/post-img/19-8-8-5.png" alt="标记-清除算法"><br> 标记-清除算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。</p><h4 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h4><p>复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。<br> <img src="/img/post-img/19-8-8-6.png" alt="复制算法"><br> 这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。</p><h4 id="标记整理算法"><a href="#标记整理算法" class="headerlink" title="标记整理算法"></a>标记整理算法</h4><p>标记整理算法(Mark-Compact)标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。<br> <img src="/img/post-img/19-8-8-7.png" alt="标记整理算法"><br>标记整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。</p><h4 id="分代收集"><a href="#分代收集" class="headerlink" title="分代收集"></a>分代收集</h4><p>严格来说并不是一种思想或理论,而是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老年代(Tenured/Old Generation)和新生代(Young Generation)。老年代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。</p><h5 id="新生代与复制算法"><a href="#新生代与复制算法" class="headerlink" title="新生代与复制算法"></a>新生代与复制算法</h5><p>目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要<br>回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代<br>划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用<br>Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另<br>一块 Survivor 空间中。<br> <img src="/img/post-img/19-8-8-8.png" alt="新生代与老年代"></p><p> 一次MinorGC的过程:<br><strong>1. eden、servicorFrom 复制到 ServicorTo,年龄+1</strong><br>首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域,同时把这些对象的年龄+1,默认情况下年龄到达 15 的对象会被<br>移到老年代中。(如果 ServicorTo 不够位置了就放到老年区);</p><p><strong>2. 清空 eden、servicorFrom</strong><br>然后,清空 Eden 和 ServicorFrom 中的对象;<br><strong>3. ServicorTo 和 ServicorFrom 互换</strong><br>最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom<br>区。</p><h5 id="为啥需要Survivor?"><a href="#为啥需要Survivor?" class="headerlink" title="为啥需要Survivor?"></a>为啥需要Survivor?</h5><p>不就是新生代到老年代么,直接 Eden 到 Old 不好了吗,为啥要这么复杂。想想如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。</p><p><strong>所以,Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。</strong></p><h5 id="为啥需要俩?"><a href="#为啥需要俩?" class="headerlink" title="为啥需要俩?"></a>为啥需要俩?</h5><p>设置两个 Survivor 区最大的好处就是解决<strong>内存碎片化</strong>。</p><p>我们先假设一下,Survivor 如果只有一个区域会怎样。Minor GC 执行后,Eden 区被清空了,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为 Survivor 有2个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。</p><p>这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivor space 是无碎片的。那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果 Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是<strong>经过权衡之后的最佳方案</strong>。</p><h5 id="老年代与标记整理算法"><a href="#老年代与标记整理算法" class="headerlink" title="老年代与标记整理算法"></a>老年代与标记整理算法</h5><p>老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记-整理算法。内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代:</p><blockquote><p><strong>大对象</strong><br>大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。<br><strong>长期存活对象</strong><br>虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。<br><strong>动态对象年龄</strong><br>虚拟机并不重视要求对象年龄必须到15岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的总合大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。</p></blockquote><p>这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。</p><h2 id="JAVA中的四种引用状态"><a href="#JAVA中的四种引用状态" class="headerlink" title="JAVA中的四种引用状态"></a>JAVA中的四种引用状态</h2><ul><li><strong>强引用</strong><br>在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。</li><li><strong>软引用</strong><br>软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。</li><li><strong>弱引用</strong><br>弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。</li><li><strong>虚引用</strong><br>虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。</li></ul><h2 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h2><p>Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:<br> <img src="/img/post-img/19-8-8-9.png" alt="垃圾收集器"></p><h3 id="Serial-垃圾收集器(单线程、复制算法)"><a href="#Serial-垃圾收集器(单线程、复制算法)" class="headerlink" title="Serial 垃圾收集器(单线程、复制算法)"></a>Serial 垃圾收集器(单线程、复制算法)</h3><p>Serial是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾收集器。Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此 Serial垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。</p><h3 id="ParNew-垃圾收集器(Serial-多线程)"><a href="#ParNew-垃圾收集器(Serial-多线程)" class="headerlink" title="ParNew 垃圾收集器(Serial+多线程)"></a>ParNew 垃圾收集器(Serial+多线程)</h3><p>ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。<br>ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。<br>ParNew虽然除了多线程外和Serial 收集器几乎完全一样,但是ParNew垃圾收集器是很多 java虚拟机运行在 Server 模式下新生代的默认垃圾收集器。</p><h3 id="Parallel-Scavenge-收集器(多线程复制算法、高效)"><a href="#Parallel-Scavenge-收集器(多线程复制算法、高效)" class="headerlink" title="Parallel Scavenge 收集器(多线程复制算法、高效)"></a>Parallel Scavenge 收集器(多线程复制算法、高效)</h3><p>Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。</p><h3 id="Serial-Old-收集器(单线程标记整理算法-)"><a href="#Serial-Old-收集器(单线程标记整理算法-)" class="headerlink" title="Serial Old 收集器(单线程标记整理算法 )"></a>Serial Old 收集器(单线程标记整理算法 )</h3><p>Serial Old 是 Serial 垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 模式下的java 虚拟机默认的老年代垃圾收集器。 在 Server 模式下,主要有两个用途:</p><ol><li>在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。</li><li>作为老年代中使用 CMS 收集器的后备垃圾收集方案。</li></ol><h3 id="Parallel-Old-收集器(多线程标记整理算法)"><a href="#Parallel-Old-收集器(多线程标记整理算法)" class="headerlink" title="Parallel Old 收集器(多线程标记整理算法)"></a>Parallel Old 收集器(多线程标记整理算法)</h3><p>Parallel Old 收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge和老年代 Parallel Old 收集器的搭配策略。</p><h3 id="CMS-收集器(多线程标记清除算法)"><a href="#CMS-收集器(多线程标记清除算法)" class="headerlink" title="CMS 收集器(多线程标记清除算法)"></a>CMS 收集器(多线程标记清除算法)</h3><p>Concurrent mark sweep(CMS)收集器是一种老年代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他老年代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。<br>CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下 4 个阶段:</p><ul><li>初始标记<br>只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。</li><li>并发标记<br>进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。</li><li>重新标记<br>为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。</li><li>并发清除<br>清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。</li></ul><h3 id="G1-收集器"><a href="#G1-收集器" class="headerlink" title="G1 收集器"></a>G1 收集器</h3><p>Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收集器两个最突出的改进是:</p><ol><li>基于标记-整理算法,不产生内存碎片。</li><li>可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。<br>G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。</li></ol>]]></content>
<categories>
<category> java </category>
<category> JVM </category>
</categories>
<tags>
<tag> java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>ConcurrentHashMap源码(二)</title>
<link href="/2019/08/05/ConcurrentHashMap%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<url>/2019/08/05/ConcurrentHashMap%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%8C%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="删除元素"><a href="#删除元素" class="headerlink" title="删除元素"></a>删除元素</h2><p>删除元素跟添加元素一样,都是先找到元素所在的桶,然后采用分段锁的思想锁住整个桶,再进行操作。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> V <span class="token function">remove</span><span class="token punctuation">(</span>Object key<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 调用替换节点方法</span> <span class="token keyword">return</span> <span class="token function">replaceNode</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">final</span> V <span class="token function">replaceNode</span><span class="token punctuation">(</span>Object key<span class="token punctuation">,</span> V value<span class="token punctuation">,</span> Object cv<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 计算hash</span> <span class="token keyword">int</span> hash <span class="token operator">=</span> <span class="token function">spread</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 自旋</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fh<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token punctuation">(</span>f <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&</span> hash<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果目标key所在的桶不存在或者要删除的节点不存在,跳出循环返回null</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>fh <span class="token operator">=</span> f<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> MOVED<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果正在扩容中,协助扩容</span> tab <span class="token operator">=</span> <span class="token function">helpTransfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> V oldVal <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 标记是否处理过</span> <span class="token keyword">boolean</span> validated <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 再次验证当前桶第一个元素是否被修改过</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">==</span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>fh <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// fh>=0表示是链表节点</span> validated <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历链表寻找目标节点</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> f<span class="token punctuation">,</span> pred <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> K ek<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> hash <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&&</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 找到了目标节点</span> V ev <span class="token operator">=</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 检查目标节点旧value是否等于cv</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cv <span class="token operator">==</span> null <span class="token operator">||</span> cv <span class="token operator">==</span> ev <span class="token operator">||</span> <span class="token punctuation">(</span>ev <span class="token operator">!=</span> null <span class="token operator">&&</span> cv<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ev<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> oldVal <span class="token operator">=</span> ev<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果value不为空则替换旧值</span> e<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>pred <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果前置节点不为空</span> <span class="token comment" spellcheck="true">// 删除当前节点</span> pred<span class="token punctuation">.</span>next <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token comment" spellcheck="true">// 如果前置节点为空</span> <span class="token comment" spellcheck="true">// 说明是桶中第一个元素,删除之</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> pred <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历到链表尾部还没找到元素,跳出循环</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">TreeBin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果是树节点</span> validated <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> TreeBin<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> t <span class="token operator">=</span> <span class="token punctuation">(</span>TreeBin<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> r<span class="token punctuation">,</span> p<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历树找到了目标节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>r <span class="token operator">=</span> t<span class="token punctuation">.</span>root<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>p <span class="token operator">=</span> r<span class="token punctuation">.</span><span class="token function">findTreeNode</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> V pv <span class="token operator">=</span> p<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 检查目标节点旧value是否等于cv</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cv <span class="token operator">==</span> null <span class="token operator">||</span> cv <span class="token operator">==</span> pv <span class="token operator">||</span> <span class="token punctuation">(</span>pv <span class="token operator">!=</span> null <span class="token operator">&&</span> cv<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>pv<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> oldVal <span class="token operator">=</span> pv<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果value不为空则替换旧值</span> p<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">removeTreeNode</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果value为空则删除元素</span> <span class="token comment" spellcheck="true">// 如果删除后树的元素个数较少则退化成链表</span> <span class="token comment" spellcheck="true">// t.removeTreeNode(p)这个方法返回true表示删除节点后树的元素个数较少</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> <span class="token function">untreeify</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>first<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 如果处理过,不管有没有找到元素都返回</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>validated<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果找到了元素,返回其旧值</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldVal <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果要替换的值为空,元素个数减1</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token function">addCount</span><span class="token punctuation">(</span><span class="token operator">-</span>1L<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> oldVal<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 没找到元素返回空</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>(1)计算hash;<br>(2)如果所在的桶不存在,表示没有找到目标元素,返回;<br>(3)如果正在扩容,则协助扩容完成后再进行删除操作;<br>(4)如果是以链表形式存储的,则遍历整个链表查找元素,找到之后再删除;<br>(5)如果是以树形式存储的,则遍历树查找元素,找到之后再删除;<br>(6)如果是以树形式存储的,删除元素之后树较小,则退化成链表;<br>(7)如果确实删除了元素,则整个map元素个数减1,并返回旧值;<br>(8)如果没有删除元素,则返回null;</p><h2 id="获取元素"><a href="#获取元素" class="headerlink" title="获取元素"></a>获取元素</h2><p>获取元素,根据目标key所在桶的第一个元素的不同采用不同的方式获取元素,关键点在于find()方法的重写。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> V <span class="token function">get</span><span class="token punctuation">(</span>Object key<span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">;</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e<span class="token punctuation">,</span> p<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> eh<span class="token punctuation">;</span> K ek<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 计算hash</span> <span class="token keyword">int</span> h <span class="token operator">=</span> <span class="token function">spread</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 如果元素所在的桶存在且里面有元素</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>e <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&</span> h<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果第一个元素就是要找的元素,直接返回</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>eh <span class="token operator">=</span> e<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> h<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&&</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>eh <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// hash小于0,说明是树或者正在扩容</span> <span class="token comment" spellcheck="true">// 使用find寻找元素,find的寻找方式依据Node的不同子类有不同的实现方式</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>p <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">?</span> p<span class="token punctuation">.</span>val <span class="token operator">:</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历整个链表寻找元素</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> h <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&&</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>(1)hash到元素所在的桶;<br>(2)如果桶中第一个元素就是该找的元素,直接返回;<br>(3)如果是树或者正在迁移元素,则调用各自Node子类的find()方法寻找元素;<br>(4)如果是链表,遍历整个链表寻找元素;<br>(5)获取元素没有加锁;</p><h2 id="获取元素个数"><a href="#获取元素个数" class="headerlink" title="获取元素个数"></a>获取元素个数</h2><p>元素个数的存储也是采用分段的思想,获取元素个数时需要把所有段加起来。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 调用sumCount()计算元素个数</span> <span class="token keyword">long</span> n <span class="token operator">=</span> <span class="token function">sumCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>n <span class="token operator"><</span> 0L<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token punctuation">(</span>n <span class="token operator">></span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span> <span class="token operator">?</span> Integer<span class="token punctuation">.</span>MAX_VALUE <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">final</span> <span class="token keyword">long</span> <span class="token function">sumCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 计算CounterCell所有段及baseCount的数量之和</span> CounterCell<span class="token punctuation">[</span><span class="token punctuation">]</span> as <span class="token operator">=</span> counterCells<span class="token punctuation">;</span> CounterCell a<span class="token punctuation">;</span> <span class="token keyword">long</span> sum <span class="token operator">=</span> baseCount<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>as <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> as<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>a <span class="token operator">=</span> as<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> sum <span class="token operator">+=</span> a<span class="token punctuation">.</span>value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> sum<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>(1)元素的个数依据不同的线程存在在不同的段里;<br>(2)计算CounterCell所有段及baseCount的数量之和;<br>(3)获取元素个数没有加锁;</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>(1)ConcurrentHashMap是HashMap的线程安全版本;<br>(2)ConcurrentHashMap采用(数组 + 链表 + 红黑树)的结构存储元素;<br>(3)ConcurrentHashMap相比于同样线程安全的HashTable,效率要高很多;<br>(4)ConcurrentHashMap采用的锁有 synchronized,CAS,自旋锁,分段锁,volatile等;<br>(5)ConcurrentHashMap中没有threshold和loadFactor这两个字段,而是采用sizeCtl来控制;<br>(6)sizeCtl = -1,表示正在进行初始化;<br>(7)sizeCtl = 0,默认值,表示后续在真正初始化的时候使用默认容量;<br>(8)sizeCtl > 0,在初始化之前存储的是传入的容量,在初始化或扩容后存储的是下一次的扩容门槛;<br>(9)sizeCtl = (resizeStamp << 16) + (1 + nThreads),表示正在进行扩容,高位存储扩容邮戳,低位存储扩容线程数加1;<br>(10)更新操作时如果正在进行扩容,当前线程协助扩容;<br>(11)更新操作会采用synchronized锁住当前桶的第一个元素,这是分段锁的思想;<br>(12)整个扩容过程都是通过CAS控制sizeCtl这个字段来进行的,这很关键;<br>(13)迁移完元素的桶会放置一个ForwardingNode节点,以标识该桶迁移完毕;<br>(14)元素个数的存储也是采用的分段思想,类似于LongAdder的实现;<br>(15)元素个数的更新会把不同的线程hash到不同的段上,减少资源争用;<br>(16)元素个数的更新如果还是出现多个线程同时更新一个段,则会扩容段(CounterCell);<br>(17)获取元素个数是把所有的段(包括baseCount和CounterCell)相加起来得到的;<br>(18)查询操作是不会加锁的,所以ConcurrentHashMap不是强一致性的;<br>(19)ConcurrentHashMap中不能存储key或value为null的元素;</p><h2 id="ConcurrentHashMap中有哪些值得学习的技术呢?"><a href="#ConcurrentHashMap中有哪些值得学习的技术呢?" class="headerlink" title="ConcurrentHashMap中有哪些值得学习的技术呢?"></a>ConcurrentHashMap中有哪些值得学习的技术呢?</h2><p>我认为有以下几点:<br>(1)CAS + 自旋,乐观锁的思想,减少线程上下文切换的时间;<br>(2)分段锁的思想,减少同一把锁争用带来的低效问题;<br>(3)CounterCell,分段存储元素个数,减少多线程同时更新一个字段带来的低效;<br>(4)@sun.misc.Contended(CounterCell上的注解),避免伪共享;<br>(5)多线程协同进行扩容;</p>]]></content>
<categories>
<category> java </category>
<category> java源码 </category>
<category> 并发 </category>
</categories>
<tags>
<tag> 集合 </tag>
<tag> java源码 </tag>
<tag> ConcurrentHashMap </tag>
</tags>
</entry>
<entry>
<title>ConcurrentHashMap源码(一)</title>
<link href="/2019/08/05/ConcurrentHashMap%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/2019/08/05/ConcurrentHashMap%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="添加元素(putVal)"><a href="#添加元素(putVal)" class="headerlink" title="添加元素(putVal)"></a>添加元素(putVal)</h2><pre class=" language-java"><code class="language-java"><span class="token keyword">final</span> V <span class="token function">putVal</span><span class="token punctuation">(</span>K key<span class="token punctuation">,</span> V value<span class="token punctuation">,</span> <span class="token keyword">boolean</span> onlyIfAbsent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// key和value都不能为null</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">==</span> null <span class="token operator">||</span> value <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 计算keyd的hash值</span> <span class="token keyword">int</span> hash <span class="token operator">=</span> <span class="token function">spread</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 要插入元素所在桶的元素个数,后面遍历桶时用到</span> <span class="token keyword">int</span> binCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 自旋,结合CAS使用(如果CAS失败,则会重新取整个桶进行下面的流程)</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fh<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果桶未初始化或者桶个数为0,则初始化桶</span> tab <span class="token operator">=</span> <span class="token function">initTable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>f <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&</span> hash<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果要插入元素所在的桶还没有元素,则把这个元素直接插入到这个桶中</span> <span class="token comment" spellcheck="true">// CAS保证操作的原子性,只有一个线程可以插入成功</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">casTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> null<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果使用CAS插入元素时,发现已经有元素了,则进入下一次循环,重新操作</span> <span class="token comment" spellcheck="true">// 如果使用CAS插入元素成功,则break跳出循环,流程结束</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// no lock when adding to empty bin</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>fh <span class="token operator">=</span> f<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> MOVED<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果要插入元素所在的桶的第一个元素的hash是MOVED,</span> <span class="token comment" spellcheck="true">// 则表示正在扩容,当前线程会帮忙一起迁移元素</span> tab <span class="token operator">=</span> <span class="token function">helpTransfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果这个桶不为空且不在迁移元素,则锁住这个桶(分段锁)</span> <span class="token comment" spellcheck="true">// 并查找要插入的元素是否在这个桶中</span> <span class="token comment" spellcheck="true">// 存在,则替换值(onlyIfAbsent=false)</span> <span class="token comment" spellcheck="true">// 不存在,则插入到链表结尾或插入树中</span> V oldVal <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 再次检测第一个元素是否有变化,如果有变化则进入下一次循环,从头来过</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">==</span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果第一个元素的hash值大于等于0(说明不是在迁移,也不是树)</span> <span class="token comment" spellcheck="true">// 表示桶中的元素使用的是链表方式存储</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>fh <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 桶中元素个数初始化为1</span> binCount <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历整个桶,每次结束binCount加1</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> f<span class="token punctuation">;</span><span class="token punctuation">;</span> <span class="token operator">++</span>binCount<span class="token punctuation">)</span> <span class="token punctuation">{</span> K ek<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> hash <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&&</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果这个元素已经存在,则直接覆盖(onlyIfAbsent=false)</span> <span class="token comment" spellcheck="true">// 备份旧值并将其返回,退出循环</span> oldVal <span class="token operator">=</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>onlyIfAbsent<span class="token punctuation">)</span> e<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> pred <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果到链表尾部还没有找到元素</span> <span class="token comment" spellcheck="true">// 就把它插入到链表结尾并退出循环</span> pred<span class="token punctuation">.</span>next <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 链表存储的情况到此结束,下面是红黑树的情况</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">TreeBin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果第一个元素是树节点</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 桶中元素个数初始化为2</span> binCount <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 调用红黑树的插入方法插入元素</span> <span class="token comment" spellcheck="true">// 如果没有重复节点成功插入则返回null</span> <span class="token comment" spellcheck="true">// 否则返回寻找到的重复节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>TreeBin<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">putTreeVal</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果找到了这个元素,则覆盖原值(onlyIfAbsent=false)</span> <span class="token comment" spellcheck="true">// 备份旧值并退出循环</span> oldVal <span class="token operator">=</span> p<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>onlyIfAbsent<span class="token punctuation">)</span> p<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//通过上述过程已经成功插入元素,接下来是插入后的一系列操作,包括树化和扩容</span> <span class="token comment" spellcheck="true">// 如果binCount不为0,说明成功插入了元素或者寻找到了元素</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>binCount <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果链表元素个数达到了8,则尝试树化</span> <span class="token comment" spellcheck="true">// 因为上面把元素插入到树中时,binCount只赋值了2,并没有计算整个树中元素的个数</span> <span class="token comment" spellcheck="true">// 所以不会重复树化</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>binCount <span class="token operator">>=</span> TREEIFY_THRESHOLD<span class="token punctuation">)</span> <span class="token function">treeifyBin</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 如果要插入的元素已经存在,则返回旧值</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldVal <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> oldVal<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 退出外层大循环,流程结束</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 成功插入元素,元素个数加1(是否要扩容在这个里面)</span> <span class="token function">addCount</span><span class="token punctuation">(</span>1L<span class="token punctuation">,</span> binCount<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 成功插入元素返回null</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>整体流程跟HashMap比较类似,大致是以下几步:<br>(1)如果桶数组未初始化,则初始化;<br>(2)如果待插入元素所在的桶为空,则尝试把此元素直接插入到桶的第一个位置;<br>(3)如果正在扩容,则当前线程一起加入到扩容的过程中;<br>(4)如果待插入的元素所在的桶不为空且不在迁移元素,则锁住这个桶(分段锁);<br>(5)如果当前桶中元素以链表方式存储,则在链表中寻找该元素或者插入元素;<br>(6)如果当前桶中元素以红黑树方式存储,则在红黑树中寻找该元素或者插入元素;<br>(7)如果元素存在,则返回旧值;<br>(8)如果元素不存在,整个Map的元素个数加1,并检查是否需要扩容;<br>添加元素操作中使用的锁主要有(自旋锁 + CAS + synchronized + 分段锁)。<br><strong>为什么使用synchronized而不是ReentrantLock?</strong><br>因为synchronized已经得到了极大地优化,在特定情况下并不比ReentrantLock差。</p><h3 id="初始化桶数组(initTable)"><a href="#初始化桶数组(initTable)" class="headerlink" title="初始化桶数组(initTable)"></a>初始化桶数组(initTable)</h3><p>第一次放元素时,会初始化桶数组。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">initTable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">;</span> <span class="token keyword">int</span> sc<span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> tab<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果sizeCtl<0说明正在初始化或者扩容,让出CPU</span> Thread<span class="token punctuation">.</span><span class="token function">yield</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// lost initialization race; just spin</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果成功把sizeCtl原子更新为-1,则当前线程进入初始化</span> <span class="token comment" spellcheck="true">// 如果原子更新失败则说明有其它线程先一步进入初始化了,则进入下一次循环</span> <span class="token comment" spellcheck="true">// 如果下一次循环时还没初始化完毕,则sizeCtl<0进入上面if的逻辑让出CPU</span> <span class="token comment" spellcheck="true">// 如果下一次循环更新完毕了,则table.length!=0,退出循环</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 再次检查table是否为空,防止ABA问题</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> tab<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果sc为0则使用默认值16</span> <span class="token comment" spellcheck="true">// 因为构造函数中可以指定初始化容量大小</span> <span class="token keyword">int</span> n <span class="token operator">=</span> <span class="token punctuation">(</span>sc <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> sc <span class="token operator">:</span> DEFAULT_CAPACITY<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 新建数组</span> <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">"unchecked"</span><span class="token punctuation">)</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator"><</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 赋值给table桶数组</span> table <span class="token operator">=</span> tab <span class="token operator">=</span> nt<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 设置sc为数组长度的0.75倍</span> <span class="token comment" spellcheck="true">// n - (n >>> 2) = n - n/4 = 0.75n</span> <span class="token comment" spellcheck="true">// 可见这里装载因子和扩容门槛都是写死了的</span> <span class="token comment" spellcheck="true">// 这也正是没有threshold和loadFactor属性的原因</span> sc <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 把sc赋值给sizeCtl,这时存储的是下次扩容门槛</span> sizeCtl <span class="token operator">=</span> sc<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> tab<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="协助扩容(helpTransfer)"><a href="#协助扩容(helpTransfer)" class="headerlink" title="协助扩容(helpTransfer)"></a>协助扩容(helpTransfer)</h3><p>线程添加元素时发现正在扩容且当前元素所在的桶元素已经迁移完成了,则协助迁移其它桶的元素。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">final</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">helpTransfer</span><span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTab<span class="token punctuation">;</span> <span class="token keyword">int</span> sc<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 如果桶数组不为空,并且当前桶第一个元素为ForwardingNode类型,并且nextTab不为空</span> <span class="token comment" spellcheck="true">// 说明当前桶已经迁移完毕了,才去帮忙迁移其它桶的元素</span> <span class="token comment" spellcheck="true">// 扩容时会把旧桶的第一个元素置为ForwardingNode,并让其nextTab指向新桶数组</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">!=</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">ForwardingNode</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>nextTab <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ForwardingNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">)</span><span class="token punctuation">.</span>nextTable<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// sizeCtl<0,说明正在扩容</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>nextTab <span class="token operator">==</span> nextTable <span class="token operator">&&</span> table <span class="token operator">==</span> tab <span class="token operator">&&</span> <span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">>>></span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">!=</span> rs <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> MAX_RESIZERS <span class="token operator">||</span> transferIndex <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 扩容线程数加1</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> sc <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 当前线程帮忙迁移元素</span> <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> nextTab<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> nextTab<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> table<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h4 id="迁移元素(transfer)"><a href="#迁移元素(transfer)" class="headerlink" title="迁移元素(transfer)"></a>迁移元素(transfer)</h4><p>扩容时容量变为两倍,并把部分元素迁移到其它桶中。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">transfer</span><span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTab<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">,</span> stride<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>stride <span class="token operator">=</span> <span class="token punctuation">(</span>NCPU <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">/</span> NCPU <span class="token operator">:</span> n<span class="token punctuation">)</span> <span class="token operator"><</span> MIN_TRANSFER_STRIDE<span class="token punctuation">)</span> stride <span class="token operator">=</span> MIN_TRANSFER_STRIDE<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// subdivide range</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>nextTab <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// initiating</span> <span class="token comment" spellcheck="true">// 如果nextTab为空,说明还没开始迁移</span> <span class="token comment" spellcheck="true">// 就新建一个新桶数组</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 新桶数组是原桶的两倍</span> <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">"unchecked"</span><span class="token punctuation">)</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator"><</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span>n <span class="token operator"><<</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> nextTab <span class="token operator">=</span> nt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// try to cope with OOME</span> sizeCtl <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> nextTable <span class="token operator">=</span> nextTab<span class="token punctuation">;</span> transferIndex <span class="token operator">=</span> n<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 新桶数组大小</span> <span class="token keyword">int</span> nextn <span class="token operator">=</span> nextTab<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 新建一个ForwardingNode类型的节点,并把新桶数组存储在里面</span> ForwardingNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> fwd <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ForwardingNode</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>nextTab<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">boolean</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">boolean</span> finishing <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// to ensure sweep before committing nextTab</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> bound <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">;</span> <span class="token keyword">int</span> fh<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 整个while循环就是在算i的值,过程太复杂,不用太关心</span> <span class="token comment" spellcheck="true">// i的值会从n-1依次递减,感兴趣的可以打下断点就知道了</span> <span class="token comment" spellcheck="true">// 其中n是旧桶数组的大小,也就是说i从15开始一直减到1这样去迁移元素</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>advance<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> nextIndex<span class="token punctuation">,</span> nextBound<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">--</span>i <span class="token operator">>=</span> bound <span class="token operator">||</span> finishing<span class="token punctuation">)</span> advance <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>nextIndex <span class="token operator">=</span> transferIndex<span class="token punctuation">)</span> <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> i <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> advance <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> TRANSFERINDEX<span class="token punctuation">,</span> nextIndex<span class="token punctuation">,</span> nextBound <span class="token operator">=</span> <span class="token punctuation">(</span>nextIndex <span class="token operator">></span> stride <span class="token operator">?</span> nextIndex <span class="token operator">-</span> stride <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> bound <span class="token operator">=</span> nextBound<span class="token punctuation">;</span> i <span class="token operator">=</span> nextIndex <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> advance <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator"><</span> <span class="token number">0</span> <span class="token operator">||</span> i <span class="token operator">>=</span> n <span class="token operator">||</span> i <span class="token operator">+</span> n <span class="token operator">>=</span> nextn<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果一次遍历完成了</span> <span class="token comment" spellcheck="true">// 也就是整个map所有桶中的元素都迁移完成了</span> <span class="token keyword">int</span> sc<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>finishing<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果全部迁移完成了,则替换旧桶数组</span> <span class="token comment" spellcheck="true">// 并设置下一次扩容门槛为新桶数组容量的0.75倍</span> nextTable <span class="token operator">=</span> null<span class="token punctuation">;</span> table <span class="token operator">=</span> nextTab<span class="token punctuation">;</span> sizeCtl <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator"><<</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">,</span> sc <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 当前线程扩容完成,把扩容线程数-1</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span> <span class="token operator"><<</span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 扩容完成两边肯定相等</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 把finishing设置为true</span> <span class="token comment" spellcheck="true">// finishing为true才会走到上面的if条件</span> finishing <span class="token operator">=</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// i重新赋值为n</span> <span class="token comment" spellcheck="true">// 这样会再重新遍历一次桶数组,看看是不是都迁移完成了</span> <span class="token comment" spellcheck="true">// 也就是第二次遍历都会走到下面的(fh = f.hash) == MOVED这个条件</span> i <span class="token operator">=</span> n<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// recheck before commit</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>f <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果桶中无数据,直接放入ForwardingNode标记该桶已迁移</span> advance <span class="token operator">=</span> <span class="token function">casTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> null<span class="token punctuation">,</span> fwd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>fh <span class="token operator">=</span> f<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> MOVED<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 如果桶中第一个元素的hash值为MOVED</span> <span class="token comment" spellcheck="true">// 说明它是ForwardingNode节点</span> <span class="token comment" spellcheck="true">// 也就是该桶已迁移</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// already processed</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 锁定该桶并迁移元素</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 再次判断当前桶第一个元素是否有修改</span> <span class="token comment" spellcheck="true">// 也就是可能其它线程先一步迁移了元素</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">==</span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 把一个链表分化成两个链表</span> <span class="token comment" spellcheck="true">// 规则是桶中各元素的hash与桶大小n进行与操作</span> <span class="token comment" spellcheck="true">// 等于0的放到低位链表(low)中,不等于0的放到高位链表(high)中</span> <span class="token comment" spellcheck="true">// 其中低位链表迁移到新桶中的位置相对旧桶不变</span> <span class="token comment" spellcheck="true">// 高位链表迁移到新桶中位置正好是其在旧桶的位置加n</span> <span class="token comment" spellcheck="true">// 这也正是为什么扩容时容量在变成两倍的原因</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> ln<span class="token punctuation">,</span> hn<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>fh <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 第一个元素的hash值大于等于0</span> <span class="token comment" spellcheck="true">// 说明该桶中元素是以链表形式存储的</span> <span class="token comment" spellcheck="true">// 这里与HashMap迁移算法基本类似</span> <span class="token comment" spellcheck="true">// 唯一不同的是多了一步寻找lastRun</span> <span class="token comment" spellcheck="true">// 这里的lastRun是提取出链表后面不用处理再特殊处理的子链表</span> <span class="token comment" spellcheck="true">// 比如所有元素的hash值与桶大小n与操作后的值分别为 0 0 4 4 0 0 0</span> <span class="token comment" spellcheck="true">// 则最后后面三个0对应的元素肯定还是在同一个桶中</span> <span class="token comment" spellcheck="true">// 这时lastRun对应的就是倒数第三个节点</span> <span class="token comment" spellcheck="true">// 至于为啥要这样处理,我也没太搞明白</span> <span class="token keyword">int</span> runBit <span class="token operator">=</span> fh <span class="token operator">&</span> n<span class="token punctuation">;</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> lastRun <span class="token operator">=</span> f<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> f<span class="token punctuation">.</span>next<span class="token punctuation">;</span> p <span class="token operator">!=</span> null<span class="token punctuation">;</span> p <span class="token operator">=</span> p<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> b <span class="token operator">=</span> p<span class="token punctuation">.</span>hash <span class="token operator">&</span> n<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>b <span class="token operator">!=</span> runBit<span class="token punctuation">)</span> <span class="token punctuation">{</span> runBit <span class="token operator">=</span> b<span class="token punctuation">;</span> lastRun <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 看看最后这几个元素归属于低位链表还是高位链表</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>runBit <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ln <span class="token operator">=</span> lastRun<span class="token punctuation">;</span> hn <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> hn <span class="token operator">=</span> lastRun<span class="token punctuation">;</span> ln <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 遍历链表,把hash&n为0的放在低位链表中</span> <span class="token comment" spellcheck="true">// 不为0的放在高位链表中</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> f<span class="token punctuation">;</span> p <span class="token operator">!=</span> lastRun<span class="token punctuation">;</span> p <span class="token operator">=</span> p<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> ph <span class="token operator">=</span> p<span class="token punctuation">.</span>hash<span class="token punctuation">;</span> K pk <span class="token operator">=</span> p<span class="token punctuation">.</span>key<span class="token punctuation">;</span> V pv <span class="token operator">=</span> p<span class="token punctuation">.</span>val<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ph <span class="token operator">&</span> n<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> ln <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>ph<span class="token punctuation">,</span> pk<span class="token punctuation">,</span> pv<span class="token punctuation">,</span> ln<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> hn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>ph<span class="token punctuation">,</span> pk<span class="token punctuation">,</span> pv<span class="token punctuation">,</span> hn<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 低位链表的位置不变</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> ln<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 高位链表的位置是原位置加n</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i <span class="token operator">+</span> n<span class="token punctuation">,</span> hn<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 标记当前桶已迁移</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fwd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// advance为true,返回上面进行--i操作</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">TreeBin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果第一个元素是树节点</span> <span class="token comment" spellcheck="true">// 也是一样,分化成两颗树</span> <span class="token comment" spellcheck="true">// 也是根据hash&n为0放在低位树中</span> <span class="token comment" spellcheck="true">// 不为0放在高位树中</span> TreeBin<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> t <span class="token operator">=</span> <span class="token punctuation">(</span>TreeBin<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> lo <span class="token operator">=</span> null<span class="token punctuation">,</span> loTail <span class="token operator">=</span> null<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> hi <span class="token operator">=</span> null<span class="token punctuation">,</span> hiTail <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">int</span> lc <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> hc <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历整颗树,根据hash&n是否为0分化成两颗树</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> t<span class="token punctuation">.</span>first<span class="token punctuation">;</span> e <span class="token operator">!=</span> null<span class="token punctuation">;</span> e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> h <span class="token operator">=</span> e<span class="token punctuation">.</span>hash<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">(</span>h<span class="token punctuation">,</span> e<span class="token punctuation">.</span>key<span class="token punctuation">,</span> e<span class="token punctuation">.</span>val<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>h <span class="token operator">&</span> n<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>prev <span class="token operator">=</span> loTail<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> lo <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token keyword">else</span> loTail<span class="token punctuation">.</span>next <span class="token operator">=</span> p<span class="token punctuation">;</span> loTail <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token operator">++</span>lc<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>prev <span class="token operator">=</span> hiTail<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> hi <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token keyword">else</span> hiTail<span class="token punctuation">.</span>next <span class="token operator">=</span> p<span class="token punctuation">;</span> hiTail <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token operator">++</span>hc<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 如果分化的树中元素个数小于等于6,则退化成链表</span> ln <span class="token operator">=</span> <span class="token punctuation">(</span>lc <span class="token operator"><=</span> UNTREEIFY_THRESHOLD<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">untreeify</span><span class="token punctuation">(</span>lo<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>hc <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">TreeBin</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>lo<span class="token punctuation">)</span> <span class="token operator">:</span> t<span class="token punctuation">;</span> hn <span class="token operator">=</span> <span class="token punctuation">(</span>hc <span class="token operator"><=</span> UNTREEIFY_THRESHOLD<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">untreeify</span><span class="token punctuation">(</span>hi<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>lc <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">TreeBin</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hi<span class="token punctuation">)</span> <span class="token operator">:</span> t<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 低位树的位置不变</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> ln<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 高位树的位置是原位置加n</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i <span class="token operator">+</span> n<span class="token punctuation">,</span> hn<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 标记该桶已迁移</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fwd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// advance为true,返回上面进行--i操作</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>(1)新桶数组大小是旧桶数组的两倍;<br>(2)迁移元素先从靠后的桶开始;<br>(3)迁移完成的桶在里面放置一ForwardingNode类型的元素,标记该桶迁移完成;<br>(4)迁移时根据hash&n是否等于0把桶中元素分化成两个链表或树;<br>(5)低位链表(树)存储在原来的位置;<br>(6)高们链表(树)存储在原来的位置加n的位置;<br>(7)迁移元素时会锁住当前桶,也是分段锁的思想;</p><h3 id="判断扩容(addCount)"><a href="#判断扩容(addCount)" class="headerlink" title="判断扩容(addCount)"></a>判断扩容(addCount)</h3><p>每次添加元素后,元素数量加1,并判断是否达到扩容门槛,达到了则进行扩容或协助扩容。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">addCount</span><span class="token punctuation">(</span><span class="token keyword">long</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> check<span class="token punctuation">)</span> <span class="token punctuation">{</span> CounterCell<span class="token punctuation">[</span><span class="token punctuation">]</span> as<span class="token punctuation">;</span> <span class="token keyword">long</span> b<span class="token punctuation">,</span> s<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 这里使用的思想跟LongAdder类是一模一样的(后面会讲)</span> <span class="token comment" spellcheck="true">// 把数组的大小存储根据不同的线程存储到不同的段上(也是分段锁的思想)</span> <span class="token comment" spellcheck="true">// 并且有一个baseCount,优先更新baseCount,如果失败了再更新不同线程对应的段</span> <span class="token comment" spellcheck="true">// 这样可以保证尽量小的减少冲突</span> <span class="token comment" spellcheck="true">// 先尝试把数量加到baseCount上,如果失败再加到分段的CounterCell上</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>as <span class="token operator">=</span> counterCells<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">||</span> <span class="token operator">!</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapLong</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> BASECOUNT<span class="token punctuation">,</span> b <span class="token operator">=</span> baseCount<span class="token punctuation">,</span> s <span class="token operator">=</span> b <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> CounterCell a<span class="token punctuation">;</span> <span class="token keyword">long</span> v<span class="token punctuation">;</span> <span class="token keyword">int</span> m<span class="token punctuation">;</span> <span class="token keyword">boolean</span> uncontended <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 如果as为空</span> <span class="token comment" spellcheck="true">// 或者长度为0</span> <span class="token comment" spellcheck="true">// 或者当前线程所在的段为null</span> <span class="token comment" spellcheck="true">// 或者在当前线程的段上加数量失败</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>as <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>m <span class="token operator">=</span> as<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator"><</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token punctuation">(</span>a <span class="token operator">=</span> as<span class="token punctuation">[</span>ThreadLocalRandom<span class="token punctuation">.</span><span class="token function">getProbe</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&</span> m<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token operator">!</span><span class="token punctuation">(</span>uncontended <span class="token operator">=</span> U<span class="token punctuation">.</span><span class="token function">compareAndSwapLong</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> CELLVALUE<span class="token punctuation">,</span> v <span class="token operator">=</span> a<span class="token punctuation">.</span>value<span class="token punctuation">,</span> v <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 强制增加数量(无论如何数量是一定要加上的,并不是简单地自旋)</span> <span class="token comment" spellcheck="true">// 不同线程对应不同的段都更新失败了</span> <span class="token comment" spellcheck="true">// 说明已经发生冲突了,那么就对counterCells进行扩容</span> <span class="token comment" spellcheck="true">// 以减少多个线程hash到同一个段的概率</span> <span class="token function">fullAddCount</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> uncontended<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>check <span class="token operator"><=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 计算元素个数</span> s <span class="token operator">=</span> <span class="token function">sumCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>check <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> nt<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> sc<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 如果元素个数达到了扩容门槛,则进行扩容</span> <span class="token comment" spellcheck="true">// 注意,正常情况下sizeCtl存储的是扩容门槛,即容量的0.75倍</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>s <span class="token operator">>=</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span><span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator"><</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// rs是扩容时的一个邮戳标识</span> <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>sc <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// sc<0说明正在扩容中</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">>>></span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">!=</span> rs <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> MAX_RESIZERS <span class="token operator">||</span> <span class="token punctuation">(</span>nt <span class="token operator">=</span> nextTable<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> transferIndex <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 扩容已经完成了,退出循环</span> <span class="token comment" spellcheck="true">// 正常应该只会触发nextTable==null这个条件,其它条件没看出来何时触发</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 扩容未完成,则当前线程加入迁移元素中</span> <span class="token comment" spellcheck="true">// 并把扩容线程数加1</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> sc <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> nt<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> <span class="token punctuation">(</span>rs <span class="token operator"><<</span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 这里是触发扩容的那个线程进入的地方</span> <span class="token comment" spellcheck="true">// sizeCtl的高16位存储着rs这个扩容邮戳</span> <span class="token comment" spellcheck="true">// sizeCtl的低16位存储着扩容线程数加1,即(1+nThreads)</span> <span class="token comment" spellcheck="true">// 所以官方说的扩容时sizeCtl的值为 -(1+nThreads)是错误的</span> <span class="token comment" spellcheck="true">// 进入迁移元素</span> <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 重新计算元素个数</span> s <span class="token operator">=</span> <span class="token function">sumCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>(1)元素个数的存储方式类似于LongAdder类,存储在不同的段上,减少不同线程同时更新size时的冲突;<br>(2)计算元素个数时把这些段的值及baseCount相加算出总的元素个数;<br>(3)正常情况下sizeCtl存储着扩容门槛,扩容门槛为容量的0.75倍;<br>(4)扩容时sizeCtl高位存储扩容邮戳(resizeStamp),低位存储扩容线程数加1(1+nThreads);<br>(5)其它线程添加元素后如果发现存在扩容,也会加入的扩容行列中来;</p>]]></content>
<categories>
<category> java </category>
<category> java源码 </category>
<category> 并发 </category>
</categories>
<tags>
<tag> 集合 </tag>
<tag> java源码 </tag>
<tag> ConcurrentHashMap </tag>
</tags>
</entry>
<entry>
<title>ConcurrentHashMap基础</title>
<link href="/2019/08/05/ConcurrentHashMap%E5%9F%BA%E7%A1%80/"/>
<url>/2019/08/05/ConcurrentHashMap%E5%9F%BA%E7%A1%80/</url>
<content type="html"><![CDATA[<h1 id="ConcurrentHashMap的实现"><a href="#ConcurrentHashMap的实现" class="headerlink" title="ConcurrentHashMap的实现"></a>ConcurrentHashMap的实现</h1><p>ConcurrentHashMap作为Concurrent一族,其有着高效地并发操作,相比Hashtable的笨重,ConcurrentHashMap则更胜一筹了。<br>在1.8版本以前,ConcurrentHashMap采用分段锁的概念,使锁更加细化,但是1.8已经改变了这种思路,而是利用CAS+Synchronized来保证并发更新的安全,当然底层采用数组+链表+红黑树的存储结构。</p><p>JDK1.7中ConcurrentHashMap的数据结构,采用<strong>Segment + HashEntry</strong>的方式进行实现。<br><img src="/img/post-img/19-8-5-1.png" alt="JDK1.7结构"><br> JDK1.8中放弃了Segment臃肿的设计,取而代之的是采用<strong>Node + CAS + Synchronized</strong>来保证并发安全进行实现,结构如下:<br><img src="/img/post-img/19-8-5-2.png" alt="JDK1.8结构"></p><h1 id="ConcurrentHashMap的属性"><a href="#ConcurrentHashMap的属性" class="headerlink" title="ConcurrentHashMap的属性"></a>ConcurrentHashMap的属性</h1><p>ConcurrentHashMap定义了如下几个常量:</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 最大容量:2^30=1073741824</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAXIMUM_CAPACITY <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator"><<</span> <span class="token number">30</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 默认初始值,必须是2的幕数</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> DEFAULT_CAPACITY <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 桶数组最大容量</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAX_ARRAY_SIZE <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE <span class="token operator">-</span> <span class="token number">8</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 默认并发量</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> DEFAULT_CONCURRENCY_LEVEL <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 默认负载因子</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">float</span> LOAD_FACTOR <span class="token operator">=</span> <span class="token number">0.75f</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 链表转红黑树阀值,> 8 链表转换为红黑树</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> TREEIFY_THRESHOLD <span class="token operator">=</span> <span class="token number">8</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器</span><span class="token comment" spellcheck="true">//分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> UNTREEIFY_THRESHOLD <span class="token operator">=</span> <span class="token number">6</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当桶数组小于这个值的时候树化链表会优先扩容</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MIN_TREEIFY_CAPACITY <span class="token operator">=</span> <span class="token number">64</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MIN_TRANSFER_STRIDE <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> RESIZE_STAMP_BITS <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 2^15-1,help resize的最大线程数</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAX_RESIZERS <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator"><<</span> <span class="token punctuation">(</span><span class="token number">32</span> <span class="token operator">-</span> RESIZE_STAMP_BITS<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 32-16=16,sizeCtl中记录size大小的偏移量</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> RESIZE_STAMP_SHIFT <span class="token operator">=</span> <span class="token number">32</span> <span class="token operator">-</span> RESIZE_STAMP_BITS<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// forwarding nodes的hash值</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MOVED <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 树根节点的hash值</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> TREEBIN <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// ReservationNode的hash值</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> RESERVED <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 可用处理器数量</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> NCPU <span class="token operator">=</span> Runtime<span class="token punctuation">.</span><span class="token function">getRuntime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">availableProcessors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><strong>几个重要概念:</strong></p><blockquote><p>table:用来存放Node节点数据的,默认为null,默认大小为16的数组,每次扩容时大小总是2的幂次方;<br>nextTable:扩容时新生成的数据,数组为table的两倍;<br>Node:节点,保存key-value的数据结构;<br>ForwardingNode:一个特殊的Node节点,hash值为-1,其中存储nextTable的引用。只有table发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在table中表示当前节点为null或则已经被移动<br>sizeCtl:控制标识符,用来控制table初始化和扩容操作的,在不同的地方有不同的用途,其值也不同,所代表的含义也不同,官方给出的解释如下:<br>(1)-1,表示有线程正在进行初始化操作<br>(2)-(1 + nThreads),表示有n个线程正在一起扩容<br>(3)0,默认值,后续在真正初始化的时候使用默认容量<br>(4)> 0,初始化或扩容完成后下一次的扩容门槛</p></blockquote><h1 id="重要内部类"><a href="#重要内部类" class="headerlink" title="重要内部类"></a>重要内部类</h1><h2 id="Node"><a href="#Node" class="headerlink" title="Node"></a>Node</h2><p>作为ConcurrentHashMap中最核心、最重要的内部类,Node担负着重要角色:key-value键值对。所有插入ConCurrentHashMap的中数据都将会包装在Node中。定义如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">Map<span class="token punctuation">.</span>Entry</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">final</span> <span class="token keyword">int</span> hash<span class="token punctuation">;</span> <span class="token keyword">final</span> K key<span class="token punctuation">;</span> <span class="token keyword">volatile</span> V val<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//带有volatile,保证可见性</span> <span class="token keyword">volatile</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//下一个节点的指针</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token keyword">int</span> hash<span class="token punctuation">,</span> K key<span class="token punctuation">,</span> V val<span class="token punctuation">,</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hash <span class="token operator">=</span> hash<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>key <span class="token operator">=</span> key<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>val <span class="token operator">=</span> val<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>next <span class="token operator">=</span> next<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> K <span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> key<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> V <span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> val<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">^</span> val<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">return</span> key <span class="token operator">+</span> <span class="token string">"="</span> <span class="token operator">+</span> val<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** 不允许修改value的值 */</span> <span class="token keyword">public</span> <span class="token keyword">final</span> V <span class="token function">setValue</span><span class="token punctuation">(</span>V value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnsupportedOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">equals</span><span class="token punctuation">(</span>Object o<span class="token punctuation">)</span> <span class="token punctuation">{</span> Object k<span class="token punctuation">,</span> v<span class="token punctuation">,</span> u<span class="token punctuation">;</span> Map<span class="token punctuation">.</span>Entry<span class="token operator"><</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span> e<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>o <span class="token keyword">instanceof</span> <span class="token class-name">Map<span class="token punctuation">.</span>Entry</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>k <span class="token operator">=</span> <span class="token punctuation">(</span>e <span class="token operator">=</span> <span class="token punctuation">(</span>Map<span class="token punctuation">.</span>Entry<span class="token operator"><</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">)</span>o<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>v <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> key <span class="token operator">||</span> k<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>v <span class="token operator">==</span> <span class="token punctuation">(</span>u <span class="token operator">=</span> val<span class="token punctuation">)</span> <span class="token operator">||</span> v<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** get()方法调用此方法 */</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token function">find</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">,</span> Object k<span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">do</span> <span class="token punctuation">{</span> K ek<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> h <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> k <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&&</span> k<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> e<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>在Node内部类中,其属性value、next都是带有volatile的。同时其对value的setter方法进行了特殊处理,不允许直接调用其setter方法来修改value的值。最后Node还提供了find方法来赋值map.get()。</p><h2 id="TreeNode"><a href="#TreeNode" class="headerlink" title="TreeNode"></a>TreeNode</h2><p>在ConcurrentHashMap中,如果链表的数据过长会转换为红黑树来处理。但它并不是直接转换,而是将这些链表的节点包装成TreeNode放在TreeBin对象中,然后由TreeBin完成红黑树的转换。所以TreeNode也必须是ConcurrentHashMap的一个核心类,其为树节点类。</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">TreeNode</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> parent<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// red-black tree links</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> left<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> right<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> prev<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// needed to unlink next upon deletion</span> <span class="token keyword">boolean</span> red<span class="token punctuation">;</span> <span class="token function">TreeNode</span><span class="token punctuation">(</span><span class="token keyword">int</span> hash<span class="token punctuation">,</span> K key<span class="token punctuation">,</span> V val<span class="token punctuation">,</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">,</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> parent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> val<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>parent <span class="token operator">=</span> parent<span class="token punctuation">;</span> <span class="token punctuation">}</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token function">find</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">,</span> Object k<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">findTreeNode</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> k<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//查找hash为h,key为k的节点</span> <span class="token keyword">final</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token function">findTreeNode</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">,</span> Object k<span class="token punctuation">,</span> Class<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span> kc<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** 省略实现 */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>源码展示TreeNode继承Node,且提供了findTreeNode用来查找查找hash为h,key为k的节点。</p><h2 id="TreeBin"><a href="#TreeBin" class="headerlink" title="TreeBin"></a>TreeBin</h2><p>该类并不负责key-value的键值对包装,它用于在链表转换为红黑树时包装TreeNode节点,也就是说ConcurrentHashMap红黑树存放是TreeBin,不是TreeNode。该类封装了一系列的方法,包括putTreeVal、lookRoot、UNlookRoot、remove、balanceInsetion、balanceDeletion。其构造方法就是在构造一个红黑树的过程:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">TreeBin</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> root<span class="token punctuation">;</span> <span class="token keyword">volatile</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> first<span class="token punctuation">;</span> <span class="token keyword">volatile</span> Thread waiter<span class="token punctuation">;</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> lockState<span class="token punctuation">;</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> WRITER <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// set while holding write lock</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> WAITER <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// set when waiting for write lock</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> READER <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// increment value for setting read lock</span> <span class="token function">TreeBin</span><span class="token punctuation">(</span>TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> b<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>TREEBIN<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>first <span class="token operator">=</span> b<span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> r <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> x <span class="token operator">=</span> b<span class="token punctuation">,</span> next<span class="token punctuation">;</span> x <span class="token operator">!=</span> null<span class="token punctuation">;</span> x <span class="token operator">=</span> next<span class="token punctuation">)</span> <span class="token punctuation">{</span> next <span class="token operator">=</span> <span class="token punctuation">(</span>TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span><span class="token punctuation">)</span> x<span class="token punctuation">.</span>next<span class="token punctuation">;</span> x<span class="token punctuation">.</span>left <span class="token operator">=</span> x<span class="token punctuation">.</span>right <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>parent <span class="token operator">=</span> null<span class="token punctuation">;</span> x<span class="token punctuation">.</span>red <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> r <span class="token operator">=</span> x<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> K k <span class="token operator">=</span> x<span class="token punctuation">.</span>key<span class="token punctuation">;</span> <span class="token keyword">int</span> h <span class="token operator">=</span> x<span class="token punctuation">.</span>hash<span class="token punctuation">;</span> Class<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span> kc <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> p <span class="token operator">=</span> r<span class="token punctuation">;</span> <span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> dir<span class="token punctuation">,</span> ph<span class="token punctuation">;</span> K pk <span class="token operator">=</span> p<span class="token punctuation">.</span>key<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ph <span class="token operator">=</span> p<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">></span> h<span class="token punctuation">)</span> dir <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ph <span class="token operator"><</span> h<span class="token punctuation">)</span> dir <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>kc <span class="token operator">==</span> null <span class="token operator">&&</span> <span class="token punctuation">(</span>kc <span class="token operator">=</span> <span class="token function">comparableClassFor</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token punctuation">(</span>dir <span class="token operator">=</span> <span class="token function">compareComparables</span><span class="token punctuation">(</span>kc<span class="token punctuation">,</span> k<span class="token punctuation">,</span> pk<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> dir <span class="token operator">=</span> <span class="token function">tieBreakOrder</span><span class="token punctuation">(</span>k<span class="token punctuation">,</span> pk<span class="token punctuation">)</span><span class="token punctuation">;</span> TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span> V<span class="token operator">></span> xp <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p <span class="token operator">=</span> <span class="token punctuation">(</span>dir <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> p<span class="token punctuation">.</span>left <span class="token operator">:</span> p<span class="token punctuation">.</span>right<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>parent <span class="token operator">=</span> xp<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dir <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> xp<span class="token punctuation">.</span>left <span class="token operator">=</span> x<span class="token punctuation">;</span> <span class="token keyword">else</span> xp<span class="token punctuation">.</span>right <span class="token operator">=</span> x<span class="token punctuation">;</span> r <span class="token operator">=</span> <span class="token function">balanceInsertion</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>root <span class="token operator">=</span> r<span class="token punctuation">;</span> <span class="token keyword">assert</span> <span class="token function">checkInvariants</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** 省略很多代码 */</span> <span class="token punctuation">}</span></code></pre><h2 id="ForwardingNode"><a href="#ForwardingNode" class="headerlink" title="ForwardingNode"></a>ForwardingNode</h2><p>这是一个真正的辅助类,该类仅仅只存活在ConcurrentHashMap扩容操作时。只是一个标志节点,并且指向nextTable,它提供find方法而已。该类也是集成Node节点,其hash为-1,key、value、next均为null。如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ForwardingNode</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">final</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTable<span class="token punctuation">;</span> <span class="token function">ForwardingNode</span><span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>MOVED<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>nextTable <span class="token operator">=</span> tab<span class="token punctuation">;</span> <span class="token punctuation">}</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token function">find</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">,</span> Object k<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// loop to avoid arbitrarily deep recursion on forwarding nodes</span> outer<span class="token operator">:</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> nextTable<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> null <span class="token operator">||</span> tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token punctuation">(</span>e <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&</span> h<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> eh<span class="token punctuation">;</span> K ek<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>eh <span class="token operator">=</span> e<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> h <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> k <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&&</span> k<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> e<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>eh <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">ForwardingNode</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> tab <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ForwardingNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>e<span class="token punctuation">)</span><span class="token punctuation">.</span>nextTable<span class="token punctuation">;</span> <span class="token keyword">continue</span> outer<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">return</span> e<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> k<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><h1 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h1><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> cap <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>initialCapacity <span class="token operator">>=</span> <span class="token punctuation">(</span>MAXIMUM_CAPACITY <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">?</span> MAXIMUM_CAPACITY <span class="token operator">:</span> <span class="token function">tableSizeFor</span><span class="token punctuation">(</span>initialCapacity <span class="token operator">+</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sizeCtl <span class="token operator">=</span> cap<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span>Map<span class="token operator"><</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">K</span><span class="token punctuation">,</span> <span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">V</span><span class="token operator">></span> m<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sizeCtl <span class="token operator">=</span> DEFAULT_CAPACITY<span class="token punctuation">;</span> <span class="token function">putAll</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">,</span> <span class="token keyword">float</span> loadFactor<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">(</span>initialCapacity<span class="token punctuation">,</span> loadFactor<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">,</span> <span class="token keyword">float</span> loadFactor<span class="token punctuation">,</span> <span class="token keyword">int</span> concurrencyLevel<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>loadFactor <span class="token operator">></span> <span class="token number">0.0f</span><span class="token punctuation">)</span> <span class="token operator">||</span> initialCapacity <span class="token operator"><</span> <span class="token number">0</span> <span class="token operator">||</span> concurrencyLevel <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator"><</span> concurrencyLevel<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// Use at least as many bins</span> initialCapacity <span class="token operator">=</span> concurrencyLevel<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// as estimated threads</span> <span class="token keyword">long</span> size <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>initialCapacity <span class="token operator">/</span> loadFactor<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> cap <span class="token operator">=</span> <span class="token punctuation">(</span>size <span class="token operator">>=</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token operator">?</span> MAXIMUM_CAPACITY <span class="token operator">:</span> <span class="token function">tableSizeFor</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sizeCtl <span class="token operator">=</span> cap<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>构造方法与HashMap相比,没有了HashMap中的threshold和loadFactor,而是改用了sizeCtl来控制,而且只存储了容量在里面。</p>]]></content>
<categories>
<category> java </category>
<category> java源码 </category>
<category> 并发 </category>
</categories>
<tags>
<tag> 集合 </tag>
<tag> java源码 </tag>
<tag> ConcurrentHashMap </tag>
</tags>
</entry>
<entry>
<title>Linux上安装配置Nginx与ftp服务</title>
<link href="/2019/08/04/Linux%E4%B8%8A%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AENginx%E4%B8%8Eftp%E6%9C%8D%E5%8A%A1/"/>
<url>/2019/08/04/Linux%E4%B8%8A%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AENginx%E4%B8%8Eftp%E6%9C%8D%E5%8A%A1/</url>
<content type="html"><![CDATA[<h1 id="Nginx安装"><a href="#Nginx安装" class="headerlink" title="Nginx安装"></a>Nginx安装</h1><p>首先在<a href="http://nginx.org/en/download.html" target="_blank" rel="noopener">Nginx官网</a>下载稳定版本的Nginx安装包,并将安装包上传到Linux。<br>使用 <code>tar -zxvf nginx-1.16.0.tar.gz</code> 将压缩包解压。</p><p>接下来先安装Nginx所需的依赖:</p><ul><li>gcc<br> 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境,需要安装gcc:<code>yum install gcc-c++</code> </li><li>PCRE<br> PCRE(Perl Compatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式,所以需要在linux上安装pcre库。<code>yum install -y pcre pcre-devel</code><br>注:pcre-devel是使用pcre开发的一个二次开发库。nginx也需要此库。</li><li>zlib<br> zlib库提供了很多种压缩和解压缩的方式,nginx使用zlib对http包的内容进行gzip,所以需要在linux上安装zlib库。<br> <code>yum install -y zlib zlib-devel</code></li><li>openssl<br> OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。<br> nginx不仅支持http协议,还支持https(即在ssl协议上传输http),所以需要在linux安装openssl库。<br> <code>yum install -y openssl openssl-devel</code></li></ul><p>然后进入刚刚解压的文件夹,执行以下命令:</p><pre><code>[root@localhost nginx-1.16.0]# ./configure \> --prefix=/usr/local/nginx \> --pid-path=/var/run/nginx/nginx.pid \> --lock-path=/var/lock/nginx.lock \> --error-log-path=/var/log/nginx/error.log \> --http-log-path=/var/log/nginx/access.log \> --with-http_gzip_static_module \> --http-client-body-temp-path=/var/temp/nginx/client \> --http-proxy-temp-path=/var/temp/nginx/proxy \> --http-fastcgi-temp-path=/var/temp/nginx/fastcgi \> --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \> --http-scgi-temp-path=/var/temp/nginx/scgi</code></pre><p>可以看到文件夹下多出一个Makefile,然后依次执行make指令和make install指令:</p><pre><code>[root@localhost ~]# make[root@localhost ~]# make install</code></pre><p>在启动Nginx之前,先创建临时文件夹:</p><pre><code>[root@localhost ~]# mkdir /var/temp[root@localhost ~]# mkdir /var/temp/nginx</code></pre><h2 id="启动Nginx"><a href="#启动Nginx" class="headerlink" title="启动Nginx"></a>启动Nginx</h2><p>进入Nginx的安装目录:/usr/local/nginx,可以看到有conf、sbin、html三个文件夹,其中sbin中存放着的就是Nginx的可执行文件:</p><pre><code>[root@localhost ~]# cd /usr/local/nginx[root@localhost ~]# cd sbin[root@localhost ~]# ./nginx</code></pre><h2 id="停止Nginx"><a href="#停止Nginx" class="headerlink" title="停止Nginx"></a>停止Nginx</h2><ol><li><p>快速停止:查找到Nginx的进程id然后使用kill命令强制杀死进程。</p><pre><code>[root@localhost ~]# cd /usr/local/nginx/sbin[root@localhost ~]# ./nginx -s stop</code></pre></li><li><p>完整停止:等待Nginx进程处理完任务再进行停止。</p><pre><code>[root@localhost ~]# cd /usr/local/nginx/sbin[root@localhost ~]# ./nginx -s quit</code></pre></li></ol><h2 id="重启Nginx"><a href="#重启Nginx" class="headerlink" title="重启Nginx"></a>重启Nginx</h2><ol><li><p>先停止再启动</p><pre><code>[root@localhost ~]# cd /usr/local/nginx/sbin[root@localhost ~]# ./nginx -s quit[root@localhost ~]# ./nginx</code></pre></li><li><p>重新加载配置文件</p><pre><code>[root@localhost ~]# cd /usr/local/nginx/sbin[root@localhost ~]# ./nginx -s reload</code></pre></li></ol><h2 id="测试Nginx"><a href="#测试Nginx" class="headerlink" title="测试Nginx"></a>测试Nginx</h2><p>Nginx的默认端口是80,需要在iptables中将80端口设为开放,打开iptables文件并加入以下信息:<br>打开iptables文件:<code>[root@localhost nginx]# vim /etc/sysconfig/iptables</code><br>在其中加入:<code>-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT</code><br>如下:<br><img src="/img/post-img/19-8-4-1.png" alt="iptables"><br>保存退出。<br>重启iptables:<br><code>[root@localhost ~]# service iptables restart</code></p><p>然后在主机浏览器通过ip+端口访问Nginx,比如:192.168.94.192:80,看到如下信息,说明Nginx配置成功:<br><img src="/img/post-img/19-8-4-2.png" alt="Nginx欢迎界面"></p><h2 id="设置Nginx开机自启动"><a href="#设置Nginx开机自启动" class="headerlink" title="设置Nginx开机自启动"></a>设置Nginx开机自启动</h2><p>这里使用编写shell脚本的方式设置:<br><code>[root@localhost ~]# vim /etc/init.d/nginx</code><br>输入如下脚本:</p><pre class=" language-shell"><code class="language-shell">#!/bin/bash# nginx Startup script for the Nginx HTTP Server# it is v.0.0.2 version.# chkconfig: - 85 15# description: Nginx is a high-performance web and proxy server.# It has a lot of features, but it's not for everyone.# processname: nginx# pidfile: /var/run/nginx.pid# config: /usr/local/nginx/conf/nginx.confnginxd=/usr/local/nginx/sbin/nginxnginx_config=/usr/local/nginx/conf/nginx.confnginx_pid=/var/run/nginx.pidRETVAL=0prog="nginx"# Source function library.. /etc/rc.d/init.d/functions# Source networking configuration.. /etc/sysconfig/network# Check that networking is up.[ ${NETWORKING} = "no" ] && exit 0[ -x $nginxd ] || exit 0# Start nginx daemons functions.start() {if [ -e $nginx_pid ];then echo "nginx already running...." exit 1fi echo -n $"Starting $prog: " daemon $nginxd -c ${nginx_config} RETVAL=$? echo [ $RETVAL = 0 ] && touch /var/lock/subsys/nginx return $RETVAL}# Stop nginx daemons functions.stop() { echo -n $"Stopping $prog: " killproc $nginxd RETVAL=$? echo [ $RETVAL = 0 ] && rm -f /var/lock/subsys/nginx /var/run/nginx.pid}# reload nginx service functions.reload() { echo -n $"Reloading $prog: " #kill -HUP `cat ${nginx_pid}` killproc $nginxd -HUP RETVAL=$? echo}# See how we were called.case "$1" instart) start ;;stop) stop ;;reload) reload ;;restart) stop start ;;status) status $prog RETVAL=$? ;;*) echo $"Usage: $prog {start|stop|restart|reload|status|help}" exit 1esacexit $RETVAL</code></pre><p>保存并退出。</p><p>设置文件的访问权限:<br><code>chmod a+x /etc/init.d/nginx</code> (a+x ==> all user can execute 所有用户可执行)</p><p>加入到rc.local中:<br><code>[root@localhost ~]# vim /etc/rc.local</code><br>加入一行:<code>/etc/init.d/nginx start</code>,下次重启会生效。</p><h1 id="ftp配置"><a href="#ftp配置" class="headerlink" title="ftp配置"></a>ftp配置</h1><h2 id="安装vsftpd组件"><a href="#安装vsftpd组件" class="headerlink" title="安装vsftpd组件"></a>安装vsftpd组件</h2><p><code>[root@localhost ~]# yum -y install vsftpd</code><br>安装完后,有/etc/vsftpd/vsftpd.conf 文件,是vsftp的配置文件。</p><h2 id="添加一个ftp用户"><a href="#添加一个ftp用户" class="headerlink" title="添加一个ftp用户"></a>添加一个ftp用户</h2><p>此用户就是用来登录ftp服务器用的。<br><code>[root@localhost ~]# useradd ftpuser</code><br>添加密码:<br><code>[root@localhost ~]# passwd ftpuser</code></p><h2 id="开启21端口"><a href="#开启21端口" class="headerlink" title="开启21端口"></a>开启21端口</h2><p>因为ftp默认的端口为21,而centos默认是没有开启的,所以要修改iptables文件<br><code>[root@localhost ~]# vim /etc/sysconfig/iptables</code><br>在22 -j ACCEPT 下面另起一行输入跟那行差不多的,只是把22换成21,然后:wq保存。<br>重启iptables:<br><code>[root@localhost ~]# service iptables restart</code></p><h2 id="修改selinux"><a href="#修改selinux" class="headerlink" title="修改selinux"></a>修改selinux</h2><p>执行以下命令查看状态:</p><pre class=" language-bash"><code class="language-bash"><span class="token punctuation">[</span>root@localhost ~<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># getsebool -a | grep ftp </span>allow_ftpd_anon_write --<span class="token operator">></span> offallow_ftpd_full_access --<span class="token operator">></span> offallow_ftpd_use_cifs --<span class="token operator">></span> offallow_ftpd_use_nfs --<span class="token operator">></span> offftp_home_dir --<span class="token operator">></span> offftpd_connect_db --<span class="token operator">></span> offftpd_use_passive_mode --<span class="token operator">></span> offhttpd_enable_ftp_server --<span class="token operator">></span> offtftp_anon_write --<span class="token operator">></span> off</code></pre><p>执行上面命令,返回的结果中allow_ftpd_full_access和ftp_home_dir都是off,代表没有开启外网的访问<br><code>[root@localhost ~]# setsebool -P allow_ftpd_full_access on</code><br><code>[root@localhost ~]# setsebool -P ftp_home_dir on</code></p><h2 id="关闭匿名访问"><a href="#关闭匿名访问" class="headerlink" title="关闭匿名访问"></a>关闭匿名访问</h2><p><code>[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf</code><br><img src="/img/post-img/19-8-4-3.png" alt="vsftptd.conf"></p><h2 id="设置开机自启"><a href="#设置开机自启" class="headerlink" title="设置开机自启"></a>设置开机自启</h2><p><code>[root@localhost ~]# chkconfig vsftpd on</code></p><h2 id="测试ftp服务"><a href="#测试ftp服务" class="headerlink" title="测试ftp服务"></a>测试ftp服务</h2><p>将Nginx作为项目图片服务器,需要配置nginx.conf文件:<br><img src="/img/post-img/19-8-4-4.png" alt="nginx.conf"><br>使用java代码测试,需要依赖Apache的commons-net依赖。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>yangming<span class="token punctuation">.</span>controller<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>commons<span class="token punctuation">.</span>net<span class="token punctuation">.</span>ftp<span class="token punctuation">.</span>FTP<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>commons<span class="token punctuation">.</span>net<span class="token punctuation">.</span>ftp<span class="token punctuation">.</span>FTPClient<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>junit<span class="token punctuation">.</span>Test<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>File<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>FileInputStream<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>IOException<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FtpTest</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testFTPClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//创建一个FTP客户端</span> FTPClient ftpClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FTPClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//创建连接</span> ftpClient<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token string">"192.168.94.129"</span><span class="token punctuation">,</span><span class="token number">21</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//登录</span> ftpClient<span class="token punctuation">.</span><span class="token function">login</span><span class="token punctuation">(</span><span class="token string">"ftpuser"</span><span class="token punctuation">,</span><span class="token string">"ftpuser"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//创建一个文件输入流,读取一张图片文件</span> FileInputStream inputStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span> <span class="token string">"C:\\Users\\Administor\\Pictures\\Saved Pictures\\timg.jpg"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//指定文件上传的路径</span> ftpClient<span class="token punctuation">.</span><span class="token function">changeWorkingDirectory</span><span class="token punctuation">(</span><span class="token string">"/home/ftpuser/www/images"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//指定上传文件的格式,默认文本格式,需要改成二进制格式</span> ftpClient<span class="token punctuation">.</span><span class="token function">setFileType</span><span class="token punctuation">(</span>FTP<span class="token punctuation">.</span>BINARY_FILE_TYPE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//上传文件</span> ftpClient<span class="token punctuation">.</span><span class="token function">storeFile</span><span class="token punctuation">(</span><span class="token string">"test.jpg"</span><span class="token punctuation">,</span>inputStream<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//退出登录</span> ftpClient<span class="token punctuation">.</span><span class="token function">logout</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>此时访问192.168.94.129/images/test.jpg就可以看到上传的图片了。</p>]]></content>
<categories>
<category> 环境配置 </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> 环境搭建 </tag>
<tag> Nginx </tag>
<tag> ftp </tag>
</tags>
</entry>
<entry>
<title>AQS学习笔记</title>
<link href="/2019/08/01/AQS%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2019/08/01/AQS%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="AQS简介"><a href="#AQS简介" class="headerlink" title="AQS简介"></a>AQS简介</h1><hr><blockquote><p>AbstractQueuedSynchronizer简称AQS,是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的大量细节问题。</p></blockquote><p> AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,这个类只有一个变量:exclusiveOwnerThread,表示当前占用该锁的线程,并且提供了相应的get,set方法。<br>AQS内部通过一个int类型的成员变量state来控制同步状态,当state=0时,则说明没有任何线程占有共享资源的锁,当state=1时,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。<br>AQS内部通过内部类Node构成FIFO的同步队列来完成线程获取锁的排队工作,同时利用内部类ConditionObject构建等待队列,当Condition调用wait()方法后,线程将会加入等待队列中,而当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行锁竞争。注意这里涉及到两种队列,一种是同步队列,当线程请求锁而等待的后将加入同步队列等待,而另一种则是等待队列(可有多个),通过Condition调用await()方法释放锁后,将加入等待队列。</p><h1 id="AQS常用-API"><a href="#AQS常用-API" class="headerlink" title="AQS常用 API"></a>AQS常用 API</h1><hr><p>AQS主要提供了如下一些方法:</p><blockquote><p><strong>getState()</strong>:返回同步状态的当前值;<br><strong>setState(int newState)</strong>:设置当前同步状态;<br><strong>compareAndSetState(int expect, int update)</strong>:使用CAS设置当前状态,该方法能够保证状态设置的原子性;<br><strong>tryAcquire(int arg)</strong>:独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态;<br><strong>tryRelease(int arg)</strong>:独占式释放同步状态;<br><strong>tryAcquireShared(int arg)</strong>:共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;<br><strong>tryReleaseShared(int arg)</strong>:共享式释放同步状态;<br><strong>isHeldExclusively()</strong>:当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;<br><strong>acquire(int arg)</strong>:独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;<br><strong>acquireInterruptibly(int arg)</strong>:与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;<br><strong>tryAcquireNanos(int arg,long nanos)</strong>:超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;<br><strong>acquireShared(int arg)</strong>:共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;<br><strong>acquireSharedInterruptibly(int arg)</strong>:共享式获取同步状态,响应中断;<br><strong>tryAcquireSharedNanos(int arg, long nanosTimeout)</strong>:共享式获取同步状态,增加超时限制;<br><strong>release(int arg)</strong>:独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;<br><strong>releaseShared(int arg)</strong>:共享式释放同步状态;</p></blockquote><h1 id="CLH同步队列"><a href="#CLH同步队列" class="headerlink" title="CLH同步队列"></a>CLH同步队列</h1><hr><p>AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。<br>CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。<br>在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),Node是AQS的内部类,其定义如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Node</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** 共享 */</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Node SHARED <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** 独占 */</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Node EXCLUSIVE <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//因为超时或者中断,节点会被设置为取消状态,</span> <span class="token comment" spellcheck="true">//被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CANCELLED <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,</span> <span class="token comment" spellcheck="true">//将会通知后继节点,使后继节点的线程得以运行</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> SIGNAL <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了</span> <span class="token comment" spellcheck="true">//signal()后,改节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CONDITION <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** 表示下一次共享式同步状态获取将会无条件地传播下去*/</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> PROPAGATE <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** 等待状态 */</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> waitStatus<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** 前驱节点 */</span> <span class="token keyword">volatile</span> Node prev<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** 后继节点 */</span> <span class="token keyword">volatile</span> Node next<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** 获取同步状态的线程 */</span> <span class="token keyword">volatile</span> Thread thread<span class="token punctuation">;</span> Node nextWaiter<span class="token punctuation">;</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">isShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> nextWaiter <span class="token operator">==</span> SHARED<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">final</span> Node <span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> NullPointerException <span class="token punctuation">{</span> Node p <span class="token operator">=</span> prev<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">return</span> p<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token function">Node</span><span class="token punctuation">(</span>Thread thread<span class="token punctuation">,</span> Node mode<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> mode<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>thread <span class="token operator">=</span> thread<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">Node</span><span class="token punctuation">(</span>Thread thread<span class="token punctuation">,</span> <span class="token keyword">int</span> waitStatus<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>waitStatus <span class="token operator">=</span> waitStatus<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>thread <span class="token operator">=</span> thread<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>其中<strong>SHARED</strong>和<strong>EXCLUSIVE</strong>常量分别代表共享模式和独占模式,所谓共享模式是一个锁允许多条线程同时操作,如信号量Semaphore采用的就是基于AQS的共享模式实现的,而独占模式则是同一个时间段只能有一个线程对共享资源进行操作,多余的请求线程需要排队等待,如ReentranLock。变量waitStatus则表示当前被封装成Node结点的等待状态,共有4种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。</p><blockquote><p>CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。<br>SIGNAL:值为-1,被标识为该状态的结点,当其前继结点的线程释放了同步锁或被取消,将会通知该结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。<br>CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。<br>PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。<br>CLH同步队列模型及结构图如下:</p></blockquote><p><img src="/img/post-img/19-8-1-1.png" alt="同步队列"><br><img src="/img/post-img/19-8-1-2.png" alt="同步队列"></p><p>head和tail分别是AQS中的变量,其中head指向同步队列的头部,<strong>注意head为空结点,不存储信息</strong>。而tail则是同步队列的队尾,同步队列采用的是双向链表的结构这样可方便队列进行结点增删操作。state变量则是代表同步状态,执行当线程调用lock方法进行加锁后,如果此时state的值为0,则说明当前线程可以获取到锁,同时将state设置为1,表示获取成功。如果state已为1,也就是当前锁已被其他线程持有,那么当前执行线程将被封装为Node结点加入同步队列等待。每个Node结点内部关联其前继结点prev和后继结点next,这样可以方便线程释放锁后快速唤醒下一个在等待的线程。</p><h2 id="入队"><a href="#入队" class="headerlink" title="入队"></a>入队</h2><p>无非就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。addWaiter(Node node)方法:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> Node <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node mode<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//新建Node</span> Node node <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mode<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//快速尝试添加尾节点</span> Node pred <span class="token operator">=</span> tail<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>pred <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//CAS设置尾节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> pred<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token keyword">return</span> node<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//多次尝试</span> <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> node<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>addWaiter(Node node)先通过快速尝试设置尾节点,如果失败,则调用enq(Node node)方法设置尾节点:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> Node <span class="token function">enq</span><span class="token punctuation">(</span><span class="token keyword">final</span> Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//多次尝试,直到成功为止</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//tail不存在,设置为首节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetHead</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> tail <span class="token operator">=</span> head<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//设置为尾节点</span> node<span class="token punctuation">.</span>prev <span class="token operator">=</span> t<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> t<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token keyword">return</span> t<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>在上面代码中,两个方法都是通过一个CAS方法compareAndSetTail(Node expect, Node update)来设置尾节点,该方法可以确保节点是线程安全添加的。在enq(Node node)方法中,AQS通过“死循环”的方式来保证节点可以正确添加,只有成功添加后,当前线程才会从该方法返回,否则会一直执行下去。<br>过程图如下:<br><img src="/img/post-img/19-8-1-3.png" alt="入队"></p><h2 id="出队"><a href="#出队" class="headerlink" title="出队"></a>出队</h2><p>CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。过程图如下:<br><img src="/img/post-img/19-8-1-4.png" alt="出队"></p><h1 id="同步状态的获取与释放"><a href="#同步状态的获取与释放" class="headerlink" title="同步状态的获取与释放"></a>同步状态的获取与释放</h1><hr><p>AQS的设计模式采用的模板模式,子类通过继承的方式,实现它的抽象方法来管理同步状态,对于子类而言它并没有太多的活要做,AQS提供了大量的模板方法来实现同步,主要是分为三类:独占式获取和释放同步状态、共享式获取和释放同步状态、查询同步队列中的等待线程情况。自定义子类使用AQS提供的模板方法就可以实现自己的同步语义。</p><h2 id="独占式"><a href="#独占式" class="headerlink" title="独占式"></a>独占式</h2><p><strong>独占式,同一时刻仅有一个线程持有同步状态。</strong></p><h3 id="独占式同步状态获取(acquire方法)"><a href="#独占式同步状态获取(acquire方法)" class="headerlink" title="独占式同步状态获取(acquire方法)"></a>独占式同步状态获取(acquire方法)</h3><p>acquire(int arg)方法为AQS提供的模板方法,该方法为独占式获取同步状态,但是该方法对中断不敏感,也就是说由于线程获取同步状态失败加入到CLH同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除。代码如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>各个方法定义如下:</p><blockquote></blockquote><p>tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。<br>addWaiter:如果tryAcquire返回FALSE(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部。<br>acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。<br>selfInterrupt:产生一个中断。</p><p>acquireQueued方法为一个自旋的过程,也就是说当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自行地观察,当条件满足,获取到同步状态后,就可以从这个自旋过程中退出,否则会一直执行下去。如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token keyword">final</span> Node node<span class="token punctuation">,</span> <span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//中断标志</span> <span class="token keyword">boolean</span> interrupted <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/* * 自旋过程,其实就是一个死循环而已 */</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//当前线程的前驱节点</span> <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//当前线程的前驱节点是头结点,且同步状态成功</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head <span class="token operator">&&</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span> failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">return</span> interrupted<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//获取失败,线程等待--具体后面介绍</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span> <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>从上面代码中可以看到,当前线程会一直尝试获取同步状态,当然前提是只有其前驱节点为头结点才能够尝试获取同步状态,理由:</p><ul><li>保持FIFO同步队列原则。</li><li>头节点释放同步状态后,将会唤醒其后继节点,后继节点被唤醒后需要检查自己是否为头节点。<br>acquire(int arg)方法流程图如下:</li></ul><p><img src="/img/post-img/19-8-1-5.png" alt="acquire流程图"></p><h3 id="独占式获取响应中断(acquireInterruptibly方法)"><a href="#独占式获取响应中断(acquireInterruptibly方法)" class="headerlink" title="独占式获取响应中断(acquireInterruptibly方法)"></a>独占式获取响应中断(acquireInterruptibly方法)</h3><p>AQS提供了acquireInterruptibly(int arg)方法,该方法在等待获取同步状态时,如果当前线程被中断了,会立刻响应中断抛出异常InterruptedException。</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquireInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">doAcquireInterruptibly</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>首先校验该线程是否已经中断了,如果是则抛出InterruptedException,否则执行tryAcquire(int arg)方法获取同步状态,如果获取成功,则直接返回,否则执行doAcquireInterruptibly(int arg)。doAcquireInterruptibly(int arg)定义如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doAcquireInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span> <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head <span class="token operator">&&</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span> failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span> <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>doAcquireInterruptibly(int arg)方法与acquire(int arg)方法仅有两个差别。</p><ol><li>方法声明抛出InterruptedException异常,</li><li>在中断方法处不再是使用interrupted标志,而是直接抛出InterruptedException异常。</li></ol><h3 id="独占式超时获取(tryAcquireNanos方法)"><a href="#独占式超时获取(tryAcquireNanos方法)" class="headerlink" title="独占式超时获取(tryAcquireNanos方法)"></a>独占式超时获取(tryAcquireNanos方法)</h3><p>AQS除了提供上面两个方法外,还提供了一个增强版的方法:tryAcquireNanos(int arg,long nanos)。该方法为acquireInterruptibly方法的进一步增强,它除了响应中断外,还有超时控制。即如果当前线程没有在指定时间内获取同步状态,则会返回false,否则返回true。如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquireNanos</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">,</span> <span class="token keyword">long</span> nanosTimeout<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">doAcquireNanos</span><span class="token punctuation">(</span>arg<span class="token punctuation">,</span> nanosTimeout<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>tryAcquireNanos(int arg, long nanosTimeout)方法超时获取最终是在doAcquireNanos(int arg, long nanosTimeout)中实现的,如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">doAcquireNanos</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">,</span> <span class="token keyword">long</span> nanosTimeout<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//nanosTimeout <= 0</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>nanosTimeout <span class="token operator"><=</span> 0L<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//超时时间</span> <span class="token keyword">final</span> <span class="token keyword">long</span> deadline <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> nanosTimeout<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//新增Node节点</span> <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//自旋</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取同步状态成功</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head <span class="token operator">&&</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span> failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/* * 获取失败,做超时、中断判断 */</span> <span class="token comment" spellcheck="true">//重新计算需要休眠的时间</span> nanosTimeout <span class="token operator">=</span> deadline <span class="token operator">-</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//已经超时,返回false</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>nanosTimeout <span class="token operator"><=</span> 0L<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//如果没有超时,则等待nanosTimeout纳秒</span> <span class="token comment" spellcheck="true">//注:该线程会直接从LockSupport.parkNanos中返回,</span> <span class="token comment" spellcheck="true">//LockSupport为JUC提供的一个阻塞和唤醒的工具类,后面做详细介绍</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&&</span> nanosTimeout <span class="token operator">></span> spinForTimeoutThreshold<span class="token punctuation">)</span> LockSupport<span class="token punctuation">.</span><span class="token function">parkNanos</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> nanosTimeout<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//线程是否已经中断了</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span> <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>针对超时控制,程序首先记录唤醒时间deadline ,deadline = System.nanoTime() + nanosTimeout(时间间隔)。如果获取同步状态失败,则需要计算出需要休眠的时间间隔nanosTimeout = deadline – System.nanoTime(),如果nanosTimeout <= 0 表示已经超时了,返回false,如果大于spinForTimeoutThreshold(1000L)则需要休眠nanosTimeout ,如果nanosTimeout <= spinForTimeoutThreshold ,就不需要休眠了,直接进入快速自旋的过程。原因在于 spinForTimeoutThreshold 已经非常小了,非常短的时间等待无法做到十分精确,如果这时再次进行超时等待,相反会让nanosTimeout 的超时从整体上面表现得不是那么精确,所以在超时非常短的场景中,AQS会进行无条件的快速自旋。<br>整个流程如下:<br><img src="/img/post-img/19-8-1-6.png" alt="acquire流程图"></p><h3 id="独占式同步状态释放(release方法)"><a href="#独占式同步状态释放(release方法)" class="headerlink" title="独占式同步状态释放(release方法)"></a>独占式同步状态释放(release方法)</h3><p>当线程获取同步状态后,执行完相应逻辑就需要释放同步状态。AQS提供了release(int arg)方法释放同步状态:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">release</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryRelease</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node h <span class="token operator">=</span> head<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> null <span class="token operator">&&</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>该方法同样是先调用自定义同步器自定义的tryRelease(int arg)方法来释放同步状态,释放成功后,会调用unparkSuccessor(Node node)方法唤醒后继节点。<br><strong>稍微总结下:</strong></p><blockquote><p>在AQS中维护着一个FIFO的同步队列,当线程获取同步状态失败后,则会加入到这个CLH同步队列的对尾并一直保持着自旋。在CLH同步队列中的线程在自旋时会判断其前驱节点是否为首节点,如果为首节点则不断尝试获取同步状态,获取成功则退出CLH同步队列。当线程执行完逻辑后,会释放同步状态,释放后会唤醒其后继节点。</p></blockquote><h2 id="共享式"><a href="#共享式" class="headerlink" title="共享式"></a>共享式</h2><p>共享式与独占式的最主要区别在于同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个线程获取同步状态。例如读操作可以有多个线程同时进行,而写操作同一时刻只能有一个线程进行写操作,其他操作都会被阻塞。</p><h3 id="共享式同步状态获取(acquireShared方法)"><a href="#共享式同步状态获取(acquireShared方法)" class="headerlink" title="共享式同步状态获取(acquireShared方法)"></a>共享式同步状态获取(acquireShared方法)</h3><p>AQS提供acquireShared(int arg)方法共享式获取同步状态:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//获取失败,自旋获取同步状态</span> <span class="token function">doAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>从上面程序可以看出,方法首先是调用tryAcquireShared(int arg)方法尝试获取同步状态,如果获取失败则调用doAcquireShared(int arg)自旋方式获取同步状态,共享式获取同步状态的标志是返回 >= 0 的值表示获取成功。自旋式获取同步状态如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//共享式节点</span> <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>SHARED<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">boolean</span> interrupted <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//前驱节点</span> <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//如果其前驱节点是头结点,获取同步状态</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//尝试获取同步</span> <span class="token keyword">int</span> r <span class="token operator">=</span> <span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span> p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>interrupted<span class="token punctuation">)</span> <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span> <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>tryAcquireShared(int arg)方法尝试获取同步状态,返回值为int,当其 >= 0 时,表示能够获取到同步状态,这个时候就可以从自旋过程中退出。<br>acquireShared(int arg)方法不响应中断,与独占式相似,AQS也提供了响应中断、超时的方法,分别是:acquireSharedInterruptibly(int arg)、tryAcquireSharedNanos(int arg,long nanos)。</p><h3 id="共享式同步状态释放(release方法)"><a href="#共享式同步状态释放(release方法)" class="headerlink" title="共享式同步状态释放(release方法)"></a>共享式同步状态释放(release方法)</h3><p>获取同步状态后,需要调用release(int arg)方法释放同步状态,方法如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>因为可能会存在多个线程同时进行释放同步状态资源,所以需要确保同步状态安全地成功释放,一般都是通过CAS和循环来完成的。</p><h1 id="阻塞和唤醒线程"><a href="#阻塞和唤醒线程" class="headerlink" title="阻塞和唤醒线程"></a>阻塞和唤醒线程</h1><hr><p>在线程获取同步状态时如果获取失败,则加入CLH同步队列,通过自旋的方式不断获取同步状态,但是在自旋的过程中则需要判断当前线程是否需要阻塞,其主要方法在acquireQueued():</p><pre class=" language-java"><code class="language-java"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></code></pre><p>通过这段代码可以看到,在获取同步状态失败后,线程并不是立马进行阻塞,需要检查该线程的状态,检查状态的方法为 shouldParkAfterFailedAcquire(Node pred, Node node) 方法,该方法主要靠前驱节点判断当前线程是否应该被阻塞,代码如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>Node pred<span class="token punctuation">,</span> Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//前驱节点</span> <span class="token keyword">int</span> ws <span class="token operator">=</span> pred<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//状态为signal,表示当前线程处于等待状态,直接放回true</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//前驱节点状态 > 0 ,则为Cancelled,表明该节点已经超时或者被中断了,</span> <span class="token comment" spellcheck="true">//需要从同步队列中取消</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">do</span> <span class="token punctuation">{</span> node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred <span class="token operator">=</span> pred<span class="token punctuation">.</span>prev<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>pred<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pred<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//前驱节点状态为Condition、propagate</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>这段代码主要检查当前线程是否需要被阻塞,具体规则如下:</p><blockquote></blockquote><p>如果当前线程的前驱节点状态为SINNAL,则表明当前线程需要被阻塞,调用unpark()方法唤醒,直接返回true,当前线程阻塞<br>如果当前线程的前驱节点状态为CANCELLED(ws > 0),则表明该线程的前驱节点已经等待超时或者被中断了,则需要从CLH队列中将该前驱节点删除掉,直到回溯到前驱节点状态 <= 0 ,返回false<br>如果前驱节点非SINNAL,非CANCELLED,则通过CAS的方式将其前驱节点设置为SINNAL,返回false</p><p>如果 shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true,则调用parkAndCheckInterrupt()方法阻塞当前线程:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>parkAndCheckInterrupt() 方法主要是把当前线程挂起,从而阻塞住线程的调用栈,同时返回当前线程的中断状态。其内部则是调用LockSupport工具类的park()方法来阻塞该方法。<br>当线程释放同步状态后,则需要唤醒该线程的后继节点:</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">//唤醒后继节点</span><span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>调用unparkSuccessor(Node node)唤醒后继节点:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//当前节点状态</span> <span class="token keyword">int</span> ws <span class="token operator">=</span> node<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//当前状态 < 0 则设置为 0</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//当前节点的后继节点</span> Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//后继节点为null或者其状态 > 0 (超时或者被中断了)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> s <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//从tail节点来找可用节点</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span> t <span class="token operator">!=</span> null <span class="token operator">&&</span> t <span class="token operator">!=</span> node<span class="token punctuation">;</span> t <span class="token operator">=</span> t<span class="token punctuation">.</span>prev<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> s <span class="token operator">=</span> t<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//唤醒后继节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">!=</span> null<span class="token punctuation">)</span> LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>可能会存在当前线程的后继节点为null,超时、被中断的情况,如果遇到这种情况了,则需要跳过该节点,但是为何是从tail尾节点开始,而不是从node.next开始呢?原因在于node.next仍然可能会存在null或者取消了,所以采用tail回溯办法找第一个可用的线程。最后调用LockSupport的unpark(Thread thread)方法唤醒该线程。</p><h3 id="LockSupport"><a href="#LockSupport" class="headerlink" title="LockSupport"></a>LockSupport</h3><p>从上面我可以看到,当需要阻塞或者唤醒一个线程的时候,AQS都是使用LockSupport这个工具类来完成的。<br>LockSupport是用来创建锁和其他同步类的基本线程阻塞原语<br>每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在进程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用 unpark 使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。<br>LockSupport定义了一系列以park开头的方法来阻塞当前线程,unpark(Thread thread)方法来唤醒一个被阻塞的线程。如下:<br><img src="/img/post-img/19-8-1-7.png" alt="LockSupport"></p><p>park(Object blocker)方法的blocker参数,主要是用来标识当前线程在等待的对象,该对象主要用于问题排查和系统监控。<br>park方法和unpark(Thread thread)都是成对出现的,同时unpark必须要在park执行之后执行,当然并不是说没有调用unpark线程就会一直阻塞,park有一个方法,它带了时间戳(parkNanos(long nanos):为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用)。<br>park()和unpark(Thread thread)方法的源码如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> UNSAFE<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> 0L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">unpark</span><span class="token punctuation">(</span>Thread thread<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>thread <span class="token operator">!=</span> null<span class="token punctuation">)</span> UNSAFE<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>从上面可以看出,其内部的实现都是通过UNSAFE(sun.misc.Unsafe UNSAFE)来实现的,其定义如下:<br><code>public native void park(boolean var1, long var2);public native void unpark(Object var1);</code></p><p>两个都是native本地方法。Unsafe 是一个比较危险的类,主要是用于执行低级别、不安全的方法集合。尽管这个类和所有的方法都是公开的(public),但是这个类的使用仍然受限,你无法在自己的java程序中直接使用该类,因为只有授信的代码才能获得该类的实例。</p>]]></content>
<categories>
<category> 并发 </category>
</categories>
<tags>
<tag> 并发 </tag>
<tag> AQS </tag>
</tags>
</entry>
<entry>
<title>谈谈volatile</title>
<link href="/2019/07/31/%E8%B0%88%E8%B0%88volatile/"/>
<url>/2019/07/31/%E8%B0%88%E8%B0%88volatile/</url>
<content type="html"><![CDATA[<blockquote><p>Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurrent包等。</p></blockquote><h3 id="volatile的用法"><a href="#volatile的用法" class="headerlink" title="volatile的用法"></a>volatile的用法</h3><p>volatile通常被比喻成“轻量级的synchronized”,也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量,无法修饰方法或代码块等。<br>volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了。</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * 双重校验锁实现单例模式 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">static</span> Singleton singleton<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getSingleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>singleton<span class="token operator">==</span>null<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>Singleton<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>singleton<span class="token operator">==</span>null<span class="token punctuation">)</span><span class="token punctuation">{</span> singleton<span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> singleton<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>以上代码是一个比较典型的使用双重锁校验的形式实现的单例,其中使用volatile关键字修饰可能被多个线程同时访问到的singleton。</p><h3 id="volatile的原理"><a href="#volatile的原理" class="headerlink" title="volatile的原理"></a>volatile的原理</h3><p>Java为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致的问题。<br>但是,对于volatile变量,当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量写回到系统主存中。<br>但是就算写回到主存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议。<br><strong>缓存一致性协议</strong>:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。<br>所以,如果一个变量被volatile所修饰的话,在每次数据变化后,其值都会别强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。</p><h4 id="内存语义"><a href="#内存语义" class="headerlink" title="内存语义"></a>内存语义</h4><p>当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中。<br>当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量</p><h3 id="volatile与可见性"><a href="#volatile与可见性" class="headerlink" title="volatile与可见性"></a>volatile与可见性</h3><p><strong>可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改后的值。</strong><br>Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1修改了某个变量的值,但是线程2不可见的情况。<br>Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。<br>其实,volatile对于可见性的实现,内存屏障也起着至关重要的作用。因为内存屏障相当于一个数据同步点,他要保证在这个同步点之后的读写操作必须在这个点之前的读写操作都执行完之后才可以执行。并且在遇到内存屏障的时候,缓存数据会和主存进行同步,或者把缓存数据写入主存、或者从主存把数据读取到缓存。<br>Java内存模型解决了缓存一致性问题。内存一致性模型的实现可以通过缓存一致性协议来实现。<br>那么既然已经有了缓存一致性协议,为什么还需要volatile?<br>这个问题可以从多方面来回答:<br>并不是所有的硬件架构都提供了相同的一致性协议,Java作为一门跨平台语言,JVM需要提供一个统一的语义。<br>操作系统中的缓存和JVM中线程的本地内存并不是一回事,通常我们可以认为:MESI可以解决缓存层面的可见性问题。使用volatile关键字,可以解决JVM层面的可见性问题。<br>可见性问题的延伸:由于传统的MESI协议的执行成本比较大,所以CPU通过Store Buffer和Invalidate Queue组件来解决,但是由于这两个组件的引入,也导致缓存和主存之间的通信并不是实时的。也就是说,缓存一致性模型只能保证缓存变更时其他缓存也跟着改变,但不能保证立刻马上执行。<br>其实,在计算机内存模型中,也是使用内存屏障来解决缓存的可见性问题的(再次强调:缓存可见性和并发编程中的可见性可以互相类比,但是他们并不是一回事儿)。<br>写内存屏障(Store Memory Barrier)可以促使处理器将当前store buffer(存储缓存)的值写回主存。读内存屏障(Load Memory Barrier)可以促使处理器处理invalidate queue(失效队列)。进而避免由于Store Buffer和Invalidate Queue的非实时性带来的问题。<br>所以,内存屏障也是保证可见性的重要手段,操作系统通过内存屏障保证缓存间的可见性,JVM通过给volatile变量加入内存屏障保证线程之间的可见性。</p><h3 id="volatile与有序性"><a href="#volatile与有序性" class="headerlink" title="volatile与有序性"></a>volatile与有序性</h3><p><strong>有序性即程序执行的顺序按照代码的先后顺序执行。</strong><br>Java内存模型中,除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load→add→save有可能被优化成load→save→add。这就可能存在有序性问题。<br>而volatile除了可以保证数据的可见性之外,还有一个强大的功能,那就是它可以禁止指令重排优化等。<br>普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得到正确的结果,而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。<br>volatile可以禁止指令重排,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被volatile修饰的变量的操作,会严格按照代码顺序执行,load→add→save的执行顺序就是load、add、save。<br>那么volatile是如何禁止指令重排的呢?答案是:volatile通过<strong>内存屏障</strong>来禁止指令重排。<br>内存屏障(Memory Barrier)是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,下表描述了和volatile有关的指令重排序禁止行为:<br><img src="/img/post-img/19-7-31-1.png" alt="重排序"><br>从表中可以看出:</p><ul><li>当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。</li><li>当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。</li><li>当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。</li></ul><p>具体实现方式是在编译期生成字节码时,会在指令序列中增加内存屏障来保证,下面是基于保守策略的JMM内存屏障插入策略:</p><blockquote><p>在每个volatile写操作的前边插入一个StoreStore屏障<br>在每个volatile写操作的后面插入一个StoreLoad屏障<br>在每个volatile读操作的后面插入一个LoadLoad屏障<br>在每个volatile读操作的后面插入一个LoadStore屏障</p></blockquote><p><img src="/img/post-img/19-7-31-2.png" alt="volatile写"><br><img src="/img/post-img/19-7-31-3.png" alt="volatile读"><br><strong>StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。<br>StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。<br>LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。<br>LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。</strong></p><p>所以,volatile通过在volatile变量的操作前后插入内存屏障的方式,来禁止指令重排,进而保证多线程情况下对共享变量的有序性。</p><h3 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h3><p>以如下一段代码来看:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">VolatileBarrierExample</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> v1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> v2 <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">readAndWrite</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">int</span> i <span class="token operator">=</span> v1<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//volatile读</span> <span class="token keyword">int</span> j <span class="token operator">=</span> v2<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//volatile读</span> a <span class="token operator">=</span> i <span class="token operator">+</span> j<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//普通读</span> v1 <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//volatile写</span> v2 <span class="token operator">=</span> j <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//volatile写</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>优化后的内存屏障图例:<br><img src="/img/post-img/19-7-31-4.png" alt="内存屏障"><br>注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确判定后面是否会有volatile读或者写,为了安全起见,编译器通常会在这里插入一个StoreLoad屏障。</p><h3 id="volatile与原子性"><a href="#volatile与原子性" class="headerlink" title="volatile与原子性"></a>volatile与原子性</h3><p><strong>原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。</strong><br>线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。<br>为了保证原子性,需要通过字节码指令monitorenter和monitorexit,但是volatile和这两个指令之间没有任何关系。<br>所以,<strong>volatile是不能保证原子性的</strong>。<br>在以下两个场景中可以使用volatile来代替synchronized:</p><blockquote><p>运算结果并不依赖变量的当前值,或者能够确保只有单一的线程会修改变量的值<br>变量不需要与其他状态变量共同参与不变约束</p></blockquote><p>除以上场景外,都需要使用其他方式来保证原子性,如synchronized或者concurrent包。</p><h3 id="个人理解"><a href="#个人理解" class="headerlink" title="个人理解"></a>个人理解</h3><p>网上有很多文章,拿i++的例子说明volatile不能保证原子性,然后进行各种分析,有的说由于引入内存屏障导致无法保证原子性,有的说一段i++代码,在编译后字节码有四个步骤。<br>这些分析只是说明了i++本身不是一个原子操作,即使使用volatile修饰i,也无法保证他是一个原子操作。并不能解释为什么volatile不能保证原子性。<br>在我看来,由于CPU是按照时间片来进行线程调度的,只要是包含多个步骤的操作的执行,天然就是无法保证原子性的。因为这种线程执行,又不像数据库一样可以回滚。如果一个线程要执行的步骤有5步,执行完3步就失去了CPU了,失去后就可能再也不会被调度,这怎么可能保证原子性呢。<br>为什么synchronized可以保证原子性 ,因为被synchronized修饰的代码片段,在进入之前加了锁,只要他没执行完,其他线程是无法获得锁执行这段代码片段的,就可以保证他内部的代码可以全部被执行。进而保证原子性。<br>但是synchronized对原子性保证也不绝对,如果真要较真的话,一旦代码运行异常,也没办法回滚。所以呢,在并发编程中,原子性的定义不应该和事务中的原子性一样。他应该定义为:一段代码,或者一个变量的操作,在没有执行完之前,不能被其他线程执行。<br>那么,为什么volatile不能保证原子性呢?因为他不是锁,他没做任何可以保证原子性的处理。当然就不能保证原子性了。</p>]]></content>
<categories>
<category> JMM内存模型 </category>
</categories>
<tags>
<tag> 并发 </tag>
<tag> volatile </tag>
</tags>
</entry>
<entry>
<title>java源码之二叉查找树与二叉平衡树</title>
<link href="/2019/07/25/java%E6%BA%90%E7%A0%81%E4%B9%8B%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91%E4%B8%8E%E4%BA%8C%E5%8F%89%E5%B9%B3%E8%A1%A1%E6%A0%91/"/>
<url>/2019/07/25/java%E6%BA%90%E7%A0%81%E4%B9%8B%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91%E4%B8%8E%E4%BA%8C%E5%8F%89%E5%B9%B3%E8%A1%A1%E6%A0%91/</url>
<content type="html"><![CDATA[<h2 id="二叉排序树"><a href="#二叉排序树" class="headerlink" title="二叉排序树"></a>二叉排序树</h2><blockquote><p>解决查询速度慢的方案除了哈希表外,还可以使用二叉排序树。查询慢主要是因为不知道元素的位置,使用hash函数映射虽然解决了问题,但其并不稳定,当出现大量的哈希碰撞后其表现更像一个链表,查询速度大大降低。<br>二叉排序树的方案则是使元素有序,这样便可以使用二分法进行查找了,虽然效率相比hash函数低一些,但可以通过AVL树、红黑树等增加稳定性。</p></blockquote><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>二叉排序树(Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树:</p><ul><li>若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值</li><li>若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值</li><li>它的左、右子树也分别为二叉排序树</li></ul><p>如下就是一棵简单的二叉排序树:<br><img src="/img/post-img/19-7-25-1.png" alt="二叉排序树"><br>当对这棵树进行中序遍历时,其结果将按照从小到大排序。</p><h4 id="查询操作"><a href="#查询操作" class="headerlink" title="查询操作"></a>查询操作</h4><p>二叉排序树的查找时间复杂度为O(lg n),查找使用二分法。要在上图中找到元素37,只需要四次操作即可。<br>首先,找到根元素22,37比22大,所以淘汰左子树,再找到35,淘汰左子树,再找到41,进入左子树,得到37。可以看到其速度比挨个对比高了很多。</p><h4 id="插入操作"><a href="#插入操作" class="headerlink" title="插入操作"></a>插入操作</h4><p>二叉排序树的插入操作和查询类似,也需要通过二分法进行查找,找到合适的位置再插入元素,所以其插入速度相比链表较慢。</p><h4 id="删除操作"><a href="#删除操作" class="headerlink" title="删除操作"></a>删除操作</h4><p>从二叉排序树中删除一个元素主要分为三种情况。<br>例如要从下面这个二叉排序树中删除一个元素:<br><img src="/img/post-img/19-7-25-2.png" alt="二叉排序树"></p><ol><li>删除的元素是叶结点,这时可以直接删除它。比如要删除值为1的元素,删除它对树没有任何影响。</li><li>删除的元素仅有左孩子或者仅有右孩子时,直接让其孩子顶替它即可。比如要删除元素35,只需要用41顶替它即可。</li><li>删除的元素既有左孩子又有右孩子,这时删除它相对复杂。一种好的方式是找到它的前驱或者后继来代替它。比如要删除元素9,就用6或者13代替它即可。</li></ol><h4 id="缺陷"><a href="#缺陷" class="headerlink" title="缺陷"></a>缺陷</h4><p>一棵普通的二叉排序树也会出现不平衡问题,如果插入的数据都在树的一侧,就会使得树的深度迅速增大,每次二分查找可以排除的数据很少,从而查询速度严重下降,比如下方这棵树:<br><img src="/img/post-img/19-7-25-3.png" alt="不平衡的二叉排序树"></p><p>要查找值为2的元素,使用二分法和使用链表速度差不多。</p><p>为了解决这种问题,就需要在元素插入时即进行修正,后续介绍的AVL树和红黑树就是两种不同的解决方案。</p><h2 id="平衡二叉树(AVL-Tree)"><a href="#平衡二叉树(AVL-Tree)" class="headerlink" title="平衡二叉树(AVL Tree)"></a>平衡二叉树(AVL Tree)</h2><blockquote><p>二叉排序树很好的平衡了插入与查找的效率,但不平衡的二叉排序树效率大打折扣。AVL树就是一种解决此问题的方案。</p></blockquote><h4 id="定义-1"><a href="#定义-1" class="headerlink" title="定义"></a>定义</h4><p>平衡二叉树(Self-Balancing Binary Search Tree 或Height-Balanced Binary Search Tree),是一种<strong>二叉排序树</strong>,其中每一个节点的左子树和右子树的高度差<strong>至多等于1</strong> 。它是一种高度平衡的二叉排序树。意思是说,要么它是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1 。我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF (Balance Factor),那么平衡二叉树上所有结点的平衡因子只可能是-1 、0 和1。<br>如下图就是一棵AVL树:<br><img src="/img/post-img/19-7-25-4.png" alt="平衡二叉树"></p><h4 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h4><p>平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。最小不平衡子树是指距离插入结点最近的,且平衡因子的绝对值大于1 的结点为根的子树。<br>下面通过一个实例,了解平衡二叉树的构建过程。<br>假如我们要将数组int[] a = {3, 2, 1, 4, 5, 6, 7, 10, 9}构建成一棵二叉排序树,如果直接按照二叉排序树的定义,会得到下面的结果:<br><img src="/img/post-img/19-7-25-5.png" alt="平衡二叉树"></p><p>以下为创建过程:<br><img src="/img/post-img/19-7-25-6.png" alt="平衡二叉树创建"><br><img src="/img/post-img/19-7-25-7.png" alt="平衡二叉树创建"><br><img src="/img/post-img/19-7-25-8.png" alt="平衡二叉树创建"></p>]]></content>
<categories>
<category> java源码 </category>
</categories>
<tags>
<tag> java源码 </tag>
<tag> 二叉树 </tag>
</tags>
</entry>
<entry>
<title>java源码之树与二叉树</title>
<link href="/2019/07/09/java%E6%BA%90%E7%A0%81%E4%B9%8B%E6%A0%91%E4%B8%8E%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
<url>/2019/07/09/java%E6%BA%90%E7%A0%81%E4%B9%8B%E6%A0%91%E4%B8%8E%E4%BA%8C%E5%8F%89%E6%A0%91/</url>
<content type="html"><![CDATA[<h2 id="树的定义"><a href="#树的定义" class="headerlink" title="树的定义"></a>树的定义</h2><p>树(Tree)是n(n≥0) 个结点的有限集。n=0 时称为空树。在任意一棵非空树中:</p><p> 1.有且仅有一个特定的称为根(Root)的结点;<br> 2.当n>1 时,其余结点可分为m (m>0) 个互不相交的有限集T1 、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。<br>下图就是一棵树:<br><img src="/img/post-img/19-7-9-2.png" alt="树示意图"></p><h2 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h2><h3 id="结点分类"><a href="#结点分类" class="headerlink" title="结点分类"></a>结点分类</h3><p>树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree) 。度为0的结点称为叶结点(Leaf) 或终端结点;度不为0 的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。</p><p>如下图所示,A结点为根节点,G、H、I、J、F为叶节点,其余节点则为内部节点,此树的度为3。<br><img src="/img/post-img/19-7-9-3.png" alt="度"></p><h3 id="结点间关系"><a href="#结点间关系" class="headerlink" title="结点间关系"></a>结点间关系</h3><p>结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)。同一个双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。<br><img src="/img/post-img/19-7-9-4.png" alt="关系示意图"></p><h3 id="深度"><a href="#深度" class="headerlink" title="深度"></a>深度</h3><p>结点的层次(LeveI)从根开始定义起,根为第一层,根的孩子为第二层。若某结点在第L层,则其子树的根就在第L+1 层。其双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度 。<br><img src="/img/post-img/19-7-11-1.png" alt="深度示意图"></p><h3 id="有序树,无序树"><a href="#有序树,无序树" class="headerlink" title="有序树,无序树"></a>有序树,无序树</h3><p>如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。</p><h3 id="二叉树"><a href="#二叉树" class="headerlink" title="二叉树"></a>二叉树</h3><p>二叉树(Binary Tree)是n(n ≥ 0) 个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。</p><h3 id="二叉树遍历"><a href="#二叉树遍历" class="headerlink" title="二叉树遍历"></a>二叉树遍历</h3><p>二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次旦仅被访问一次。</p><h4 id="前序遍历"><a href="#前序遍历" class="headerlink" title="前序遍历"></a>前序遍历</h4><p> 规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树, 再前序遍历右子树。</p><p>如下图所示,遍历结果为:ABDGHCEIF。<br><img src="/img/post-img/19-7-11-2.png" alt="前序遍历"></p><h4 id="中序遍历"><a href="#中序遍历" class="headerlink" title="中序遍历"></a>中序遍历</h4><p>规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点) ,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。</p><p>如下图所示,遍历结果为:GDHBAEICF。<br><img src="/img/post-img/19-7-11-3.png" alt="中序遍历"></p><h4 id="后序遍历"><a href="#后序遍历" class="headerlink" title="后序遍历"></a>后序遍历</h4><p>规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。</p><p>如下图所示,遍历结果为:GHDBIEFCA。<br><img src="/img/post-img/19-7-11-4.png" alt="后序遍历"></p>]]></content>
<categories>
<category> java源码 </category>
</categories>
<tags>
<tag> java源码 </tag>
<tag> 二叉树 </tag>
</tags>
</entry>
<entry>
<title>java源码之数组、链表与哈希表</title>
<link href="/2019/07/09/java%E6%BA%90%E7%A0%81%E4%B9%8B%E6%95%B0%E7%BB%84%E3%80%81%E9%93%BE%E8%A1%A8%E4%B8%8E%E5%93%88%E5%B8%8C%E8%A1%A8/"/>
<url>/2019/07/09/java%E6%BA%90%E7%A0%81%E4%B9%8B%E6%95%B0%E7%BB%84%E3%80%81%E9%93%BE%E8%A1%A8%E4%B8%8E%E5%93%88%E5%B8%8C%E8%A1%A8/</url>
<content type="html"><![CDATA[<h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>在java中,数组定义为一种基本类型,其可以通过下标获取到对应位置的数据。数组在内存中是一段连续的存储单元,每个数据依次放在每个单元中。分析这种结构,可以得出以下几个结论:</p><blockquote><ul><li>创建一个数组,必须声明其长度,以在内存中寻找合适的一段连续存储单元。这也意味着数组的大小是固定的,我们无法动态调整其大小。</li><li>想要获取数组中第i个元素,其时间复杂度是 O(1),因为可以根据其地址直接找到它。同理修改也是。</li><li>数组对查询表现一般,要想查找一个元素,需要遍历,时间复杂度为O(n)。</li><li>因为地址连续,想要在数组中插入一个元素是复杂的,因为从插入位置起,后边的所有元素都需要向后移动一位。同理删除也是,只是移动方向为向前。并且,当数组存满时,就无法继续插入了。</li><li>因为数组要占据一整块内存,有可能产生许多的碎片,也可能因为找不到合适的内存块,而导致存储失败。</li></ul></blockquote><p>总结起来就是:<strong>数组大小固定,查找迅速,增删复杂,需要完整的内存块,容易产生碎片。</strong></p><h3 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h3><p>链表是一种离散存储结构,其在内存中存储不是连续的,每个数据元素都通过一个指针指向其下一个元素的地址。根据指针域的不同,链表又分为单链表、双向链表、循环链表等。对于单链表而言,可以得出以下几个结论:</p><blockquote><ul><li>声明一个链表时,不需要知道其长度,也不需要连续的内存块,所以其大小可以动态调整。</li><li>链表的每个元素都分为数据域和指针域,前者是实际存储的数据,后者则指向下一个元素的地址。和数组相比,每个元素需要占用的内存更大了。</li><li>要获取链表的第 i 个元素变得复杂,因为其地址存放在它上一个元素的指针域里,所以只能从第一个元素起,进行 i 次操作。同理修改也是。</li><li>链表对查询表现也一般,需要遍历,时间复杂度为O(n)。</li><li>增加与删除一个元素更方便了,因为没有对内存地址的限制,我们只需要在对应节点合理处理下指针域的值,就可以把一个元素插入链表或者从链表删除。</li><li>链表对内存的要求很小,只要能够存储下一个数据元素的内存块都可以使用,因此不会造成碎片化。</li></ul></blockquote><p>总结起来就是:<strong>大小可以动态调整,增删迅速,查找较慢,数据元素所占内存略多,不需要整块内存块,不会造成碎片化。</strong></p><h3 id="数组与链表的选择"><a href="#数组与链表的选择" class="headerlink" title="数组与链表的选择"></a>数组与链表的选择</h3><p>通过以上分析,数组和链表对我们影响最大的几点区别在于:</p><blockquote><ul><li>数组按位置查找迅速,链表增删方便</li><li>数组是固定大小,链表可以随时扩充与缩减</li><li>链表每个元素占据内存略多于数组</li><li>数组和链表在查询方面表现都比较一般,耗时较长</li></ul></blockquote><p>在数据量很小,内容基本固定时,选择何种数据结构的影响并不大。但当数据量较大时,如果我们需要对数据进行频繁的插入删除,就应该选择链表,如果我们需要频繁的获取某个位置的元素,就应该选择数组。数组与链表并没有明确的优劣之分,根据不同的使用场景进行不同的选择,才是这两种结构使用的最佳方式。</p><h3 id="哈希表"><a href="#哈希表" class="headerlink" title="哈希表"></a>哈希表</h3><p>无论是数组还是链表,其对数据的查询表现都比较无力,要想知道一个元素是否在数组或链表中,只能从前向后挨个对比。出现这个问题的根源在于,我们没有办法直接根据一个元素找到它存储的位置。哈希表就是解决查询问题的一种方案。</p><h3 id="哈希表与Hash函数"><a href="#哈希表与Hash函数" class="headerlink" title="哈希表与Hash函数"></a>哈希表与Hash函数</h3><p>通俗来讲,哈希表就是通过关键字来获取数据的一种数据结构,它通过把关键字映射为表中的位置来获取元素,这种映射主要是使用Hash函数。</p><p>Hash函数,实际上是建立起key值与int值映射关系的函数。这就好比每个人都有一个身份证号一样,无论是男是女,出生在何处,都可以通过身份证号来分辨,这就是把人的信息映射成一串数字的典型做法。Hash函数和此类似,不过是把任意的Java对象,映射成一个int数值,供哈希表使用。</p><p>而哈希表,就是一个数组,只是其元素不是按照数组的规则排列的。任何一个元素要放进哈希表中,都必须先通过Hash函数获取到一个int数值,这个数值经过处理后将作为它的存放位置,然后这个元素才能放进哈希表中。</p><p>哈希表完全继承了数组的优点,又显著的提高了查询的速度,通过Hash函数使得查询速度达到了O(1)。既然有了哈希表,它这么优秀,为何还需要数组的存在呢?那是因为Hash表是有缺陷的,这个缺陷就是哈希碰撞。</p><h3 id="哈希碰撞"><a href="#哈希碰撞" class="headerlink" title="哈希碰撞"></a>哈希碰撞</h3><p>Hash函数所做的事,就是无论什么对象,都根据一个规则映射为一个int值。被转换的对象有无数种可能,但是int的值是有限的,它只有2^32个,这样一来,必然会有不同的对象,映射得到相同的int值,这就是所谓的哈希碰撞。发生碰撞之后,就要把不同的元素插入到相同的位置,这时候单纯的使用一维数组已经无法满足需求了。</p><p>目前比较通用的解决哈希碰撞的方法,就是使用<strong>数组+链表</strong>组合的方式。当出现哈希碰撞时,在该位置的数据就通过链表的方式链接起来,如下图所示:<br><img src="/img/post-img/19-7-9-1.png" alt="数组+链表"><br>这是当前比较理想的方法,既继承了数组的优点,又在碰撞时继承了链表的优点,这也是哈希表强大的地方之一。</p><p>在JDK1.7及之前的版本中,HashMap的存储结构和上图是一致的,在JDK1.8之后还加入了红黑树以进一步优化。</p><h3 id="哈希表的优缺点"><a href="#哈希表的优缺点" class="headerlink" title="哈希表的优缺点"></a>哈希表的优缺点</h3><p>哈希表是一种优化存储的思想,具体存储元素的依然是其他的数据结构。设计良好的哈希表,能同时兼备数组和链表的优点,它能在插入和查找时都具备良好的性能。然而设计不好的哈希表,有可能会出现较多的哈希碰撞,导致链表过长,从而哈希表会更像一个链表。还有当数据量很大时,为防止链表过长,就需要对数组进行扩容,这时就涉及到了数组的拷贝,其对性能的影响也很严重,所以需要提前对可能的情况有良好的预测,才能真正发挥哈希表的优势。</p>]]></content>
<categories>
<category> java源码 </category>
</categories>
<tags>
<tag> java源码 </tag>
<tag> 数组 </tag>
<tag> 链表 </tag>
<tag> 哈希表 </tag>
</tags>
</entry>
<entry>
<title>线程池</title>
<link href="/2019/06/04/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
<url>/2019/06/04/%E7%BA%BF%E7%A8%8B%E6%B1%A0/</url>
<content type="html"><![CDATA[<h2 id="为什么要用线程池?"><a href="#为什么要用线程池?" class="headerlink" title="为什么要用线程池?"></a>为什么要用线程池?</h2><p>单线程方式存在以下几个问题:</p><ul><li>线程的工作周期:假设线程创建所需时间为T1,线程执行任务所需时间为T2,线程销毁所需的时间为T3,往往是T1+T3大于T2,所以如果频繁的创建线程会损耗过多的额外时间。</li><li>如果有任务来了,再去创建线程的话效率比较低,如果从一个池子中可以直接获取可用的线程,那么效率会有所提升。所以线程池省去了任务过来要先创建线程的过程,节省了时间,提升了效率。</li><li>线程池可以管理和控制线程,因为线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。</li><li>线程池提供队列,存放缓冲等待执行的任务。</li></ul><blockquote><p>通过线程池创建线程从调用 API 角度来说分为两种,一种是原生的线程池,另外该一种是通过 Java 提供的并发包来创建,后者其实是对原生的线程池创建方式做了一次简化包装,让调用者使用起来更方便,但道理都是一样的。</p></blockquote><h2 id="ThreadPoolExecutor"><a href="#ThreadPoolExecutor" class="headerlink" title="ThreadPoolExecutor"></a>ThreadPoolExecutor</h2><p>通过ThreadPoolExecutor创建线程池,API如下所示:<br><img src="/img/post-img/19-6-4-1.png" alt="ThreadPoolExecutor"></p><ul><li><strong>corePoolSize</strong>:指定<strong>核心线程数</strong>(核心池大小)。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,预创建线程,即在任务到来之前就创建corePoolSize个或者一个线程。默认情况下,在创建了线程池后,线程池中的线程池数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数达到corePoolSize后,就会把到达的任务放到缓存队列中。</li><li><strong>maximumPoolSize</strong>:<strong>线程池最大线程数</strong>,它表示在线程池中最多能创建多少个线程。</li><li><strong>keepAliveTime</strong>:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。但是如果调用了 allowCoreThreadTimeOut(boolean) 方法,在线程池中的线程数不大于 corePoolSize 时,keepAliveTime 参数也会起作用,直到线程池中的线程数为0。</li><li><strong>unit</strong>:参数keepAliveTime的<strong>时间单位</strong>。</li><li><strong>workQueue</strong>:一个<strong>阻塞队列</strong>,用来存储等待执行的任务,这个参数会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。</li><li><strong>threadFactory</strong>:<strong>线程工厂</strong>,用来创建线程。</li><li><strong>handler</strong>:表示当拒绝处理服务时的策略(<strong>拒绝策略</strong>),有以下四种取值:<br>ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectExecutionException异常。<br>ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。<br>ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。<br>ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。</li></ul><p>线程池之间的参数协作如图所示(注意数字顺序):<br><img src="/img/post-img/19-6-4-2.png" alt="线程池参数协作图"><br><strong>1、任务优先向CorePool中提交,创建核心线程执行任务<br>2、在CorePool满了之后,任务被提交提交到任务队列,等待线程池空闲<br>3、在任务队列满了之后,但CorePool中还没有空闲线程,那么任务将被提交到maxPool中,创建非核心线程执行任务<br>4、msxPool满了之后执行task拒绝策略</strong><br>具体流程图如下:<br><img src="/img/post-img/19-6-4-3.png" alt="线程池流程图"></p><h2 id="Executors"><a href="#Executors" class="headerlink" title="Executors"></a>Executors</h2><p>  Executor框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架。<br>  无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。<br>  利用Executors框架可以非常方便的创建一个线程池,Java通过Executors提供四种线程池,分别为:</p><ul><li><strong>newSingleThreadExecutor</strong>:创建<strong>一个线程的线程池</strong>,在这个线程池中始终只有一个线程存在。如果线程池中的线程因为异常问题退出,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。</li><li><strong>newFixedThreadPool</strong>:创建<strong>固定大小的线程池</strong>。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。</li><li><strong>newCachedThreadPool</strong>:可根据实际情况,<strong>调整线程数量的线程池</strong>,线程池中的线程数量不确定,如果有空闲线程会优先选择空闲线程,如果没有空闲线程并且此时有任务提交会创建新的线程。在正常开发中并不推荐这个线程池,因为在极端情况下,会因为 newCachedThreadPool 创建过多线程而耗尽 CPU 和内存资源。</li><li><strong>newScheduledThreadPool</strong>:此线程池可以<strong>指定固定数量的线程来周期性的去执行</strong>。比如通过 scheduleAtFixedRate 或者 scheduleWithFixedDelay 来指定周期时间。</li></ul><p><strong>推荐使用ThreadPoolExecutor方式。</strong><br>  阿里的 Java 开发手册里有一条是不推荐使用 Executors 去创建线程池,而是推荐去使用 ThreadPoolExecutor 来创建。<br>  这样做的主要原因是:使用 Executors 创建线程池不会传入核心参数,而是采用的默认值,这样的话我们往往会忽略掉里面参数的含义,如果业务场景要求比较苛刻的话,存在资源耗尽的风险;另外采用 ThreadPoolExecutor 的方式可以让我们更加清楚地了解线程池的运行规则,不管是面试还是对技术成长都有莫大的好处。</p>]]></content>
<categories>
<category> 并发 </category>
</categories>
<tags>
<tag> 线程池 </tag>
</tags>
</entry>
<entry>
<title>Docker基础(二)</title>
<link href="/2019/06/02/Docker%E5%9F%BA%E7%A1%80%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<url>/2019/06/02/Docker%E5%9F%BA%E7%A1%80%EF%BC%88%E4%BA%8C%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="一、Docker数据管理"><a href="#一、Docker数据管理" class="headerlink" title="一、Docker数据管理"></a>一、Docker数据管理</h2><blockquote><p>  在实际使用Docker的过程中,往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,这必然涉容器的数据管理操作。<br>  容器中的数据管理主要由两种方式:<br>  1.数据卷:容器内数据直接映射到本地主机环境;<br>  2.数据卷容器:使用特定容器维护数据卷。</p></blockquote><h3 id="1、数据卷"><a href="#1、数据卷" class="headerlink" title="1、数据卷"></a>1、数据卷</h3><p>数据卷是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似Linux中的mount行为。</p><p>数据卷提供了很多有用的特性:</p><ul><li>数据卷可以在容器之间共享和重用,容器间传递数据将变得高效与方便;</li><li>对数据卷内数据的修改会立刻生效,无论是容器内操作还是本地操作;</li><li>对数据卷的更新不会影响镜像,解耦开应用和数据;</li><li>卷会一直存在,知道没有容器使用,可以安全地卸载它。<h4 id="1-1-创建数据卷"><a href="#1-1-创建数据卷" class="headerlink" title="1.1. 创建数据卷"></a>1.1. 创建数据卷</h4><blockquote><p>$ docker volume create -d local test<br>test<br>$ sudo ls -l /var/lib/docker/volumes<br>总用量 28</p></blockquote></li><li>rw——- 1 root root 32768 6月 2 17:39 metadata.db<br>drwxr-xr-x 3 root root 4096 6月 2 17:39 test</li></ul><p>除了create子命令外,volume还支持inspect(查看详细信息)、ls(列出已有数据卷)、prune(清除无用数据卷)、rm(删除数据卷)等。</p><h4 id="1-2-绑定数据卷"><a href="#1-2-绑定数据卷" class="headerlink" title="1.2. 绑定数据卷"></a>1.2. 绑定数据卷</h4><p>在创建容器时将主机本地的任意路径挂载到容器内作为数据卷,这种形式创建的数据卷称为绑定数据卷。</p><p>docker run命令使用-mount选项来使用数据卷。该选项支持三种类型的数据卷:</p><ul><li>volume:普通数据卷,映射到主机/var/lib/docker/volumes路径下;</li><li>bind:绑定数据卷,映射到主机指定路径下;</li><li>tmpfs:临时数据卷 ,只存在于内存中。</li></ul><blockquote><p>使用training/webapp镜像创建一个web容器,并创建一个数据卷挂载到容器的/opt/webapp目录:<br>$ docker run -d -P –name web –mount type=bind,source=/webapp,destination=/opt/webapp training/webapp python app.py<br>4c8d75fe28918c96c610200c1fb164dc1e10cbc78b93eef1000e26e3fa328619</p></blockquote><p>注意:本地目录必须是绝对路径,容器内目录可以为相对路径。</p><h3 id="2、数据卷容器"><a href="#2、数据卷容器" class="headerlink" title="2、数据卷容器"></a>2、数据卷容器</h3><h3 id="3、利用数据卷容器来迁移数据"><a href="#3、利用数据卷容器来迁移数据" class="headerlink" title="3、利用数据卷容器来迁移数据"></a>3、利用数据卷容器来迁移数据</h3>]]></content>
<categories>
<category> Docker </category>
</categories>
<tags>
<tag> Docker </tag>
</tags>
</entry>
<entry>
<title>Docker基础(一)</title>
<link href="/2019/05/31/Docker%E5%9F%BA%E7%A1%80%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<url>/2019/05/31/Docker%E5%9F%BA%E7%A1%80%EF%BC%88%E4%B8%80%EF%BC%89/</url>
<content type="html"><![CDATA[<blockquote><p>今天开始正式学习Docker,边学习边总结,任重而道远。</p></blockquote><ul><li>启动docker服务:systemctl start docker</li><li>启动守护进程:systemctl daemon-reload</li><li>重启docker服务:systemctl restart docker</li><li>关闭docker服务:systemctl stop docker</li></ul><p>[toc]</p><h2 id="一、Docker镜像"><a href="#一、Docker镜像" class="headerlink" title="一、Docker镜像"></a>一、Docker镜像</h2><h3 id="1-获取镜像"><a href="#1-获取镜像" class="headerlink" title="1. 获取镜像"></a>1. 获取镜像</h3><p>格式:<code>docker [image] pull NAME[:TAG]</code><br>例如:获取一个Ubuntu18.04系统的基础镜像:$ docker pull ubuntu:18.04<br>如果不显式指定TAG,则默认会选择latest标签,image指定镜像源,一般不用<br>pull子命令支持的选项:</p><ul><li>-a,–all-tags=true | false :是否获取仓库中的所有镜像,默认为否;</li><li>–disable-content-trust :取消镜像的内容校验,默认为真。</li></ul><p>下载镜像到本地后就可以随时使用该镜像了,例如利用该镜像创建一个容器,在其中运行bash应用,打印“Hello World”:<br><img src="/img/post-img/19-6-1-1.png" alt="使用镜像创建容器"></p><h3 id="2-查看镜像信息"><a href="#2-查看镜像信息" class="headerlink" title="2. 查看镜像信息"></a>2. 查看镜像信息</h3><h4 id="2-1、使用images命令列出镜像"><a href="#2-1、使用images命令列出镜像" class="headerlink" title="2.1、使用images命令列出镜像"></a>2.1、使用images命令列出镜像</h4><p>格式:<code>docker images</code>或者<code>docker image ls</code><br>在列出的信息中,可以看到几个字段:</p><blockquote><ul><li>REPOSITORY:来源于哪个仓库,比如ubuntu表示ubuntu系列的基础镜像;</li><li>TAG:镜像的标签信息,比如18.04、latest表示不同的版本信息。一般用来标记来自同一仓库的不同镜像。标签只是标记,并不能标识镜像内容;</li><li>IMAGE ID:镜像的ID(唯一标识),如果两个镜像的ID相同,说明它们实际上指向了同一个镜像,只是具有不同的标签而已;</li><li>CREATED:创建时间,镜像最后更新的时间;</li><li>SIZE:镜像大小,优秀的镜像往往体积都较小。只表示该镜像的逻辑大小,实际上相同的镜像在本地只会存储一份。</li></ul></blockquote><p>images子命令支持的选项:</p><ul><li>a,–all=true | false:列出所有(包括临时文件)镜像文件,默认为否;</li><li>–digests=trur | false:列出镜像的数字摘要值,默认为否;</li><li>-f,–filter=[]:过滤列出的镜像,如dangling=true只显示没有被使用的镜像;也可以指定带有特殊标注的镜像等;</li><li>–format=”TEMPLATE”:控制输出格式,如.ID代表ID信息,.Repository代表仓库信息等;</li><li>–no-trunc=true | false:对输出结果中太长的部分是否进行截断,如ID信息,默认为是;</li><li>-q,–quiet=true | false:仅输出ID信息,默认为否。</li></ul><p>更多子命令可通过<code>man docker-images</code>来查看。</p><h4 id="2-2、使用tag命令添加镜像标签"><a href="#2-2、使用tag命令添加镜像标签" class="headerlink" title="2.2、使用tag命令添加镜像标签"></a>2.2、使用tag命令添加镜像标签</h4><p>格式:docker tag 旧标签 新标签<br>例如:添加一个新的myubuntu:latest镜像标签:<br><code>$ docker tag ubuntu:latest myubuntu:latest</code><br>再次使用docker images,可以看到多了一个myubuntu:latest标签的镜像,之后,用户就可以用新的标签来使用这个镜像了。</p><h4 id="2-3、使用inspect命令查看详细信息"><a href="#2-3、使用inspect命令查看详细信息" class="headerlink" title="2.3、使用inspect命令查看详细信息"></a>2.3、使用inspect命令查看详细信息</h4><p>格式:docker [image] inspect 镜像标签<br>例如:<code>docker [image] inspect ubuntu:18.04</code><br>结果返回一个JSON格式的消息,如果只要其中的一项内容,可以使用-f来指定。例如获取镜像的Id:</p><blockquote><p>$ docker inspect -f .Id ubuntu:18.04<br>sha256:7698f282e5242af2b9d2291458d4e425c75b25b0008c1e058d66b717b4c06fa9</p></blockquote><h4 id="2-4、使用history命令查看镜像历史"><a href="#2-4、使用history命令查看镜像历史" class="headerlink" title="2.4、使用history命令查看镜像历史"></a>2.4、使用history命令查看镜像历史</h4><p>格式:docker history 镜像标签<br>例如:查看ubuntu:18.04镜像的创建过程:<br><code>$ docker history ubuntu:18.04</code></p><h3 id="3-搜寻镜像"><a href="#3-搜寻镜像" class="headerlink" title="3. 搜寻镜像"></a>3. 搜寻镜像</h3><p>格式:docker search [option] keyword<br>支持的命令选项主要包括:</p><ul><li>-f,–filter filter:过滤输出内容</li><li>–format string:格式化输出内容</li><li>–limit int:限制输出结果个数,默认为25个</li><li>–no-trunc:不截断输出结果</li></ul><p>例如:搜索官方提供的带nginx关键字的镜像:<br><code>$ docker search --filter=is-official=true nginx</code><br>搜索所有收藏数超过4的、关键词包括tensorflow的镜像:<br><code>$ docker search --filter=stars=4 tensorflow</code></p><h3 id="4-删除和清理镜像"><a href="#4-删除和清理镜像" class="headerlink" title="4. 删除和清理镜像"></a>4. 删除和清理镜像</h3><h4 id="4-1、使用标签删除镜像"><a href="#4-1、使用标签删除镜像" class="headerlink" title="4.1、使用标签删除镜像"></a>4.1、使用标签删除镜像</h4><p>格式:<code>docker rmi IMAGE [IMAGE...]</code>或者<code>docker image rm IMAGE [IMAGE...]</code><br>支持选项包括:</p><ul><li>-f,-force:强制删除镜像,即使有容器依赖它;</li><li>-no-prune:不要清理未带标签的额父镜像。</li></ul><p>例如:删除myubuntu:latest镜像:<br><code>$ docker rmi myubuntu:latest</code></p><h4 id="4-2、使用镜像ID来删除镜像"><a href="#4-2、使用镜像ID来删除镜像" class="headerlink" title="4.2、使用镜像ID来删除镜像"></a>4.2、使用镜像ID来删除镜像</h4><p>格式:<code>docker rmi IMAGE-ID [IMAGE-ID...]</code><br>会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身。注意,当有该镜像创建的容器存在时,镜像文件默认是无法删除的(<strong><code>docker ps -a</code>查看本机所有容器</strong>)。如果要强行删除,可以用 -f 选项,但是不推荐使用,正确的做法是先删除依赖该镜像的所有容器,再来删除镜像(<strong><code>docker rm 容器</code> 删除指定容器</strong>)。</p><h4 id="4-3、清理镜像"><a href="#4-3、清理镜像" class="headerlink" title="4.3、清理镜像"></a>4.3、清理镜像</h4><p>使用docker一段时间后,系统中可能会遗留一些临时的镜像文件,以及一些没有被使用的镜像,这时用到镜像清理命令。<br>格式:docker image prune<br>支持的选项包括:</p><ul><li>-a,–all:删除所有无用镜像,不光是临时文件;</li><li>-filter filter:只清除符合给定过滤器的镜像;</li><li>-f,-force:强制删除镜像,而不进行提示确认。</li></ul><h3 id="5-创建镜像"><a href="#5-创建镜像" class="headerlink" title="5. 创建镜像"></a>5. 创建镜像</h3><h4 id="5-1、基于已有容器创建"><a href="#5-1、基于已有容器创建" class="headerlink" title="5.1、基于已有容器创建"></a>5.1、基于已有容器创建</h4><p>格式:docker [container] commit [OPTIONS] CONTAINER [REPOSITORY [:TAG]]<br>主要选项包括:</p><ul><li>-a,–author=””:作者信息;</li><li>-c,–change=[]:提交的时候执行dockerfile指令,包括CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | ONBUILD | USER | VOLUME | WORKDIR等;</li><li>-m,–message=””:提交消息;</li><li>-p,–pause=true:提交时暂停容器运行。</li></ul><h4 id="5-2、基于本地模板导入"><a href="#5-2、基于本地模板导入" class="headerlink" title="5.2、基于本地模板导入"></a>5.2、基于本地模板导入</h4><p>格式:docker [image] import [OPTIONS] file|URL| - [REPOSITORY[:TAG]]<br>通过下载<a href="http://openvz.org/Download/templates/precreated" target="_blank" rel="noopener">OpenVz</a>提供的模板压缩包导入:<br><code>$ cat ubuntu-18.04-x86_64-minimal.tag.gz | docker import - ubuntu:18.04</code></p><h4 id="5-3、基于Dockerfile创建"><a href="#5-3、基于Dockerfile创建" class="headerlink" title="5.3、基于Dockerfile创建"></a>5.3、基于Dockerfile创建</h4><p>Dockerfile是一个文本文件,利用给定的指令描述基于某个父镜像创建新镜像的过程。</p><h3 id="6-存出和载入镜像"><a href="#6-存出和载入镜像" class="headerlink" title="6. 存出和载入镜像"></a>6. 存出和载入镜像</h3><h4 id="6-1、存出镜像"><a href="#6-1、存出镜像" class="headerlink" title="6.1、存出镜像"></a>6.1、存出镜像</h4><p>格式:docker [image] save<br>该命令支持 -o、-output string参数,导出镜像到指定的文件中。<br>例如,导出本地的ubuntu:18.04镜像为文件ubuntu_18.04.tar:<br><code>$ docker save -o ubuntu_18.04.tar ubuntu:18.04</code><br>之后用户就可以通过复制ubuntu_18.04.tar文件将该镜像分享给他人。</p><h4 id="6-2、载入镜像"><a href="#6-2、载入镜像" class="headerlink" title="6.2、载入镜像"></a>6.2、载入镜像</h4><p>格式:docker [image] load<br>支持 -i、-input string选项,从指定文件中读入镜像内容。<br>例如,从文件ubuntu_18.04.tar导入镜像到本地镜像列表:<br><code>$ docker load -i ubuntu_18.04.tar</code></p><h3 id="7-上传镜像"><a href="#7-上传镜像" class="headerlink" title="7. 上传镜像"></a>7. 上传镜像</h3><p>格式:docker [image] push NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_POST] / ] NAME [:TAG]<br>第一次上传时会提示输入登录信息或进行注册,之后登录信息会记录到本地的~/.docker目录下。</p><h2 id="二、Docker容器"><a href="#二、Docker容器" class="headerlink" title="二、Docker容器"></a>二、Docker容器</h2><h3 id="1-创建容器"><a href="#1-创建容器" class="headerlink" title="1. 创建容器"></a>1. 创建容器</h3><h4 id="1-1、新建容器"><a href="#1-1、新建容器" class="headerlink" title="1.1、新建容器"></a>1.1、新建容器</h4><p>格式:docker [container] create</p><blockquote><p>$ docker create -it ubuntu:latest<br>3fa6a10fb9243158d5673430f1930ef5b4b8af44875f9822a229d2442a9c415c</p></blockquote><p>新建的容器处于停止状态,可以使用<code>docker [container] start</code>命令来启动它。<br>由于容器是整个Docker技术栈的核心,create命令支持的选项十分复杂。选项主要包括以下几大类:与容器运行模式相关、与容器环境配置相关、与容器资源限制和安全保护相关。<br><img src="/img/post-img/19-6-1-2.jpg" alt="与容器运行模式相关"><br><img src="/img/post-img/19-6-1-3.jpg" alt="其他选项"><br><img src="/img/post-img/19-6-1-4.jpg" alt="续"></p><h4 id="1-2、启动容器"><a href="#1-2、启动容器" class="headerlink" title="1.2、启动容器"></a>1.2、启动容器</h4><p>格式:docker [container] start CONTAINER ID<br>可以通过<code>docker ps</code>命令查看运行中的容器</p><h4 id="1-3、创建并启动容器"><a href="#1-3、创建并启动容器" class="headerlink" title="1.3、创建并启动容器"></a>1.3、创建并启动容器</h4><p>格式:docker [container] run<br>等价于先执行docker create命令,再执行docker start命令。</p><blockquote><p>$ docker run ubuntu /bin/echo ‘Hello World’<br>Hello World</p></blockquote><p>利用run命令来创建并启动容器时,Docker在后台运行的标准操作包括:</p><ul><li>检查本地是否存在指定的镜像,不存在就从公有仓库下载;</li><li>利用镜像创建一个容器,并启动该容器;</li><li>分配一个文件系统给容器,并在只读的镜像层外面挂载一层可读写层;</li><li>从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去;</li><li>从网桥的地址池配置一个IP地址给容器;</li><li>执行用户指定的应用程序;</li><li>执行完毕后容器被自动终止。</li></ul><h4 id="1-4、守护态运行"><a href="#1-4、守护态运行" class="headerlink" title="1.4、守护态运行"></a>1.4、守护态运行</h4><p>通过添加 -d 参数来实现容器在后台以守护态形式运行。</p><blockquote><p>$ docker run -d ubuntu /bin/sh -c “while true; do echo hello world; sleep 1; done”<br>171067c30ef818f79d82f173331e59e9ce2297857106c3fface447a90aa81fd3</p></blockquote><p>容器启动后会返回一个唯一的id,通过<code>docker ps</code>或<code>docker container ls</code>来查看容器信息。</p><h4 id="1-5、查看容器输出"><a href="#1-5、查看容器输出" class="headerlink" title="1.5、查看容器输出"></a>1.5、查看容器输出</h4><p>格式:docker [container] logs<br>支持的选项:</p><ul><li>-details:打印详细信息;</li><li>-f,-follow:持续保持输出;</li><li>-since string:输出从某个时间开始的日志;</li><li>-tail string:输出最近的若干日志;</li><li>-t,-timestamps:显示时间戳信息;</li><li>-until string:输出某个时间之前的日志。</li></ul><h3 id="2-停止容器"><a href="#2-停止容器" class="headerlink" title="2. 停止容器"></a>2. 停止容器</h3><h4 id="2-1、暂停容器"><a href="#2-1、暂停容器" class="headerlink" title="2.1、暂停容器"></a>2.1、暂停容器</h4><p>格式:docker [container] pause CONTAINER [CONTAINER…]<br>处于暂停状态的容器,可以使用<code>docker [container] unpause CONTAINER [CONTAINER...]</code> 命令来恢复到运行状态。</p><h4 id="2-2、终止容器"><a href="#2-2、终止容器" class="headerlink" title="2.2、终止容器"></a>2.2、终止容器</h4><p>格式:docker [container] stop [-t | –time [=10]] [CONTAINER…]<br>该命令会首先向容器发送SIGTERM信号,等待一段超时时间(默认为10秒)后,再发送SIGKILL信号来终止容器。<br>还可以通过<code>docker [container] kill</code> 直接发送SIGKILL信号来强行终止容器。<br>此时可以通过<code>docker container prune</code> 命令清除掉所有处于停止状态的容器。<br>处于终止状态的容器,可以通过<code>docker [container] start</code> 命令来重新启动。<br><code>docker [container] restart</code> 命令会将一个运行态的容器先终止,然后再重新启动。</p><h3 id="3-进入容器"><a href="#3-进入容器" class="headerlink" title="3. 进入容器"></a>3. 进入容器</h3><p>在使用 -d 参数时,容器启动后会进入后台,用户无法看到容器中的信息,也无法进行操作,这个时候如果需要进入容器操作,就需要用到此命令。</p><h4 id="3-1、attach命令"><a href="#3-1、attach命令" class="headerlink" title="3.1、attach命令"></a>3.1、attach命令</h4><p>格式:docker [container] attach<br>支持三个主要选项:</p><ul><li>–detach-keys[=[]]:指定退出attach模式的快捷键序列,默认是CTRL-p,CTRL-q;</li><li>–no-stdin=true | false:是否关闭标准输入,默认是保持打开;</li><li>–sig-proxy=true | false:是否代理收到的系统信号给应用进程,默认是true。</li></ul><p>然而使用attach命令有时候并不方便。当多个窗口同时attach到同一个容器的时候,所有窗口都会同步显示;当某个窗口因命令阻塞时,其他窗口也无法执行操作了。</p><h4 id="3-2、exec命令"><a href="#3-2、exec命令" class="headerlink" title="3.2、exec命令"></a>3.2、exec命令</h4><p>格式:docker [container] exec<br>比较重要的参数有:</p><ul><li>-d:在容器中后台执行命令;</li><li>–detach-keys=””:指定将容器切回后台的按键;</li><li>-e:指定环境变量列表;</li><li>-i:打开标准输入接受用户输入命令,默认值为false;</li><li>–privileged=true | false:是否执行命令以高权限,默认值为false;</li><li>-t:分配伪终端,默认值为false;</li><li>-u:执行命令的用户名或ID</li></ul><h3 id="4-删除容器"><a href="#4-删除容器" class="headerlink" title="4. 删除容器"></a>4. 删除容器</h3><p>格式:docker [container] rm<br>主要支持的选项:</p><ul><li>-f:是否强行终止并删除一个运行中的容器;</li><li>-l:删除容器的连接,但保留容器;</li><li>-v:删除容器挂载的数据卷。</li></ul><h3 id="5-导入和导出容器"><a href="#5-导入和导出容器" class="headerlink" title="5. 导入和导出容器"></a>5. 导入和导出容器</h3><p>容器的导入导出是为了实现容器从一个系统迁移到另外 一个系统。<br>导出容器:<code>docker [container] export</code> -o 参数指定导出的tar文件名<br>导入容器:<code>docker [container] import</code> </p><p>docker load与docker import的区别和联系:<br>联系:docker load用来导入镜像存储文件到本地镜像库,docker import用来导入一个容器快照到本地镜像库。<br>区别:容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积更大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。</p>]]></content>
<categories>
<category> Dcoker </category>
</categories>
<tags>
<tag> Docker </tag>
</tags>
</entry>
<entry>
<title>Ubuntu环境下安装配置Docker</title>
<link href="/2019/05/30/Ubuntu%E7%8E%AF%E5%A2%83%E4%B8%8B%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AEDocker/"/>
<url>/2019/05/30/Ubuntu%E7%8E%AF%E5%A2%83%E4%B8%8B%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AEDocker/</url>
<content type="html"><![CDATA[<blockquote><p>安装环境:Ubuntu 18.04</p></blockquote><h3 id="1-系统要求"><a href="#1-系统要求" class="headerlink" title="1. 系统要求"></a>1. 系统要求</h3><p>Ubuntu系统对Docker的支持十分成熟,只要是64位的即可。<br>Docker目前支持的最低的Ubuntu版本为14.04 LTS,但实际上从稳定性上考虑,推荐使用16.04 LTS或18.04 LTS版本,并且系统内核越新越好,以支持Docker最新的特性。</p><blockquote><p>查看内核版本详细信息:(二选其一即可)<br>$ uname -a<br>$ cat /proc/version</p></blockquote><h3 id="2-添加镜像源"><a href="#2-添加镜像源" class="headerlink" title="2. 添加镜像源"></a>2. 添加镜像源</h3><p>首先需要安装apt-transport-https等软件包支持https协议的源:</p><blockquote><p>$ sudo apt-get update<br>$ sudo apt-get install <br> apt-transport-https <br> ca-certificates <br> curl <br> software-properties-common</p></blockquote><p>添加源的gpg密钥:</p><blockquote><p>$ curl -fsSL <a href="https://download.docker.com/linux/ubuntu/gpg" target="_blank" rel="noopener">https://download.docker.com/linux/ubuntu/gpg</a> | sudo apt-get add -<br>OK</p></blockquote><p>确认公钥:</p><blockquote><p>$ sudo apt-key fingerprint 0EBFCD88</p></blockquote><p>获取当前操作系统的代号:</p><blockquote><p>$ lsb_release -cs<br>bionic</p></blockquote><p>添加Docker稳定版的官方软件源:(注意“bionic”处与查看到的代号相一致)</p><blockquote><p>$ sudo add-apt-erpository <br>“deb [arch=amd64] <a href="https://download.docker.com/linux/ubuntu" target="_blank" rel="noopener">https://download.docker.com/linux/ubuntu</a> <br>bionic stable”</p></blockquote><p>添加成功后,再次更新apt软件包缓存:</p><blockquote><p>$ sudo apt-get update</p></blockquote><h3 id="3-开始安装Docker"><a href="#3-开始安装Docker" class="headerlink" title="3. 开始安装Docker"></a>3. 开始安装Docker</h3><p>添加完源之后就可以安装最新版的Docker了,软件包名称为docker-ce,代表是社区版本:</p><blockquote><p>$ sudo apt-get install -y docker-ce</p></blockquote><p>如果系统中存在较旧版本的Docker,会提示是否先删除,选择是即可。<br>安装成功后,会自动启动Docker服务。<br>查看docker信息:</p><blockquote><p>sudo docker version</p></blockquote><p><img src="/img/post-img/19-5-30-1.png" alt="docker信息"></p><h3 id="4-配置Docker服务"><a href="#4-配置Docker服务" class="headerlink" title="4. 配置Docker服务"></a>4. 配置Docker服务</h3><p>为了避免每次使用Docker命令时都需要切换到特权身份,可以将当前用户加入安装中自动创建的docker用户组:(USER_NAME部分为当前用户名)</p><blockquote><p>sudo username -aG docker USER_NAME</p></blockquote><p>用户更新组信息,退出并重新登录后即可生效。</p><p>Docker服务启动实际上是调用了dockerd命令,支持多种启动参数。因此,用户可以直接通过执行dockerd命令来启动Docker服务,如下面的命令启动Docker服务,开启debug模式,并监听在本地的2376端口:</p><blockquote><p>$ dockerd -D -H tcp://127.0.0.1:2376</p></blockquote><p>这些选项可以写入/etc/docker/路径下的daemon.json文件中,由dockerd服务启动时读取:</p><blockquote><p>{<br>  ”debug”: true,<br>  ”hosts”: [“tcp://127.0.0.1:2376”]<br>}</p></blockquote><p>当然,操作系统也对Docker服务进行了封装,以Ubuntu为例,Docker服务的默认配置文件为/etc/default/docker,可以通过修改其中的DOCKER_OPTS来修改服务启动的参数,例如让Docker服务开启网络2375端口的监听:</p><blockquote><p>DOCKER_OPTS=”$DOCKER_OPTS -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock”</p></blockquote><p>修改之后,通过service命令来重启Docker服务:</p><blockquote><p>sudo service docker restart</p></blockquote><p>另外,使用国外的镜像源下载速度慢,修改镜像源可以加快下载速度。切换到root权限,打开/etc/docker/daemon.json文件,添加如下内容:</p><blockquote><p>{<br>“registry-mirrors”: [<br>“<a href="https://kfwkfulq.mirror.aliyuncs.com"" target="_blank" rel="noopener">https://kfwkfulq.mirror.aliyuncs.com"</a>,<br>“<a href="https://2lqq34jg.mirror.aliyuncs.com"" target="_blank" rel="noopener">https://2lqq34jg.mirror.aliyuncs.com"</a>,<br>“<a href="https://pee6w651.mirror.aliyuncs.com"" target="_blank" rel="noopener">https://pee6w651.mirror.aliyuncs.com"</a>,<br>“<a href="https://registry.docker-cn.com"" target="_blank" rel="noopener">https://registry.docker-cn.com"</a>,<br>“<a href="http://hub-mirror.c.163.com"" target="_blank" rel="noopener">http://hub-mirror.c.163.com"</a><br>],<br>“dns”: [“8.8.8.8”,”8.8.4.4”]<br>}</p></blockquote><p>此外,如果服务不正常,可以通过查看Docker服务的日志信息来解决问题,Ubuntu系统上可以执行<code>journalctl -u docker.service</code></p><p>每次重启Docker服务后,可以通过查看docker信息(<code>docker info</code>命令),确保服务已经正常运行。</p>]]></content>
<categories>
<category> 环境配置 </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> 环境搭建 </tag>
<tag> Ubuntu </tag>
<tag> docker </tag>
</tags>
</entry>
<entry>
<title>线程基本概念</title>
<link href="/2019/05/28/%E7%BA%BF%E7%A8%8B%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/"/>
<url>/2019/05/28/%E7%BA%BF%E7%A8%8B%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/</url>
<content type="html"><![CDATA[<h2 id="一、程序、进程与线程"><a href="#一、程序、进程与线程" class="headerlink" title="一、程序、进程与线程"></a>一、程序、进程与线程</h2><p>首先区分一下程序、进程与线程这三个之间的概念。</p><blockquote><p> 程序 (Program):程序是一段静态的代码,它是应用程序执行的蓝本</p></blockquote><blockquote><p>进程 (Process):进程是指一种正在运行的程序,有自己的地址空间。进<strong>程是分配资源的最小单位</strong>,一个进程可以生成多个线程,这些线程拥有共享的进程资源。<br>进程的特点:动态性、并发性、独立性<br>并发和并行的区别:多个CPU同时执行多个任务是并发;一个CPU同时执行多个任务(采用时间片)是并行。</p></blockquote><blockquote><p>线程 (Thread):进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。<strong>线程是CPU调度的基本单位</strong>。<br>线程又被称为轻量级进程(Lightweight Process)<br>如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。<br>线程的特点:轻量级进程、独立调度的基本单位、可并发执行、共享进程资源。</p></blockquote><p>线程和进程的区别如下图:<br><img src="/img/post-img/19-5-28-1.png" alt="线程与进程"></p><h2 id="二、线程的创建和启动"><a href="#二、线程的创建和启动" class="headerlink" title="二、线程的创建和启动"></a>二、线程的创建和启动</h2><h4 id="1-线程的创建"><a href="#1-线程的创建" class="headerlink" title="1.线程的创建"></a>1.线程的创建</h4><p>一般来说创建线程有三种方式:<br>方式一:继承java.lang.Thread类,覆写run()方法<br>方式二:实现java.lang.Runnable接口,实现run()方法<br>方式三:实现java.util.current.Callable接口,实现call()方法</p><p>这三种创建方式的比较:</p><ul><li>继承Thread类方式的多线程:<br>  优势:编写简单;<br>  劣势:无法继承其他父类</li><li>实现Runnable接口方式的多线程:<br>  优势:可以继承其他类,多线程可共享同一个Runnable对象<br>  劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThread()方法。</li><li>实现Callable接口方式的多线程:<br>  优势:功能更强大,可以有返回值,支持泛型的返回值,可以抛出异常<br>  劣势:需要借助FutureTask,比如返回结果。<br>  Future接口:可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask是Future接口唯一的实现类,FutureTask同时实现了Runnable、Future接口。它可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。</li></ul><p><strong>实现Runnable接口方式要通用一些。</strong></p><h4 id="2-线程的启动"><a href="#2-线程的启动" class="headerlink" title="2.线程的启动"></a>2.线程的启动</h4><p>新的线程不会自动开始运行,必须通过start方法启动。<br><strong>不能直接调用run()方法来启动线程,否则run()将作为一个普通方法立即执行,执行完毕前其他线程无法并发执行</strong>。<br>java程序启动时,会立即创建主线程,main就是在这个线程上运行。当不再产生新线程时,程序就是单线程的。</p><ul><li>继承Thread类方式的多线程:<br>在主线程(主函数)创建继承了Thread类的类的实例,直接调用start方法:</li></ul><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token keyword">extends</span> <span class="token class-name">Thread</span><span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//线程体</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"threadTest"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//创建子类对象</span> Test test <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//调用start方法</span> test<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//不要直接调用run方法</span> <span class="token comment" spellcheck="true">//test.run();</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><ul><li>实现Runnable接口方式的多线程:<br>创建实例,通过代理线程启动线程:</li></ul><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span><span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//线程体</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"RunnableTest"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//真实角色</span> Test test <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//代理,同时为线程重命名为Runnable</span> Thread testProxy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>test<span class="token punctuation">,</span><span class="token string">"Runnable"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//启动线程</span> test<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><ul><li>实现Callable接口方式的多线程:</li></ul><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token keyword">implements</span> <span class="token class-name">Callable</span><span class="token operator"><</span>Integer<span class="token operator">></span><span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> Integer <span class="token function">call</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception<span class="token punctuation">{</span> <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"i:"</span><span class="token operator">+</span>i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> i<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException<span class="token punctuation">,</span> ExecutionException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//指定线程池存放线程数</span> ExecutorService ser <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//创建线程</span> Test test1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Test test2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取值</span> Future<span class="token operator"><</span>Integer<span class="token operator">></span> result1 <span class="token operator">=</span> ser<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span>test1<span class="token punctuation">)</span><span class="token punctuation">;</span> Future<span class="token operator"><</span>Integer<span class="token operator">></span> result2 <span class="token operator">=</span> ser<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span>test2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> num1 <span class="token operator">=</span> result1<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> num2 <span class="token operator">=</span> result2<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"num1:"</span><span class="token operator">+</span>num1<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"num2:"</span><span class="token operator">+</span>num2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//停止服务</span> ser<span class="token punctuation">.</span><span class="token function">shutdownNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><h2 id="三、线程的生命周期"><a href="#三、线程的生命周期" class="headerlink" title="三、线程的生命周期"></a>三、线程的生命周期</h2><p>在线程的生命周期中,共有五个状态,分别是新生状态、就绪状态、运行状态、阻塞状态和死亡状态。<br><img src="/img/post-img/19-5-28-2.png" alt="状态转换图1"></p><ul><li>新生状态(New)<br>  用new关键字建立一个线程对象后,该线程对象就处于新生状态。<br>  此时<strong>JVM为其分配内存</strong>,并<strong>初始化其成员变量的值</strong>;线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体;处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。</li></ul><ul><li>就绪状态(Runnable)<br>  当线程对象调用了start()方法之后,该线程处于就绪状态。<br>  此时JVM会为其<strong>创建方法调用栈</strong>和<strong>程序计数器</strong>;<br>  处于就绪状态的线程具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为<strong>可运行池</strong>而不是可运行队列。因为CPU的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU,线程并没有开始运行,当系统为一个线程分配CPU时间片后,它就会从就绪状态进入运行状态,该动作称之为“<strong>CPU调度</strong>”。</li></ul><ul><li>运行状态(Running)<br>  在运行状态的线程执行自己的run方法中代码,直到<strong>等待某资源而阻塞</strong>或<strong>完成任务而死亡</strong>。<br>  如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。<br>  如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态;如果在一个多处理器的机器上,将会有多个线程并行执行,处于运行状态;当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象;<br>  处于运行状态的线程最为复杂,它不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了),线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。线程状态可能会变为阻塞状态、就绪状态和死亡状态。比如:<br>  对于采用<strong>抢占式策略</strong>的系统而言,系统会给每个可执行的线程分配一个时间片来处理任务;当该时间片用完后,系统就会<strong>剥夺该线程所占用的资源</strong>,让其他线程获得执行的机会。线程就会又从运行状态变为就绪状态,重新等待系统分配资源;<br>  对于采用<strong>协作式策略</strong>的系统而言,只有当一个线程调用了它的yield()方法后才会放弃所占用的资源,也就是<strong>必须由该线程主动放弃所占用的资源</strong>,线程就会又从运行状态变为就绪状态。</li></ul><ul><li>阻塞状态(Blocked)<br>  当发生如下情况时,线程将会进入阻塞状态:<br>  <strong>线程调用sleep()方法</strong>,主动放弃所占用的处理器资源,暂时进入中断状态(不会释放持有的对象锁),时间到后等待系统分配CPU继续执行;<br>  <strong>线程调用一个阻塞式IO方法</strong>,在该方法返回之前,该线程被阻塞;<br>  <strong>线程试图获得一个同步监视器</strong>,但该同步监视器正被其他线程所持有;<br>  <strong>程序调用了线程的suspend方法将线程挂起</strong>;(suspend方法已经废弃)<br>  <strong>线程调用wait</strong>,等待notify/notifyAll唤醒时(会释放持有的对象锁)<br>  阻塞状态的分类:<br>  <strong>等待阻塞</strong>:运行状态中的线程<strong>执行wait()方法</strong>,使本线程进入到等待阻塞状态;<br>  <strong>同步阻塞</strong>:线程在<strong>获取synchronized同步锁失败</strong>(因为锁被其它线程占用),它会进入到同步阻塞状态;<br>  <strong>其他阻塞</strong>:通过调用线程的 sleep()或join()或发出I/O请求 时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;<br>  在阻塞状态的线程只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所决定。当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器资源(或者yield方法自动让出资源)时,该线程进入就绪状态。</li></ul><ul><li>死亡状态(Dead)<br>  死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性的终止,如通过执行stop方法来终止(不推荐),三是线程抛出未捕获的Exception或者Error。<br>  处于死亡状态的线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。</li></ul><p>详细的线程状态转换图:</p><p><img src="/img/post-img/19-5-28-3.png" alt="状态转换图2"></p><p><img src="/img/post-img/19-5-28-4.png" alt="状态转换图3"></p><h2 id="四、线程相关概念"><a href="#四、线程相关概念" class="headerlink" title="四、线程相关概念"></a>四、线程相关概念</h2><h3 id="1、锁"><a href="#1、锁" class="headerlink" title="1、锁"></a>1、锁</h3><p>  当多个线程对同一个共享变量/对象进行操作,即使是最简单的操作,比如i++,在处理上实际也涉及到<strong>读取</strong>、<strong>自增</strong>、<strong>赋值</strong>这三个操作,也就是说,这中间可能存在时间差,导致多个线程没有按照程序编写者所期望的顺序去执行,出现错位,从而导致最终结果与预期的不一致。<br>  java中的多线程同步是通过锁的概念来体现的,锁不是一个对象,也不是一个具体的东西,而是一种机制的名称。锁机制需要保证如下两种特性:</p><ul><li><strong>互斥性</strong>:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问,互斥性我们也往往称之为操作的原子性。</li><li><strong>可见性</strong>:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。</li></ul><h3 id="2、挂起与休眠、阻塞与非阻塞"><a href="#2、挂起与休眠、阻塞与非阻塞" class="headerlink" title="2、挂起与休眠、阻塞与非阻塞"></a>2、挂起与休眠、阻塞与非阻塞</h3><ul><li><strong>挂起(Suspend)</strong>:当线程被挂起的时候,其会失去CPU的使用时间,直到被其他线程(用户线程或调度线程)唤醒。</li><li><strong>休眠(Sleep)</strong>:同样会失去CPU的使用时间,但是在过了指定的休眠时间之后,它会<strong>自动激活</strong>,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。</li><li><strong>阻塞(Block)</strong>:在线程执行时,所<strong>需要的资源不能得到</strong>,则线程被挂起,直到满足可操作的条件。</li><li><strong>非阻塞(UnBlock)</strong>:在线程执行时,所需要的资源不能得到,但线程不是被挂起等待,而是<strong>继续执行其余事情</strong>,待条件满足了之后,收到了<strong>通知</strong>(同样是守护线程去做)再执行。<blockquote><p>  挂起和休眠是独立的操作系统的概念,而阻塞与非阻塞则是在资源不能得到时的两种处理方式,不限于操作系统,当资源申请不到时,要么挂起线程等待、要么继续执行其他操作,资源被满足后再通知该线程重新请求。显然非阻塞的效率要高于阻塞,相应的实现的复杂度也要高一些。</p></blockquote></li></ul><p>  在Java中显式的挂起之前是通过Thread的suspend方法来体现,现在此概念已经消失,原因是suspend/resume方法已经被废弃,它们容易产生死锁,在suspend方法的注释里有这么一段话:<strong>当suspend的线程持有某个对象锁,而resume它的线程又正好需要使用此锁的时候,死锁就产生了。</strong><br>  所以,现在的JDK版本中,挂起是<strong>JVM的系统行为</strong>,程序员无需干涉。休眠的过程中也不会释放锁,但它一定会在某个时间后被唤醒,所以不会死锁。现在我们所说的挂起,往往并非指编写者的程序里主动挂起,而是由操作系统的线程调度器去控制。<br>  相应地有必要提下java.lang.Object的wait/notify,这两个方法同样是等待/通知,但它们的前提是<strong>已经获得了锁</strong>,且在wait(等待)期间会释放锁。在wait方法的注释里明确提到:<strong>线程要调用wait方法,必须先获得该对象的锁</strong>,在调用wait之后,当前线程释放该对象锁并进入休眠(这里到底是进入休眠还是挂起?文档没有细说,从该方法能指定等待时间来看,更可能是休眠,没有指定等待时间的,则可能是挂起,不管如何,在休眠/挂起之前,JVM都会从当前线程中把该对象锁释放掉),只有以下几种情况下会被唤醒:<strong>其他线程调用了该对象的notify或notifyAll、当前线程被中断、调用wait时指定的时间已到</strong>。</p><h3 id="3、内核态与用户态"><a href="#3、内核态与用户态" class="headerlink" title="3、内核态与用户态"></a>3、内核态与用户态</h3><p>  有一些系统级的调用,比如:清除时钟、创建进程等这些系统指令,如果这些底层系统级指令能够被应用程序任意访问的话,那么后果是危险的,系统随时可能崩溃,所以 CPU将所执行的指令设置为多个特权级别,在硬件执行每条指令时都会校验指令的特权,比如:Intel x86架构的CPU将特权分为0-3四个特权级,0级的权限最高,3权限最低。<br>  而<strong>操作系统根据系统调用的安全性分为两种:内核态和用户态。内核态执行的指令的特权是0,用户态执行的指令的特权是3</strong>。<br>  当一个任务(进程)执行系统调用而进入内核指令执行时,进程处于内核运行态(或简称为内核态);<br>  当任务(进程)执行自己的代码时,进程就处于用户态。<br>  在执行系统级调用时,需要将变量传递进去、可能要拷贝、计数、保存一些上下文信息,然后内核态执行完成之后需要再将参数传递到用户进程中去,这个切换的代价相对来说是比较大的,所以应该是<strong>尽量避免频繁地在内核态和用户态之间切换</strong>。<br>  Java并没有自己的线程模型,而是使用了操作系统的原生线程!线程方面的事在操作系统来说属于系统级的调用,需要在内核态完成,所以如果频繁地执行线程挂起、调度,就会频繁造成在内核态和用户态之间切换,影响效率。<br>  JDK5之前的synchronized效率低下,是因为在阻塞时线程就会被挂起、然后等待重新调度,而线程操作属于内核态,这频繁的挂起、调度使得操作系统频繁处于内核态和用户态的转换,造成频繁的变量传递、上下文保存等,从而性能较低。</p><h3 id="4、Main线程"><a href="#4、Main线程" class="headerlink" title="4、Main线程"></a>4、Main线程</h3><p>  <strong>main线程是个非守护线程,不能设置为守护线程。</strong><br>  这是因为,Main线程是由Java虚拟机在启动的时候创建的。main方法开始执行的时候,主线程已经创建好并在运行了。对于运行中的线程,调用Thread.setDaemon()会抛出异常Exception in thread “main” java.lang.IllegalThreadStateException。<br>  <strong>Main线程结束,其他线程一样可以正常运行</strong><br>  主线程,只是个普通的非守护线程,用来启动应用程序,不能设置成守护线程;除此之外,它跟其他非守护线程没有什么不同。主线程执行结束,其他线程一样可以正常执行。线程其实并不存在互相依赖的关系,一个线程的死亡从理论上来说,不会对其他线程有什么影响。<br>  <strong>Main线程结束,其他线程也可以立刻结束,当且仅当这些子线程都是守护线程</strong><br>  Java虚拟机(相当于进程)退出的时机是:虚拟机中所有存活的线程都是守护线程。只要还有存活的非守护线程虚拟机就不会退出,而是等待非守护线程执行完毕;反之,如果虚拟机中的线程都是守护线程,那么不管这些线程的死活java虚拟机都会退出。</p><h3 id="5、并发与并行"><a href="#5、并发与并行" class="headerlink" title="5、并发与并行"></a>5、并发与并行</h3><p>  并发和并行的区别就是:一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务。前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生。<br>  并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。<br>  并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。<br>  来个比喻:并发和并行的区别就是一个人同时吃三个馒头和三个人同时吃三个馒头。</p><blockquote><p>单就一个CPU而言两个线程可以解决线程阻塞造成的不流畅问题,其本身运行效率并没有提高,多CPU的并行运算才真正解决了运行效率问题,这也正是并发和并行的区别。</p></blockquote>]]></content>
<categories>
<category> 并发 </category>
</categories>
<tags>
<tag> 线程 </tag>
</tags>
</entry>
<entry>
<title>ORM与反射</title>
<link href="/2019/05/25/ORM%E4%B8%8E%E5%8F%8D%E5%B0%84/"/>
<url>/2019/05/25/ORM%E4%B8%8E%E5%8F%8D%E5%B0%84/</url>
<content type="html"><![CDATA[<h2 id="ORM(Object-Relation-Mapping)"><a href="#ORM(Object-Relation-Mapping)" class="headerlink" title="ORM(Object Relation Mapping)"></a>ORM(Object Relation Mapping)</h2><p><strong>对象关系模型</strong><br>关系模型(数据库表): 表、字段、字段类型<br>对象模型(java实体类):类、属性、属性类型<br>通过ORM框架解除模型之间的阻抗。</p><h2 id="利用反射实现ORM中的准备预编译SQL语句"><a href="#利用反射实现ORM中的准备预编译SQL语句" class="headerlink" title="利用反射实现ORM中的准备预编译SQL语句"></a>利用反射实现ORM中的准备预编译SQL语句</h2><ol><li>自定义注解:</li></ol><p>Column.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>ElementType<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Retention<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>RetentionPolicy<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Target<span class="token punctuation">;</span><span class="token annotation punctuation">@Target</span><span class="token punctuation">(</span>ElementType<span class="token punctuation">.</span>FIELD<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//指定标注位置为属性上</span><span class="token annotation punctuation">@Retention</span><span class="token punctuation">(</span>RetentionPolicy<span class="token punctuation">.</span>RUNTIME<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//指定在运行期间有效</span><span class="token keyword">public</span> @<span class="token keyword">interface</span> <span class="token class-name">Column</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//配置映射字段名称</span> String <span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>PK.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>ElementType<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Retention<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>RetentionPolicy<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Target<span class="token punctuation">;</span><span class="token annotation punctuation">@Target</span><span class="token punctuation">(</span>ElementType<span class="token punctuation">.</span>FIELD<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//指定标注位置为字段上</span><span class="token annotation punctuation">@Retention</span><span class="token punctuation">(</span>RetentionPolicy<span class="token punctuation">.</span>RUNTIME<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//指定在运行期间有效</span><span class="token keyword">public</span> @<span class="token keyword">interface</span> <span class="token class-name">PK</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//是否自动增长,默认为true</span> <span class="token keyword">boolean</span> <span class="token function">isAuto</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">default</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>table.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>ElementType<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Retention<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>RetentionPolicy<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Target<span class="token punctuation">;</span><span class="token comment" spellcheck="true">/** * 注意: * 1、注解标注的位置 * 2、注解作用域 */</span><span class="token annotation punctuation">@Target</span><span class="token punctuation">(</span>ElementType<span class="token punctuation">.</span>TYPE<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//指定标注位置为类上</span><span class="token annotation punctuation">@Retention</span><span class="token punctuation">(</span>RetentionPolicy<span class="token punctuation">.</span>RUNTIME<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//指定在运行期间有效</span><span class="token keyword">public</span> @<span class="token keyword">interface</span> <span class="token class-name">Table</span> <span class="token punctuation">{</span> String <span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><ol start="2"><li>实体类:User.java</li></ol><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>entity<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Column<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>PK<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Table<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>Serializable<span class="token punctuation">;</span><span class="token comment" spellcheck="true">/** * 用户实体类 */</span><span class="token annotation punctuation">@Table</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"users"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">implements</span> <span class="token class-name">Serializable</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@PK</span><span class="token punctuation">(</span>isAuto <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token annotation punctuation">@Column</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"user_id"</span><span class="token punctuation">)</span> <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span> <span class="token annotation punctuation">@Column</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"user_name"</span><span class="token punctuation">)</span> <span class="token keyword">private</span> String name<span class="token punctuation">;</span> <span class="token keyword">private</span> String email<span class="token punctuation">;</span> <span class="token keyword">private</span> String country<span class="token punctuation">;</span> <span class="token annotation punctuation">@Column</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"user_password"</span><span class="token punctuation">)</span> <span class="token keyword">private</span> String password<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setId</span><span class="token punctuation">(</span><span class="token keyword">int</span> id<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>id <span class="token operator">=</span> id<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> Integer <span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> id<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> String <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> name<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setName</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> String <span class="token function">getEmail</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> email<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setEmail</span><span class="token punctuation">(</span>String email<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>email <span class="token operator">=</span> email<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> String <span class="token function">getCountry</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> country<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setCountry</span><span class="token punctuation">(</span>String country<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>country <span class="token operator">=</span> country<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> String <span class="token function">getPassword</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> password<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setPassword</span><span class="token punctuation">(</span>String password<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>password <span class="token operator">=</span> password<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><ol start="3"><li>Dao层实现SQL语句:</li></ol><p>UserDao.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>dao<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">UserDao</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">save</span><span class="token punctuation">(</span>Object obj<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>UserDaoImpl.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>dao<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Column<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>Table<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>reflect<span class="token punctuation">.</span>Field<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>ArrayList<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>List<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserDaoImpl</span> <span class="token keyword">implements</span> <span class="token class-name">UserDao</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">save</span><span class="token punctuation">(</span>Object obj<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//加载驱动</span> <span class="token comment" spellcheck="true">//获取连接</span> <span class="token comment" spellcheck="true">//准备预编译SQL语句</span> String sql <span class="token operator">=</span> <span class="token function">prepareSQL</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//通过连接对象创建预编译对象</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 拼装SQL语句的方法 * 如果用@Table注解的使用注解表名,没有的话使用实体类作为表名 * * @param obj * @return */</span> <span class="token keyword">private</span> String <span class="token function">prepareSQL</span><span class="token punctuation">(</span>Object obj<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> StringBuffer stringBuffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuffer</span><span class="token punctuation">(</span><span class="token string">"INSERT INTO "</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取obj中定义的Class对象</span> Class<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span> clazz <span class="token operator">=</span> obj<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String tablename <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getSimpleName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//判断是否使用@Table注解</span> Table table <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getAnnotation</span><span class="token punctuation">(</span>Table<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>table <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取@Table中的表名</span> tablename <span class="token operator">=</span> table<span class="token punctuation">.</span><span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> stringBuffer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>tablename <span class="token operator">+</span> <span class="token string">"("</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取当前操作类中所有定义的字段</span> Field<span class="token punctuation">[</span><span class="token punctuation">]</span> fields <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredFields</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//定义存储字段对应的值的集合</span> List<span class="token operator"><</span>Object<span class="token operator">></span> valueList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Field field<span class="token operator">:</span>fields<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取默认的字段名</span> String firldName <span class="token operator">=</span> field<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//判断是否使用@Column注解</span> Column column <span class="token operator">=</span> field<span class="token punctuation">.</span><span class="token function">getAnnotation</span><span class="token punctuation">(</span>Column<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>column<span class="token operator">!=</span>null<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取映射的字段名称</span> firldName <span class="token operator">=</span> column<span class="token punctuation">.</span><span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> field<span class="token punctuation">.</span><span class="token function">setAccessible</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取字段的值</span> Object value <span class="token operator">=</span> field<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token operator">!=</span>null<span class="token punctuation">)</span><span class="token punctuation">{</span> stringBuffer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>firldName<span class="token operator">+</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span> valueList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//删除最后一个逗号</span> stringBuffer<span class="token punctuation">.</span><span class="token function">deleteCharAt</span><span class="token punctuation">(</span>stringBuffer<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> stringBuffer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">") VALUES ("</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//设置值</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Object value<span class="token operator">:</span>valueList<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//如果是字符串,为其加上单引号</span> Object temp <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>temp <span class="token keyword">instanceof</span> <span class="token class-name">String</span><span class="token punctuation">)</span><span class="token punctuation">{</span> temp <span class="token operator">=</span> <span class="token string">"'"</span><span class="token operator">+</span>value<span class="token operator">+</span><span class="token string">"'"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> stringBuffer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>temp<span class="token operator">+</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//删除最后一个逗号</span> stringBuffer<span class="token punctuation">.</span><span class="token function">deleteCharAt</span><span class="token punctuation">(</span>stringBuffer<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> stringBuffer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">")"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>stringBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><ol start="4"><li>启动类:App.java</li></ol><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>app<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>dao<span class="token punctuation">.</span>UserDaoImpl<span class="token punctuation">;</span><span class="token keyword">import</span> com<span class="token punctuation">.</span>xzy<span class="token punctuation">.</span>ORM<span class="token punctuation">.</span>entity<span class="token punctuation">.</span>User<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//创建一个User对象实例</span> User user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"Jack"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setEmail</span><span class="token punctuation">(</span><span class="token string">"[email protected]"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setCountry</span><span class="token punctuation">(</span><span class="token string">"US"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> user<span class="token punctuation">.</span><span class="token function">setPassword</span><span class="token punctuation">(</span><span class="token string">"12546"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> UserDaoImpl userDao <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">UserDaoImpl</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//输出一条正常的SQL语句</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> userDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 反射 </tag>
</tags>
</entry>
<entry>
<title>java反射原理</title>
<link href="/2019/05/25/java%E5%8F%8D%E5%B0%84%E5%8E%9F%E7%90%86/"/>
<url>/2019/05/25/java%E5%8F%8D%E5%B0%84%E5%8E%9F%E7%90%86/</url>
<content type="html"><![CDATA[<h2 id="一、什么是反射?"><a href="#一、什么是反射?" class="headerlink" title="一、什么是反射?"></a>一、什么是反射?</h2><p>  简单来说,反射可以帮助我们在动态运行的时候,对于任意一个类,可以获取其所有的方法(包括public、protected、private和默认状态的),所有的变量(包括public、protected、private和默认状态的)。</p><h2 id="二、反射机制的原理"><a href="#二、反射机制的原理" class="headerlink" title="二、反射机制的原理"></a>二、反射机制的原理</h2><h3 id="1-一个类正常被执行的流程"><a href="#1-一个类正常被执行的流程" class="headerlink" title="1. 一个类正常被执行的流程"></a>1. 一个类正常被执行的流程</h3><p>  .java –> .class –> JVM运行期系统 –> 操作系统 –> 物理硬件<br>  .首先在编译期,一个java源文件(.java文件)通过编译器(javac指令)编译后,成为.class字节码文件。<br>  在运行期间,字节码文件以字节流的形式传输到类加载器,类加载器结合本地类库验证字节码的正确性,验证完成后将字节码文件交由java虚拟机来执行,JVM中的解释器和即时编译器对字节码文件进行处理,最终变为虚拟机可以识别的java运行系统,最后将文件放入操作系统,操作系统在物理硬件上运行这个类文件。<br>  反射就是在运行期间不知道是哪一个类被编译了,但是在类加载器中包含这个类的所有信息,所以可以动态类加载器中的字节码文件,从而获取整个类的源信息。<br><img src="/img/post-img/19-5-26-1.png" alt="反射原理"></p><h3 id="2-类加载机制"><a href="#2-类加载机制" class="headerlink" title="2. 类加载机制"></a>2. 类加载机制</h3><p>  以 Person person = new Person(); 为例,在new对象时,JVM将.class文件加载到方法区,创建一个Class对象,方法区存放类的所有信息,例如类名、访问修饰符、方法、属性、构造函数、静态变量、静态块等,但不会初始化。然后在堆里开辟一块空间,将new出的对象放入此空间,在调用构造方法时,方法区中的所有非静态成员属性拷贝到堆内存中,并初始化为缺省值。并持有非静态方法的引用。然后将person对象放入栈空间中,这个对象指向堆内存的地址。<br><img src="/img/post-img/19-5-26-2.png" alt="类加载机制"></p><h2 id="二、反射的基本使用"><a href="#二、反射的基本使用" class="headerlink" title="二、反射的基本使用"></a>二、反射的基本使用</h2><p>反射常用API:类(Class)、属性(Field)、方法(Method)、构造器(Constructor)</p><ul><li>三种获取类的Class对象的方式:<strong>通过.class获取</strong>,<strong>通过classForName()方法获取</strong>,<strong>通过类的实例获取</strong></li><li>两种获取类属性的方法,<strong>getFields()</strong> 和 <strong>getDeclaredFields()</strong> 方法。前者只能获取类中的公有属性以及父类中的公有属性,后者可以获取类中所有的属性,但<strong>不包括父类的属性</strong>。</li><li>获取类中方法的方法,<strong>getMethods()</strong> 和<strong>getDeclaredMethods()</strong>,同样,前者只能获取到类中的公有方法以及从父类继承来的公有方法,后者可以获取当前类中定义的所有方法,不区分访问权限,不包括父类方法。</li><li>获取类中的构造方法信息的方法:<strong>getConstructors()</strong> 和<strong>getDeclaredConstructors()</strong>,前者只获取公有构造方法,后者获取所有构造方法。</li></ul><p>详细测试用例:<br>Human.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Human</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> String sex<span class="token punctuation">;</span> <span class="token keyword">protected</span> String height<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>Person.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">import</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>Serializable<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Person</span> <span class="token keyword">extends</span> <span class="token class-name">Human</span> <span class="token keyword">implements</span> <span class="token class-name">Serializable</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> Integer personId<span class="token punctuation">;</span> <span class="token keyword">public</span> String personName<span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">static</span> <span class="token keyword">int</span> personAge<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">Person</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">Person</span><span class="token punctuation">(</span><span class="token keyword">int</span> personId<span class="token punctuation">,</span>String personName<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token function">Person</span><span class="token punctuation">(</span>String personName<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> Integer <span class="token function">getPersonId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> personId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setPersonId</span><span class="token punctuation">(</span>Integer personId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>personId <span class="token operator">=</span> personId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> String <span class="token function">getPersonName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> personName<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">setPersonName</span><span class="token punctuation">(</span>String personName<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>personName <span class="token operator">=</span> personName<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">getPersonAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> personAge<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>ReflectDemo.java:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">import</span> org<span class="token punctuation">.</span>junit<span class="token punctuation">.</span>Test<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>reflect<span class="token punctuation">.</span>Constructor<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>reflect<span class="token punctuation">.</span>Field<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>reflect<span class="token punctuation">.</span>Method<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>reflect<span class="token punctuation">.</span>Modifier<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>Arrays<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ReflectDemo</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 三种获取类的Class对象的方式 */</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testCreateClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//通过.class获取</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Person<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//通过Class对象中的classForName()获取</span> Class<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span> aClass <span class="token operator">=</span> Class<span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span><span class="token string">"com.xzy.reflect.Person"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>aClass<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//通过类的实例获取</span> Person person <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>person<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 获取一个类中的属性 */</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testClassForField</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取类的Class对象</span> Class<span class="token operator"><</span>Person<span class="token operator">></span> clazz <span class="token operator">=</span> Person<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取类中的所有属性</span> <span class="token comment" spellcheck="true">//getFields()方法获取类中所有public属性,包括父类的共有属性</span> Field<span class="token punctuation">[</span><span class="token punctuation">]</span> fields <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getFields</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Field field<span class="token operator">:</span>fields<span class="token punctuation">)</span><span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>field<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"======================================"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//只获取当前类中所有定义的属性,不区分访问权限</span> Field<span class="token punctuation">[</span><span class="token punctuation">]</span> fields1 <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredFields</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Field field<span class="token operator">:</span>fields1<span class="token punctuation">)</span><span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>field<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//各部分分开获取</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"访问修饰符:"</span><span class="token operator">+</span>Modifier<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>field<span class="token punctuation">.</span><span class="token function">getModifiers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"属性名称:"</span><span class="token operator">+</span>field<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getSimpleName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"属性名:"</span><span class="token operator">+</span>field<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 获取方法的所有信息 */</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">getClassForMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取Class对象</span> Class<span class="token operator"><</span>Person<span class="token operator">></span> clazz <span class="token operator">=</span> Person<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取所有public修饰的方法</span> Method<span class="token punctuation">[</span><span class="token punctuation">]</span> methods <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getMethods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Method method<span class="token operator">:</span>methods<span class="token punctuation">)</span><span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>method<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"==============================="</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取当前类中所有方法</span> Method<span class="token punctuation">[</span><span class="token punctuation">]</span> declaredMethods <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredMethods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Method method<span class="token operator">:</span>declaredMethods<span class="token punctuation">)</span><span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>method<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"访问修饰符:"</span><span class="token operator">+</span>Modifier<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>method<span class="token punctuation">.</span><span class="token function">getModifiers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"返回类型:"</span><span class="token operator">+</span>method<span class="token punctuation">.</span><span class="token function">getReturnType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getSimpleName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"方法名:"</span><span class="token operator">+</span>method<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"参数个数:"</span><span class="token operator">+</span>method<span class="token punctuation">.</span><span class="token function">getParameterCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"参数类型:"</span><span class="token operator">+</span>Arrays<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>method<span class="token punctuation">.</span><span class="token function">getParameterTypes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"======================================="</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取单个方法</span> Method method <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredMethod</span><span class="token punctuation">(</span><span class="token string">"setPersonName"</span><span class="token punctuation">,</span> String<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//通过反射实例化一个对象</span> Person instance <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//开启方法访问权限</span> method<span class="token punctuation">.</span><span class="token function">setAccessible</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//通过反射钓鱼用实例中对应的方法</span> method<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>instance<span class="token punctuation">,</span><span class="token string">"张三"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取值,注意需要再获取getPersonName方法</span> method <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredMethod</span><span class="token punctuation">(</span><span class="token string">"getPersonName"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//因为getPersonName方法是public,所以不需要开启访问权限,直接拿值</span> Object value <span class="token operator">=</span> method<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>instance<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 获取类中的构造方法信息 */</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">getClassForConstructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取Class对象</span> Class<span class="token operator"><</span>Person<span class="token operator">></span> clazz <span class="token operator">=</span> Person<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取所有public修饰的构造方法</span> Constructor<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> constructors <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getConstructors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Constructor constructor<span class="token operator">:</span>constructors<span class="token punctuation">)</span><span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>constructor<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"====================================="</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取类中所有的定义的构造方法</span> Constructor<span class="token operator"><</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> declaredConstructors <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredConstructors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Constructor constructor<span class="token operator">:</span>declaredConstructors<span class="token punctuation">)</span><span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>constructor<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"访问修饰符:"</span><span class="token operator">+</span>Modifier<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>constructor<span class="token punctuation">.</span><span class="token function">getModifiers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"构造方法名:"</span><span class="token operator">+</span>constructor<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"参数列表:"</span><span class="token operator">+</span>Arrays<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>constructor<span class="token punctuation">.</span><span class="token function">getParameterTypes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"-------------------------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//获取类中指定的构造方法</span> Constructor<span class="token operator"><</span>Person<span class="token operator">></span> constructor <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredConstructor</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> String<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> constructor<span class="token punctuation">.</span><span class="token function">setAccessible</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Person instance <span class="token operator">=</span> constructor<span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token number">19</span><span class="token punctuation">,</span> <span class="token string">"李四"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>instance<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 利用反射暴力访问类中的私有属性 */</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testAccessPrivate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//获取Class对象</span> Class<span class="token operator"><</span>Person<span class="token operator">></span> clazz <span class="token operator">=</span> Person<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取到Person对象的实例</span> Object instance <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//单独获取到personId字段</span> Field personIdField <span class="token operator">=</span> clazz<span class="token punctuation">.</span><span class="token function">getDeclaredField</span><span class="token punctuation">(</span><span class="token string">"personId"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//设置字段访问权限</span> personIdField<span class="token punctuation">.</span><span class="token function">setAccessible</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//调用字段对应的set方法</span> personIdField<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>instance<span class="token punctuation">,</span><span class="token number">23</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//获取刚刚设置的字段中的值</span> Object value <span class="token operator">=</span> personIdField<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>instance<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> java </tag>
<tag> 反射 </tag>
</tags>
</entry>
<entry>
<title>BIO与反应器模式</title>
<link href="/2019/05/25/BIO%E4%B8%8E%E5%8F%8D%E5%BA%94%E5%99%A8%E6%A8%A1%E5%BC%8F/"/>
<url>/2019/05/25/BIO%E4%B8%8E%E5%8F%8D%E5%BA%94%E5%99%A8%E6%A8%A1%E5%BC%8F/</url>
<content type="html"><![CDATA[<h2 id="BIO(同步阻塞IO)"><a href="#BIO(同步阻塞IO)" class="headerlink" title="BIO(同步阻塞IO)"></a>BIO(同步阻塞IO)</h2><p>  我们熟知的Socket编程就是一种BIO,一个socket连接一个处理线程(这个线程负责这个Socket连接的一系列数据传输操作)。阻塞的原因在于:操作系统允许的线程数量是有限的,多个socket申请与服务端建立连接时,服务端不能提供相应数量的处理线程,没有分配到处理线程的连接就会阻塞等待或被拒绝。<br>  比如说,当我们最开始使用Java编写网络请求,都是建立一个ServerSocket,它负责绑定IP地址,启动监听端口;然后,Socket负责发起连接操作,连接成功建立后,双方通过输入输出流进行同步阻塞式通信;如果没有成功建立,要么等待,要么被拒绝。即:一个连接,要求Server对应一个处理线程。<br>  简单描述一下BIO的服务端通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理每次处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答通信模型。<br><img src="/img/post-img/19-5-25-6.png" alt="一对一通信模型"></p><p>该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了</p><h2 id="反应器-reactor-模式"><a href="#反应器-reactor-模式" class="headerlink" title="反应器(reactor)模式"></a>反应器(reactor)模式</h2><p>使用单线程模拟多线程,提高资源利用率和程序的效率,增加系统吞吐量</p><blockquote><p>一个老板经营一个饭店,传统模式:来一个客人安排一个服务员招呼,客人很满意;(相当于一个连接一个线程)后来客人越来越多,需要的服务员越来越多,资源条件不足以再请更多的服务员了,传统模式已经不能满足需求。老板之所以为老板自然有过人之处,老板发现,服务员在为客人服务时,当客人点菜的时候,服务员基本处于等待状态,(阻塞线程,不做事)。于是乎就让服务员在客人点菜的时候,去为其他客人服务,当客人菜点好后再招呼服务员即可。反应器(reactor)模式诞生了饭店的生意红红火火,几个服务员就足以支撑大量的客流量,老板用有限的资源赚了更多的MONEY</p></blockquote>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> IO </tag>
<tag> BIO </tag>
</tags>
</entry>
<entry>
<title>IO思维导图</title>
<link href="/2019/05/25/IO%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE/"/>
<url>/2019/05/25/IO%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE/</url>
<content type="html"><![CDATA[<h2 id="按操作方式分类结构图"><a href="#按操作方式分类结构图" class="headerlink" title="按操作方式分类结构图"></a>按操作方式分类结构图</h2><p><img src="/img/post-img/19-5-25-2.png" alt="操作方式分类"></p><h2 id="字节流的输入输出对照表"><a href="#字节流的输入输出对照表" class="headerlink" title="字节流的输入输出对照表"></a>字节流的输入输出对照表</h2><p><img src="/img/post-img/19-5-25-3.png" alt="字节流的输入输出对照表"></p><h2 id="字符流的输入输出对照表"><a href="#字符流的输入输出对照表" class="headerlink" title="字符流的输入输出对照表"></a>字符流的输入输出对照表</h2><p><img src="/img/post-img/19-5-25-4.png" alt="字符流的输入输出对照表"></p><h2 id="按操作对象分类结构图"><a href="#按操作对象分类结构图" class="headerlink" title="按操作对象分类结构图"></a>按操作对象分类结构图</h2><p><img src="/img/post-img/19-5-25-5.png" alt="按操作对象分类结构图"></p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> IO </tag>
</tags>
</entry>
<entry>
<title>java NIO浅析</title>
<link href="/2019/05/25/java-NIO%E6%B5%85%E6%9E%90/"/>
<url>/2019/05/25/java-NIO%E6%B5%85%E6%9E%90/</url>
<content type="html"><![CDATA[<blockquote><p>NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。</p></blockquote><p>NIO主要有三大核心部分:<strong>Channel(通道)</strong>,<strong>Buffer(缓冲区)</strong>,<strong>Selector(选择器)</strong>。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector用于监听多个通道的事件(比如:连接打开,数据到达)。因此,<strong>单个线程可以监听多个数据通道</strong>。</p><h2 id="NIO与IO的主要区别"><a href="#NIO与IO的主要区别" class="headerlink" title="NIO与IO的主要区别"></a>NIO与IO的主要区别</h2><ul><li><strong>IO是面向流的,NIO是面向缓冲区的</strong>。Java IO面向流意味着每次只能从流中读取一个或多个字节,直到读取完所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲区导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,而且,需确保当更多的数据读入缓冲区时,不能覆盖掉缓冲区尚未处理的数据。</li><li><strong>IO是阻塞的,NIO是同步非阻塞的</strong>。IO的各种流是阻塞的,这意味着,当一个线程调用read()或write()方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事了,NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而是保持线程阻塞,所以直至数据变到可以读取之前,该线程可以继续做其他事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以<strong>一个单独线程可以管理多个输入输出通道(channel)</strong>。</li></ul><h2 id="NIO"><a href="#NIO" class="headerlink" title="NIO"></a>NIO</h2><blockquote><ul><li> NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。</li><li> Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。</li><li> Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。</li></ul></blockquote><h2 id="缓冲区-Buffer-:"><a href="#缓冲区-Buffer-:" class="headerlink" title="缓冲区(Buffer):"></a>缓冲区(Buffer):</h2><p>为什么说NIO是基于缓冲区的IO方式呢?因为,当一个连接建立完成后,IO的数据未必会马上到达,为了当数据到达时能够正确完成IO操作,在BIO(阻塞IO)中,等待IO的线程必须被阻塞,以全天候地执行IO操作。为了解决这种IO方式低效的问题,引入了缓冲区的概念,<strong>当数据到达时,可以预先被写入缓冲区,再由缓冲区交给线程,因此线程无需阻塞地等待IO</strong>。</p><p>缓冲区分类:</p><ul><li><strong>ByteBuffer:字节缓冲区</strong> </li><li>MappedByteBuffer:直接字节缓冲区,其内容是文件的内存映射区域</li><li>CharBuffer:字符缓冲区</li><li>DoubleBuffer:double缓冲区</li><li>FloatBuffer:float缓冲区</li><li>IntBuffer:int缓冲区</li><li>LongBuffer:long缓冲区</li><li>ShortBuffer:short缓冲区</li></ul><p>常用属性:</p><table><thead><tr><th>序号</th><th>属性描述</th></tr></thead><tbody><tr><td>1</td><td>capacity<br>缓冲区大小,无论是读模式还是写模式,此属性值不会变</td></tr><tr><td>2</td><td>position<br>写数据时,position表示当前写的位置,每写一个数据,会向下移动一个数据单元,初始为0;最大为capacity - 1切换到读模式时,position会被置为0,表示当前读的位置</td></tr><tr><td>3</td><td>limit<br>写模式下,limit 相当于capacity 表示最多可以写多少数据,切换到读模式时,limit 等于原先的position,表示最多可以读多少数据。</td></tr></tbody></table><p>常用方法:</p><table><thead><tr><th>序号</th><th>方法描述</th></tr></thead><tbody><tr><td>1</td><td>public static ByteBuffer allocate(int capacity)<br>分配一个新的字节缓冲区</td></tr><tr><td>2</td><td>public ByteBuffer get(byte[]dst)<br>此方法将此缓冲区的字节传输到给定的目标数组中。</td></tr><tr><td>3</td><td>public ByteBuffer put(byte[]dst)<br>此方法将给定的源 byte 数组的所有内容传输到此缓冲区中。</td></tr><tr><td>4</td><td>public final Buffer flip();<br>将缓冲区从写模式切换到读模式</td></tr><tr><td>5</td><td>public Buffer clear();<br>从读模式切换到写模式,不会清空数据,但后续写数据会覆盖原来的数据,即使有部分数据没有读,也会被遗忘。</td></tr></tbody></table><h2 id="通道-Channel-:"><a href="#通道-Channel-:" class="headerlink" title="通道(Channel):"></a>通道(Channel):</h2><blockquote><p>类似于流,但是可以异步读写数据(流只能同步读写),通道是双向的(流是单向的),通道的数据总是要先读到一个buffer 或者 从一个buffer写入,即通道与buffer进行数据交互。</p></blockquote><p><strong>注意:读的时候不能写,写的时候不能读,如果需要必须切换状态</strong></p><ul><li>FileChannel:从文件中读写数据。非异步,阻塞</li><li>DatagramChannel:能通过UDP读写网络中的数据。</li><li>SocketChannel:能通过TCP读写网络中的数据。<blockquote><p>创建一个SocketChannel:<code>SocketChannel channel = SocketChannel.open();</code><br>请求服务器:<code>channel.connect(IP,Port);</code></p></blockquote></li><li>ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。<blockquote><p>创建一个ServerSocketChannel:<code>ServerSocketChannel ssc = ServerSocketChannel.open();</code><br>绑定指定端口号9999:<code>ssc.socket().bind(new InetSocketAddress(9999));</code><br>关闭ServerSocket:<code>Channelssc.close();</code><br>非阻塞模式false(阻塞模式true):<code>ssc.configureBlocking(false);</code><br>监听新进来的channel(客户端的channel):<code>SocketChannel channel = ssc.accept();</code><br>注意:<strong>一般都是死循环方式,一直监听,如果channel非null,则获取到客户端的channel,否则继续监听</strong></p></blockquote></li></ul><p><strong>注意:FileChannel比较特殊,它可以与通道进行数据交互, 不能切换到非阻塞模式,套接字通道可以切换到非阻塞模式;</strong></p><h2 id="通道和缓冲区:"><a href="#通道和缓冲区:" class="headerlink" title="通道和缓冲区:"></a>通道和缓冲区:</h2><blockquote><p>基本上,所有的IO在NIO中都从一个Channel开始。Channel有点像流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。</p></blockquote><h2 id="选择器:"><a href="#选择器:" class="headerlink" title="选择器:"></a>选择器:</h2><blockquote><p>Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。</p></blockquote><p>这是在一个单线程中使用一个Selector处理3个Channel的图示:<br><img src="/img/post-img/19-5-25-1.png" alt="selector图示"></p><p>要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。</p><p><strong>选择器相当于一个观察者,用来监听通道感兴趣的事件,一个选择器可以绑定多个通道</strong>。</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 1. 选择器的创建</span>Selector selector<span class="token operator">=</span>Selector<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 2. 向选择器注册通道</span>channel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>SelectionKey key <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span>SelectionKey<span class="token punctuation">.</span>事件<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 一共有四种事件</span><span class="token comment" spellcheck="true">// SelectionKey.OP_CONNECT :请求连接</span><span class="token comment" spellcheck="true">// SelectionKey.OP_ACCEPT :接收请求</span><span class="token comment" spellcheck="true">// SelectionKey.OP_READ :读取</span><span class="token comment" spellcheck="true">// SelectionKey.OP_WRITE :写入</span><span class="token comment" spellcheck="true">// 注意:使用选择器时,通道必须是非阻塞的</span>selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//代表注册的事件发生是可以执行它下边的代码,否则阻塞</span><span class="token comment" spellcheck="true">// 3. SelectionKey</span>ServerSocketChannel ssc <span class="token operator">=</span> <span class="token punctuation">(</span>强转<span class="token punctuation">)</span>key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>SocketChannel sc <span class="token operator">=</span> <span class="token punctuation">(</span>强转<span class="token punctuation">)</span>key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 4. 通过Selector选择通道</span><span class="token comment" spellcheck="true">//一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。下面是select()方法:</span><span class="token keyword">int</span> <span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 阻塞到至少有一个通道在你注册的事件上就绪了。</span><span class="token keyword">int</span> <span class="token function">select</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeout<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 和select()一样,除了最长会阻塞timeout毫秒(参数)</span><span class="token keyword">int</span> <span class="token function">selectNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 不会阻塞,不管什么通道就绪都立刻返回.(注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零)。</span><span class="token comment" spellcheck="true">/**select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。*/</span><span class="token comment" spellcheck="true">// 5. 关闭选择器</span><span class="token comment" spellcheck="true">// 用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。</span></code></pre><p>相关链接:<a href="https://tech.meituan.com/nio.html" target="_blank" rel="noopener">美团技术团队-java NIO浅析</a></p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> IO </tag>
<tag> NIO </tag>
</tags>
</entry>
<entry>
<title>基于JDK8的HashMap详解</title>
<link href="/2019/05/22/%E5%9F%BA%E4%BA%8EJDK8%E7%9A%84HashMap%E8%AF%A6%E8%A7%A3/"/>
<url>/2019/05/22/%E5%9F%BA%E4%BA%8EJDK8%E7%9A%84HashMap%E8%AF%A6%E8%A7%A3/</url>
<content type="html"><![CDATA[<h3 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h3><p>HashMap是程序员使用频率较高的一种用于映射(键值对)处理的数据类型,随着JDK(Java Development Kit)版本的更新,HashMap也在不断被优化。其中JDK1.8在HashMap底层引入了红黑树的数据结构并对其扩容进行了优化等。本文将结合JDK1.7与JDK1.8对HashMap进行分析,浅析HashMap在JDK1.8中的改进。</p><h2 id="一、HashMap的继承体系和特点"><a href="#一、HashMap的继承体系和特点" class="headerlink" title="一、HashMap的继承体系和特点"></a>一、HashMap的继承体系和特点</h2><h4 id="1-HashMap的继承体系"><a href="#1-HashMap的继承体系" class="headerlink" title="1. HashMap的继承体系"></a>1. HashMap的继承体系</h4><p>java.util.Map是java为数据结构中的映射定义的接口,实现此接口的有四个常用类,分别是HashMap、HashTable、LinkedHashMap和TreeMap,他们的继承关系如下:<br><img src="/img/post-img/19-5-22-11.png" alt="HashMap继承体系"></p><h4 id="2-HashMap及相关类的特点:"><a href="#2-HashMap及相关类的特点:" class="headerlink" title="2. HashMap及相关类的特点:"></a>2. HashMap及相关类的特点:</h4><ul><li><strong>HashMap</strong>:它根据键(key)的HashCode值存储数据,大多数情况下可以直接定位到它的值,因此具有很快的访问速度,但遍历顺序却是不确定的。HashMap最多允许一个元素的键为null,允许多个元素的值为null。HashMap是非线程安全的,即可以有多个线程同时访问HashMap,可能导致数据的前后访问不一致。可以用Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者直接使用引入了分段锁的ConcurrentHashMap。</li><li><strong>HashTable</strong>:HashTable是遗留类,很多映射的常用功能与HashMap类似,不同的是HashTable继承自Dictionary类,并且是线程安全的,任一时刻只允许一个线程访问HashTable,并发性不如ConcurrentHashMap。新代码中不建议使用HashTable,因为在不需要线程安全的场合可以用HashMap代替,需要线程安全的场合可以用ConcurrentHashMap替代。</li><li><strong>LinkedHashMap</strong>:LinkedHashMap是HashMap的一个子类,它保存了元素的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的元素肯定是先插入的,也可以在构造时带参数,按照访问次序排序。</li><li><strong>TreeMap</strong>:TreeMap实现了SortedMap接口,能够将它的元素根据键排序,默认根据键的升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的结果是排过序的,如果需要排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap时传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。<blockquote><p>通过以上比较,我们了解到HashMap是java中Map家族的普通一员,因为它是满足大多数场景的使用条件,所以HashMap是使用最频繁的一个。下面我们将结合HashMap源码,从存储结构、常用方法、扩容以及安全性等方面深入理解HashMap的工作原理。</p></blockquote></li></ul><h2 id="二、HashMap的内部实现"><a href="#二、HashMap的内部实现" class="headerlink" title="二、HashMap的内部实现"></a>二、HashMap的内部实现</h2><h4 id="1-HashMap是什么——存储结构"><a href="#1-HashMap是什么——存储结构" class="headerlink" title="1. HashMap是什么——存储结构"></a>1. HashMap是什么——存储结构</h4><p>从结构来讲,HashMap是数组+链表+红黑树(JDK1.8新增红黑树部分)实现的,数组table存放类型为Node<K,V>的结点,该结点向外引申出单向链表,当链表长度大于8时,转换为红黑树。如下图所示:<br><img src="/img/post-img/19-5-22-12.png" alt="存储结构"></p><p>Node [] table数组被称为哈希桶数组。那么Node到底是什么?JDK1.8中对Node<K,V>的定义如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Node</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">Map<span class="token punctuation">.</span>Entry</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">final</span> <span class="token keyword">int</span> hash<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//用来定为数组索引位置</span> <span class="token keyword">final</span> K key<span class="token punctuation">;</span> V value<span class="token punctuation">;</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//链表的下一个Node</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token keyword">int</span> hash<span class="token punctuation">,</span> K key<span class="token punctuation">,</span> V value<span class="token punctuation">,</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> K <span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> V <span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> V <span class="token function">setValue</span><span class="token punctuation">(</span>V newValue<span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">equals</span><span class="token punctuation">(</span>Object o<span class="token punctuation">)</span> <span class="token punctuation">{</span> ··· <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>Node是HashMap的一个内部类,实现了Map.Entry接口,本质上就是一个映射(键值对),上图中的每个蓝色椭圆就表示一个Node的对象。</p><p>HashMap使用哈希表存储的。为解决哈希冲突,哈希表可以采用开放地址法和链地址法等方法,java中的HashMap采用的是链地址法。所谓链地址法,简单来说就是数组加链表的组合,每个数组元素上都有一个链表结构,将数据被hash后得到的数字作为数组下标,将该元素添加到对应下标下的链表中。<br>举例说明,假如程序在执行以下代码:<br><code>map.put("通信","张三");</code><br>系统会“通信”这个key的hashCode方法(该方法适用于任何java对象)得到其HashCode值,再通过Hash算法的<strong>后两步运算</strong>(高位运算和取模运算)来定位该键值对的存储位置。有时两个key会定位到相同的位置,表示发生了<strong>哈希碰撞</strong>。hash算法计算结果越分散均匀,发生哈希碰撞的概率就越小,HashMap的存储效率就越高。这就会出现以下问题,如果哈希桶数组足够大,即使差的hash算法分布也比较均匀,相反如果哈希桶数组很小,即使好的hash算法也很难避免出现较多碰撞。因此就需要在时间成本和空间成本之间有所权衡,即根据实际情况设定合适大小的哈希桶数组,并设计好的hash算法来减少哈希碰撞。那么通过什么方式来控制HashMap使哈希桶数组占用空间又少有能降低hash碰撞的几率呢?答案就是<strong>好的hash算法加扩容机制</strong>。</p><p>在理解Hash和扩容流程之前,我们得先了解下HashMap的几个字段。从HashMap的默认构造函数源码可知,构造函数就是对以下几个字段进行初始化,源码如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">int</span> threshold<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//所能容纳的key-value对极限</span><span class="token keyword">final</span> <span class="token keyword">float</span> loadFactor;<span class="token comment" spellcheck="true">//负载因子</span><span class="token keyword">int</span> modCount;<span class="token keyword">int</span> size;</code></pre><p>首先,Node[] table的初始长度length(默认值是<strong>16</strong>),loadFactor为<strong>负载因子</strong>(默认是<strong>0.75</strong>),threshold是HashMap所能容纳的最大数据量的Node个数。<code>threshold=length*loadFactor</code>。也就是说,在数组定义好长度后,负载因子越大,所能容纳的键值对的个数越多。</p><p>结合负载因子的定义公式可知,threshold就是在此loadFactor和length对应下允许的最大元素数目,超过这个数目就重新resize,扩容后的HashMap容量是之前的两倍。默认的负载因子是0.75,是对空间和时间效率的一个平衡选择,建议大家不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子loadFactor的值,相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。</p><p>size这个字段很好理解,就是HashMap中<strong>实际存在的键值对数量</strong>。注意和table的长度length、容纳最大键值对数量threshold的区别。而modCount字段主要用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败。强调,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。</p><p>在HashMap中,哈希桶数组table的长度length大小必须为2的n次方,这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小与合数,Hashtable初始化桶大小为11,就是桶大小设计为素数的应用。HashMap采用这种非常规设计,主要是为了在<strong>取模和扩容时做优化</strong>,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了<strong>高位参与运算</strong>的过程。</p><p>这里存在一个问题,即使负载因子和hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当<strong>链表长度大于8时</strong>,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论,想了解很多红黑树数据结构的工作原理可以参考另一篇博文深入理解红黑树。</p><h4 id="2-HashMap能干什么——功能实现"><a href="#2-HashMap能干什么——功能实现" class="headerlink" title="2. HashMap能干什么——功能实现"></a>2. HashMap能干什么——功能实现</h4><p>HashMap的内部功能实现很多,本文主要从根据key获取哈希桶数组索引位置、put方法的详细执行过程、扩容过程这三个具有代表性的点深入展开。</p><ul><li><strong>根据key确定哈希桶数组索引位置</strong></li></ul><p>不管是增加、删除还是查找键值对,定位到哈希桶数组的位置都是很关键的第一步。前面说到HashMap的结构是数组加链表的组合,所以我们当然希望HashMap里的元素分布尽量均匀些。HashMap定位数组索引位置,直接决定了HashMap的离散性能。我们先看一下源码对hash算法的实现:<br><img src="/img/post-img/19-5-22-13.png" alt="hash方法"></p><p><code>h=key.hashCode()</code> 为算法第一步,取hashCode值;</p><p><code>h^(h>>>16)</code> 为算法第二步,高位参与运算;</p><p>我们注意到在JDK1.7的源码中有这样一个方法:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">indexFor</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">,</span><span class="token keyword">int</span> length<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">return</span> h<span class="token operator">&</span><span class="token punctuation">(</span>length<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>其中的<code>h&(length-1)</code>为hash算法的第三步,取模运算,这个方法在JDK1.8中没有出现,但是实现原理是相同的。</p><p><code>h&(table.length-1)</code>这个方法非常巧妙,因为一般我们能想到的计算索引方法就是将hash值对table数组长度进行取模运算,但模运算的消耗还是比较大的,而HashMap底层的数组长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方的时候,h&(length-1)等价于取模运算,但&比%具有更高的效率。</p><p>JDK1.8中,优化了高位运算的算法,通过将hash的高16位与低16位进行异或运算,主要是从速度、功效、质量上考虑的,这么做可以在table的length很小的时候也能考虑到高低位都参与到运算中,同时不会有太大的开销。</p><p>具体计算过程如下:<br><img src="/img/post-img/19-5-22-14.png" alt="高位运算过程"></p><ul><li><strong>HashMap的put过程</strong></li></ul><p>可以通过下图来辅助理解HashMap的put过程:<br><img src="/img/post-img/19-5-22-15.png" alt="put流程"></p><p>JDK1.8中HashMap的put方法源码如下:<br><img src="/img/post-img/19-5-22-16.png" alt="put源码"></p><p>此方法将对key进行hash运算,得到hash值。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">final</span> V <span class="token function">putVal</span><span class="token punctuation">(</span><span class="token keyword">int</span> hash<span class="token punctuation">,</span> K key<span class="token punctuation">,</span> V value<span class="token punctuation">,</span> <span class="token keyword">boolean</span> onlyIfAbsent<span class="token punctuation">,</span> <span class="token keyword">boolean</span> evict<span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">;</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> i<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//第一步,判断table是否为空,如果为空,创建</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> n <span class="token operator">=</span> <span class="token punctuation">(</span>tab <span class="token operator">=</span> <span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//第二步,计算index,并对null做处理</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p <span class="token operator">=</span> tab<span class="token punctuation">[</span>i <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&</span> hash<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">newNode</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e<span class="token punctuation">;</span> K k<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//第三步,如果key结点已经存在,直接覆盖value</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p<span class="token punctuation">.</span>hash <span class="token operator">==</span> hash <span class="token operator">&&</span><span class="token punctuation">(</span><span class="token punctuation">(</span>k <span class="token operator">=</span> p<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>key <span class="token operator">!=</span> null <span class="token operator">&&</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> e <span class="token operator">=</span> p<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//第四步,判断该链是否为红黑树</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token keyword">instanceof</span> <span class="token class-name">TreeNode</span><span class="token punctuation">)</span> e <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>p<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">putTreeVal</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> tab<span class="token punctuation">,</span> hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//该链为链表</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> binCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">;</span> <span class="token operator">++</span>binCount<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//如果链表为空,直接插入</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> p<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> p<span class="token punctuation">.</span>next <span class="token operator">=</span> <span class="token function">newNode</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//如果链表长度大于8,转换为红黑树进行操作</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>binCount <span class="token operator">>=</span> TREEIFY_THRESHOLD <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// -1 for 1st</span> <span class="token function">treeifyBin</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> hash<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//key已经存在,直接覆盖value</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> hash <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>k <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>key <span class="token operator">!=</span> null <span class="token operator">&&</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> p <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// existing mapping for key</span> V oldValue <span class="token operator">=</span> e<span class="token punctuation">.</span>value<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>onlyIfAbsent <span class="token operator">||</span> oldValue <span class="token operator">==</span> null<span class="token punctuation">)</span> e<span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token function">afterNodeAccess</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> oldValue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">++</span>modCount<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//第五步,超过最大容量,扩容 </span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">++</span>size <span class="token operator">></span> threshold<span class="token punctuation">)</span> <span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">afterNodeInsertion</span><span class="token punctuation">(</span>evict<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><ul><li><strong>HashMap的扩容机制</strong></li></ul><p>扩容(resize)就是重新计算容量,向HashMap里不停的添加元素,而当HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组长度,以便能装入更多的元素。当然java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换更大的水桶。</p><p>JDK1.8中对扩容引入了红黑树,较为复杂,为了便于理解我们仍然使用JDK1.7的源码来分析,本质上区别不大,但易于理解:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">void</span> <span class="token function">resize</span><span class="token punctuation">(</span><span class="token keyword">int</span> newCapacity<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment" spellcheck="true">//传入新的容量</span> Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> oldTable <span class="token operator">=</span> table<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//引用扩容前的Entry数组</span> <span class="token keyword">int</span> oldCapacity <span class="token operator">=</span> oldTable<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>oldCapacity <span class="token operator">==</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment" spellcheck="true">//扩容前的数组大小如果已经达到最大(2^30)了</span> threshold <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//修改阈值为int的最大值(2^31-1),这样以后就不会扩容了</span> <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span>Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> newTable <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">[</span>newCapacity<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//初始化一个新的Entry数组</span><span class="token function">transfer</span><span class="token punctuation">(</span>newTable<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//将数据转移到新的Entry数组里</span>table<span class="token operator">=</span>newTable<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//HashMap的table属性引用新的Entry数组</span>thteshold<span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">(</span>newCapacity<span class="token operator">*</span>loadFactor<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//修改阈值</span><span class="token punctuation">}</span><span class="token keyword">void</span> <span class="token function">transfer</span><span class="token punctuation">(</span>Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> newtable<span class="token punctuation">)</span><span class="token punctuation">{</span> Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> src <span class="token operator">=</span> table<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//src引用了旧的Entry数组</span> <span class="token keyword">int</span> newCapacity <span class="token operator">=</span> newTable<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>j<span class="token operator"><</span>src<span class="token punctuation">.</span>length<span class="token punctuation">;</span>j<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment" spellcheck="true">//遍历旧的Entry数组</span> Entry<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> src<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//取得旧Entry数组的每个元素</span> <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token operator">!=</span>null<span class="token punctuation">)</span><span class="token punctuation">{</span> src<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token operator">=</span>null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)</span> <span class="token keyword">do</span><span class="token punctuation">{</span> Entry<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">;</span> <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token function">indexFor</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash<span class="token punctuation">,</span>newCapacity<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//重新计算每个元素在数组中的位置</span> e<span class="token punctuation">.</span>next <span class="token operator">=</span> newTable<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//标记[1]</span> newTable<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token operator">=</span>e<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//将元素放在数组上</span> e<span class="token operator">=</span>next<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//访问下一个Entry链上的元素</span> <span class="token punctuation">}</span><span class="token keyword">while</span><span class="token punctuation">(</span>e<span class="token operator">!=</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>newTable[i]的引用赋给了e.next,也就是使用了单链表的头插方式,同一位置上的新元素总会被放到链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了Hash冲突的话)。这一点和JDK1.8有区别,下文详解。在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。</p><p>下面举个例子说明一下扩容过程。假设我们的hash算法就是简单的用key mod一下数组的长度。其中的哈希桶数组table的size=2,所以key=3、7、5,put顺序依次为5、7、3.在mod 2以后都冲突在table[1]这里了。假设负载因子loadFactor=1,即当键值对的实际大小size大于table的实际大小时扩容。接下来的三个步骤是哈希桶数组resize成4,然后所有的Node重新rehash的过程。<br><img src="/img/post-img/19-5-22-17.png" alt="put举例"></p><p>下面我们讲解下JDK1.8做了哪些优化。经过观察可以发现,我们使用的是2次幂的扩展(长多扩为原来的2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置上,因此我们在扩充HashMap的时候,不需要像JDK1.7那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引不变,是1的话索引变成“原索引+oldCapacity”。可以借助下图来理解:<br><img src="/img/post-img/19-5-22-18.png" alt="扩容"></p><p>这个设计非常巧妙,既省去了重新计算hash值的时间,同时,由于新增的1位是0还是1可以认为是随机的,因此resize的过程均匀的吧之前的冲突的结点分散到新的bucket了,这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。有兴趣的读者可以研究下JDK1.8的resize源码,如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">final</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> oldTab <span class="token operator">=</span> table<span class="token punctuation">;</span> <span class="token keyword">int</span> oldCap <span class="token operator">=</span> <span class="token punctuation">(</span>oldTab <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> oldTab<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token keyword">int</span> oldThr <span class="token operator">=</span> threshold<span class="token punctuation">;</span> <span class="token keyword">int</span> newCap<span class="token punctuation">,</span> newThr <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldCap <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//超过最大值就不再扩容了</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldCap <span class="token operator">>=</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token punctuation">{</span> threshold <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">;</span> <span class="token keyword">return</span> oldTab<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//没超过最大值,就扩为原来的2倍</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>newCap <span class="token operator">=</span> oldCap <span class="token operator"><<</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator"><</span> MAXIMUM_CAPACITY <span class="token operator">&&</span> oldCap <span class="token operator">>=</span> DEFAULT_INITIAL_CAPACITY<span class="token punctuation">)</span> newThr <span class="token operator">=</span> oldThr <span class="token operator"><<</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// double threshold</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldThr <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// initial capacity was placed in threshold</span> newCap <span class="token operator">=</span> oldThr<span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// zero initial threshold signifies using defaults</span> newCap <span class="token operator">=</span> DEFAULT_INITIAL_CAPACITY<span class="token punctuation">;</span> newThr <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">(</span>DEFAULT_LOAD_FACTOR <span class="token operator">*</span> DEFAULT_INITIAL_CAPACITY<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//设置新的resize上限</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newThr <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">float</span> ft <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">float</span><span class="token punctuation">)</span>newCap <span class="token operator">*</span> loadFactor<span class="token punctuation">;</span> newThr <span class="token operator">=</span> <span class="token punctuation">(</span>newCap <span class="token operator"><</span> MAXIMUM_CAPACITY <span class="token operator">&&</span> ft <span class="token operator"><</span> <span class="token punctuation">(</span><span class="token keyword">float</span><span class="token punctuation">)</span>MAXIMUM_CAPACITY <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>ft <span class="token operator">:</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> threshold <span class="token operator">=</span> newThr<span class="token punctuation">;</span> <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"rawtypes"</span><span class="token punctuation">,</span><span class="token string">"unchecked"</span><span class="token punctuation">}</span><span class="token punctuation">)</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> newTab <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">[</span>newCap<span class="token punctuation">]</span><span class="token punctuation">;</span> table <span class="token operator">=</span> newTab<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>oldTab <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//把每个bucket都移到新的buckets中</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator"><</span> oldCap<span class="token punctuation">;</span> <span class="token operator">++</span>j<span class="token punctuation">)</span> <span class="token punctuation">{</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> oldTab<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> oldTab<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>next <span class="token operator">==</span> null<span class="token punctuation">)</span> newTab<span class="token punctuation">[</span>e<span class="token punctuation">.</span>hash <span class="token operator">&</span> <span class="token punctuation">(</span>newCap <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">TreeNode</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>TreeNode<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>e<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> newTab<span class="token punctuation">,</span> j<span class="token punctuation">,</span> oldCap<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// preserve order</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> loHead <span class="token operator">=</span> null<span class="token punctuation">,</span> loTail <span class="token operator">=</span> null<span class="token punctuation">;</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> hiHead <span class="token operator">=</span> null<span class="token punctuation">,</span> hiTail <span class="token operator">=</span> null<span class="token punctuation">;</span> Node<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">;</span> <span class="token keyword">do</span> <span class="token punctuation">{</span> next <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">&</span> oldCap<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>loTail <span class="token operator">==</span> null<span class="token punctuation">)</span> loHead <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token keyword">else</span> loTail<span class="token punctuation">.</span>next <span class="token operator">=</span> e<span class="token punctuation">;</span> loTail <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>hiTail <span class="token operator">==</span> null<span class="token punctuation">)</span> hiHead <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token keyword">else</span> hiTail<span class="token punctuation">.</span>next <span class="token operator">=</span> e<span class="token punctuation">;</span> hiTail <span class="token operator">=</span> e<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> next<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>loTail <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> loTail<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> newTab<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> loHead<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>hiTail <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> hiTail<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> newTab<span class="token punctuation">[</span>j <span class="token operator">+</span> oldCap<span class="token punctuation">]</span> <span class="token operator">=</span> hiHead<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> newTab<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><h2 id="三、小结"><a href="#三、小结" class="headerlink" title="三、小结"></a>三、小结</h2><ul><li><input checked disabled type="checkbox"> 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。</li><li><input checked disabled type="checkbox"> 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。</li><li><input checked disabled type="checkbox"> HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。</li><li><input checked disabled type="checkbox"> JDK1.8引入红黑树大程度优化了HashMap的性能</li></ul>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> 基础知识 </tag>
<tag> HashMap </tag>
</tags>
</entry>
<entry>
<title>浅谈ArrayList</title>
<link href="/2019/05/22/%E6%B5%85%E8%B0%88ArrayList/"/>
<url>/2019/05/22/%E6%B5%85%E8%B0%88ArrayList/</url>
<content type="html"><![CDATA[<h2 id="一、ArrayList的继承体系和特点"><a href="#一、ArrayList的继承体系和特点" class="headerlink" title="一、ArrayList的继承体系和特点"></a>一、ArrayList的继承体系和特点</h2><p>ArrayList总体继承体系图如下:<br><img src="/img/post-img/19-5-22-1.png" alt="ArrayList继承体系"></p><p>ArrayList的特点主要有以下几点:</p><blockquote><ul><li>ArrayList在内存中分配连续的存储空间,可理解为长度可变的数组。</li><li>ArrayList存储元素可以重复,存储顺序和添加顺序一致。</li><li>遍历元素和随机访问元素的效率较高,因为和数组一样存在索引。 </li><li>添加、删除元素时需移动大量元素,按照内容查询时效率低。</li></ul></blockquote><h2 id="二、ArrayList的用法"><a href="#二、ArrayList的用法" class="headerlink" title="二、ArrayList的用法"></a>二、ArrayList的用法</h2><pre class=" language-java"><code class="language-java">List<span class="token operator"><</span>String<span class="token operator">></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator"><</span>String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//使用多态创建ArrayList,泛型指定该ArrayList只能放String类型的元素</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator"><</span><span class="token number">5</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span> list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token operator">+</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//架循环往ArrayList里添加元素</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//输出整个ArrayList</span> list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">"a0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//在指定索引处添加指定元素</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span> list<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//删除指定索引处的元素</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//获取ArrayList的长度并输出</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>List<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//拿到指定索引处的元素并输出</span> list<span class="token punctuation">.</span><span class="token function">removeAll</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//删除所有元素 </span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span>输出结果:<span class="token punctuation">[</span>a0<span class="token punctuation">,</span> a1<span class="token punctuation">,</span> a2<span class="token punctuation">,</span> a3<span class="token punctuation">,</span> a4<span class="token punctuation">]</span><span class="token punctuation">[</span>a0<span class="token punctuation">,</span> a1<span class="token punctuation">,</span> a2<span class="token punctuation">,</span> a0<span class="token punctuation">,</span> a3<span class="token punctuation">,</span> a4<span class="token punctuation">]</span><span class="token punctuation">[</span>a0<span class="token punctuation">,</span> a1<span class="token punctuation">,</span> a0<span class="token punctuation">,</span> a3<span class="token punctuation">,</span> a4<span class="token punctuation">]</span><span class="token number">5</span> a3<span class="token punctuation">[</span><span class="token punctuation">]</span></code></pre><p>ArrayList的一些其他常用方法:</p><p><code>toArray(T [] a)</code>:把ArrayList转换成数组放到指定数组a里</p><p><code>contains(Object o)</code>:判断ArrayList中是否包含指定元素,返回Boolean类型</p><p><code>isEmpty()</code>:判断ArrayList按是否为空,返回Boolean类型</p><h2 id="三、ArrayList的自动扩容机制"><a href="#三、ArrayList的自动扩容机制" class="headerlink" title="三、ArrayList的自动扩容机制"></a>三、ArrayList的自动扩容机制</h2><p>java为了实现自动扩容,引入了Capacity和size的概念,用来区别数组的length。为了保证用户增加新的对象,java设置了最小容量(minCapacity),通常情况下,它大于列表对象的数目,所以Capactiy虽然就是底层数组的长度(length),但是对于最终用户来讲,它是没有意义的。而存储着列表对象数量的size才是最终用户所需要的。为了防止用户错误修改,这一属性被设置为private的,不过可以通过size()方法获取。<br><img src="/img/post-img/19-5-22-2.png" alt="size属性"></p><p>ArrayList的初始容量默认为10:<br><img src="/img/post-img/19-5-22-3.png" alt="初始容量"></p><p>ArrayList有两个构造方法:<br><img src="/img/post-img/19-5-22-4.png" alt="构造方法1"><br><img src="/img/post-img/19-5-22-5.png" alt="构造方法2"></p><p>这说明Capacity初始值(initialCapacity)可以由用户直接指定或由用户指定的Collection集合存储的对象数目确定,如果没有指定,系统默认为10。而size的被声明为int型变量,默认为0,当用户用指定Collection创建ArrayList时,size值等于initialCapacity。</p><p>我们知道ArrayList可以用add()方法添加元素,我们来看一下add()的实现:<br><img src="/img/post-img/19-5-22-6.png" alt="add方法"></p><p>ensureCapacityInternal()方法的调用主要用来确认数组是否可以放下这一元素,修改elementData数组的指向。ensureCapacityInternal()方法的实现如下:<br><img src="/img/post-img/19-5-22-7.png" alt="ensureCapacityInternal()方法"></p><p>首先看看数组是否为空,如果数组为空,就将DEFAULT_CAPACITY和minCapacity中较大的一个作为初始大小赋给minCapacity,DEFAULT_CAPACITY就是先前定义的10,minCapacity就是add方法中传入的size+1。</p><p>如果数组不为空,就执行ensureExplicitCapacity()方法,其实现如下:<br><img src="/img/post-img/19-5-22-8.png" alt="ensureExplicitCapacity()方法"></p><p>modCount属性在ArrayList的父类AbstractList中定义,用于存储结构修改次数。</p><p>此方法比较minCapacity与elementData.length的大小。当第一次插入值时,由于minCapacity一定大于等于10,而elementData.length 是0,此时调用grow()方法,grow()方法正是ArrayList扩容的核心所在,其实现如下:<br><img src="/img/post-img/19-5-22-9.png" alt="grow()方法"></p><p>这个方法首先计算出一个容量,大小为oldCapacity + (oldCapacity >> 1)。即elementData数组长度的<strong>1.5倍</strong>。再从minCapacity和这个容量中取较大值作为扩容后的新的数组大小。</p><p>新的容量小于数组的最大值MAX_ARRAY_SIZE,可能超过一次能申请的整块内存的大小上限,出现OutOfMemoryError。</p><p>如果新的容量大于数组的最大值MAX_ARRAY_SIZE,调用hugeCapacity()方法,其实现如下:<br><img src="/img/post-img/19-5-22-10.png" alt="hugeCapacity()方法"></p><p>此方法会对minCapacity和MAX_ARRAY_SIZE进行比较,minCapacity 大的话,就将Integer.MAX_VALUE 作为新数组的大小,否则将MAX_ARRAY_SIZE作为数组的大小。最后,就把原来数组的数据复制到新的数组中。调用了Arrays的copyOf方法。内部是System的arraycopy方法,由于是native方法,所以效率较高。</p><blockquote><p>通过以上源码我们不难看出,java自动增加ArrayList大小的思路是:<strong>向ArrayList添加对象时,原对象数目加1,如果大于原底层数组长度,则以适当长度新建一个原数组的拷贝,并修改原数组,指向这个新建数组。原数组自动抛弃(java垃圾回收机制会自动回收)。size则在向数组添加对象,自增1。</strong></p></blockquote><p>综上所述,ArrayList的扩容会产生一个新的数组,将原来数组的值复制到新的数组中。会消耗一定的资源。所以我们初始化ArrayList时,最好可以估算一个初始的大小。</p>]]></content>
<categories>
<category> java </category>
</categories>
<tags>
<tag> 集合 </tag>
<tag> ArrayList </tag>
<tag> 基础知识 </tag>
</tags>
</entry>
<entry>
<title>单例模式(Singleton Pattern)</title>
<link href="/2019/05/21/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%EF%BC%88Singleton-Pattern%EF%BC%89/"/>
<url>/2019/05/21/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%EF%BC%88Singleton-Pattern%EF%BC%89/</url>
<content type="html"><![CDATA[<p>单例模式是最简单的设计模式之一,这种设计模式是一种创建型的模式,提供了创建对象的最佳方式。</p><p>单例模式顾名思义就是一个类只允许创建一个实例,因此它只涉及到一个单一的类,并且这个类要完成自己创建自己的实例的工作,并保证能且只能创建一个实例。这个类还需要提供一个访问这个实例的方法。</p><p>接下来我们分析一下单例模式的多种实现方式。(以下代码均为Java实现,若读者有兴趣可自行用其他语言实现)</p><h2 id="一、懒汉模式(延迟加载)"><a href="#一、懒汉模式(延迟加载)" class="headerlink" title="一、懒汉模式(延迟加载)"></a>一、懒汉模式(延迟加载)</h2><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//定义一个未初始化的私有静态对象,外部不可访问</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton singleton<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//构造私有,保证外部不可创建实例</span> <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//提供公有方法获取实例</span> <span class="token keyword">public</span> <span class="token keyword">synchronized</span> <span class="token keyword">static</span> Single <span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>singleton<span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//如果singleton为空就为其创建实例</span> singleton<span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//将创建好的实例返回</span> <span class="token keyword">return</span> singleton<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><blockquote><p>懒汉形式是标准的单例实现形式,它的延迟加载体现在newInstance方法里的判断,方法加了synchronized关键字可以避免多线程问题,但会影响程序性能。</p></blockquote><h2 id="二、饿汉模式(贪婪加载)"><a href="#二、饿汉模式(贪婪加载)" class="headerlink" title="二、饿汉模式(贪婪加载)"></a>二、饿汉模式(贪婪加载)</h2><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//直接创建私有静态实例</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton singleton<span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//构造方法私有保证外部不可创建实例</span> <span class="token keyword">private</span> <span class="token function">singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//将创建好的实例返回</span> <span class="token keyword">return</span> singleton<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><blockquote><p>在单例对象声明的时候就直接初始化对象,可以避免多线程问题,但是如果对象初始化比较复杂,会导致程序初始化缓慢。</p></blockquote><h2 id="三、双重锁检查"><a href="#三、双重锁检查" class="headerlink" title="三、双重锁检查"></a>三、双重锁检查</h2><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//静态对象用volatile关键字修饰</span> <span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">static</span> Singleton singleton<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>singleton <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>Singleton<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>singleton <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> singleton <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> singleton<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><blockquote><p>这个是懒汉形式的加强版,将synchronized关键字移到了newInstance方法里面,同时将singleton对象加上volatile关键字,这种方式既可以避免多线程问题,又不会降低程序的性能。但volatile关键字也有一些性能问题,不建议大量使用。</p></blockquote><h2 id="四、Lazy-initialization-holder-class"><a href="#四、Lazy-initialization-holder-class" class="headerlink" title="四、Lazy initialization holder class"></a>四、Lazy initialization holder class</h2><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SingletonHolder</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton singleton <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> SingletonHolder<span class="token punctuation">.</span>singleton<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><blockquote><p>这种方法创建了一个内部静态类,通过内部类的机制使得单例对象可以延迟加载,同时内部类相当于是外部类的静态部分,所以可以通过jvm来保证其线程安全。这种形式比较推荐。</p></blockquote><p>还有一些其他的方法有待学习探索,正所谓学无止境,与君共勉。</p>]]></content>
<categories>
<category> 设计模式 </category>
</categories>
<tags>
<tag> 设计模式 </tag>
</tags>
</entry>
<entry>
<title>hashCode和equals方法</title>
<link href="/2019/05/21/hashCode%E5%92%8Cequals%E6%96%B9%E6%B3%95/"/>
<url>/2019/05/21/hashCode%E5%92%8Cequals%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<p>hashCode和equals方法是Object类中的两个常用方法。其定义如下:</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">// hashCode()方法默认是native方法:</span><span class="token keyword">public</span> <span class="token keyword">native</span> <span class="token keyword">int</span> <span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// equals(obj)默认比较的是内存地址:</span><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">equals</span><span class="token punctuation">(</span>Object obj<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token operator">==</span> obj<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><blockquote><p>hashCode()方法有三个关注点:</p><ul><li>关注点1:主要是这个hashCode方法对哪些类是有用的,并不是任何情况下都要使用这个方法,(不使用时根本就没有必要覆写此方法),而是当涉及到像HashMap、HashSet(他们的内部实现中使用到了hashCode方法)等与hash有关的一些类时,才会使用到hashCode方法。</li><li>关注点2:推荐按照这样的原则来设计,即 当equals(object)相同时,hashCode()的返回值也要尽量相同,当equals(object)不相同时,hashCode()的返回没有特别的要求,但是也要尽量不相同以获取好的性能。</li><li>关注点 3:默认的hashCode实现一般是内存地址对应的数字,所以不同的对象,hashCode()的返回值是不一样的。</li></ul></blockquote><p>在这种缺省实施情况下,只有它们引用真正同一个对象时这两个引用才是相等的。同样,Object提供的hashCode()的缺省实施通过将对象的内存地址对映于一个整数值来生成。</p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><p>HashMap内部是由Entry<K,V>类型的数组table来存储数据,其初始状态如下:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> Entry<span class="token operator"><</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> EMPTY_TABLE <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><span class="token keyword">transient</span> Entry<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> table <span class="token operator">=</span> <span class="token punctuation">(</span>Entry<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> EMPTY_TABLE<span class="token punctuation">;</span></code></pre><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Entry</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">Map<span class="token punctuation">.</span>Entry</span><span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">{</span> <span class="token keyword">final</span> K key<span class="token punctuation">;</span> V value<span class="token punctuation">;</span> Entry<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// key的hashCode方法的返回值经过hash运算得到的值</span> <span class="token keyword">int</span> hash<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** * Create new entry. */</span> <span class="token function">Entry</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">,</span>K k<span class="token punctuation">,</span>V <span class="token punctuation">,</span>v<span class="token punctuation">,</span>Entry<span class="token operator"><</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> n<span class="token punctuation">)</span><span class="token punctuation">{</span> value <span class="token operator">=</span> v<span class="token punctuation">;</span> next <span class="token operator">=</span> n<span class="token punctuation">;</span> key <span class="token operator">=</span> k<span class="token punctuation">;</span> hash <span class="token operator">=</span> h<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>HashMap的存储结构如下图所示:<br><img src="/img/post-img/19-5-21-2.png" alt="hashmap结构"></p><p>图中的每一个方格就表示一个Entry<K,V>对象,其中的横向则构成一个Entry<K,V>[] table数组,而竖向则是由Entry<K,V>的next属性形成的链表。</p><p>HashMap在添加元素(put)时的第一步就是计算该元素的key的hash值,用到如下方法:<br><img src="/img/post-img/19-5-21-3.png" alt="hash方法"></p><p>HashMap对于key的重复性判断是基于两个内容的判断,一个就是hash值是否一样(会演变成key的hashCode是否一样),另一个就是equals方法是否一样(引用一样则肯定一样)。<code>e.hash == hash && ((k = e.key) == key || key.equals(k))</code>。</p><p>hashCode重写的原则:<strong>当equals方法返回true,则两个对象的hashCode必须一样</strong>。</p><p>equals()方法在get()方法中的使用:<br><img src="/img/post-img/19-5-21-4.png" alt="get方法"><br><img src="/img/post-img/19-5-21-5.png" alt="getNode方法"></p><h3 id="为什么要同时覆写HashCode-和equals"><a href="#为什么要同时覆写HashCode-和equals" class="headerlink" title="为什么要同时覆写HashCode()和equals() ?"></a>为什么要同时覆写HashCode()和equals() ?</h3><ul><li>由于作为key的对象将通过计算其hashCode来确定与之对应的value的位置,因此任何作为key的对象都必须实现 hashCode和equals方法。</li><li>hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即<code>obj1.equals(obj2)=true</code>,则它们的hashCode必须相同,但如果两个对象不同,则它们的 hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,hashCode()方法目的纯粹用于提高效率,所以尽量定义好的 hashCode()方法,能加快哈希表的操作。</li></ul><blockquote><p>如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:<strong>要同时覆写(重载)equals方法和hashCode方法,而不要只写其中一个。</strong></p></blockquote>]]></content>
<categories>
<category> 技术文章 </category>
</categories>
<tags>
<tag> java </tag>
<tag> 基础知识点 </tag>
</tags>
</entry>
<entry>
<title>使用Jedis远程连接Redis时的小插曲</title>
<link href="/2019/05/19/%E4%BD%BF%E7%94%A8Jedis%E8%BF%9C%E7%A8%8B%E8%BF%9E%E6%8E%A5Redis%E6%97%B6%E7%9A%84%E5%B0%8F%E6%8F%92%E6%9B%B2/"/>
<url>/2019/05/19/%E4%BD%BF%E7%94%A8Jedis%E8%BF%9C%E7%A8%8B%E8%BF%9E%E6%8E%A5Redis%E6%97%B6%E7%9A%84%E5%B0%8F%E6%8F%92%E6%9B%B2/</url>
<content type="html"><![CDATA[<blockquote><p>Jedis是远程连接redis的主流集成工具,在使用Jedis的过程中踩了几个坑,特此纪念。</p></blockquote><p>从Maven依赖库库中下载两个jar包,分别是commons-pool2-2.4.2.jar和jedis-2.9.0.jar,版本不作要求。将这个两个jar包导入到工程中,然后开始编写程序。</p><p>先写一个简单的测试用例:<br><img src="/img/post-img/19-5-19-13.png" alt="测试用例"></p><p>其中192.168.94.129是我Linux虚拟机的ip地址,在保确保虚拟机上开启redis服务的前提下,运行测试用例,发现连接失败,怎么回事?</p><p>原来又是Linux防火墙,Linux防火墙将6379端口拦截掉了,那我们就在Linux系统上将6379端口打开:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># <span class="token operator">/</span>sbin<span class="token operator">/</span>iptables <span class="token operator">-</span>I INPUT <span class="token operator">-</span>p tcp <span class="token operator">--</span>dport <span class="token number">6379</span> <span class="token operator">-</span>j ACCEPT<span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># <span class="token operator">/</span>etc<span class="token operator">/</span>rc<span class="token punctuation">.</span>d<span class="token operator">/</span>init<span class="token punctuation">.</span>d<span class="token operator">/</span>iptables save</code></pre><p>然后再运行一次测试用例,发现和刚才一样,还是连接超时,一大堆的异常,这又是怎么回事呢?端口已经打开了呀!</p><p>可是仔细观察就会发现,在Linux虚拟机上连接到Redis服务的时候显示是127.0.0.1:6379>,那我们把ip换成127.0.0.1试一下,很遗憾,失败了。</p><p>是不是配置文件搞的鬼呢?进入redis.conf文件看一下,果然!有这么一段话:<br><img src="/img/post-img/19-5-19-14.png" alt="redis.conf"></p><blockquote><p>bind后边指明的ip地址才是访问redis的合法地址,所以我们在其下边加入bind 192.168.94.129之后保存退出。</p></blockquote><p>此时我们重新启动redis服务:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>redis<span class="token operator">-</span>cli shutdown<span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>redis<span class="token operator">-</span>server <span class="token punctuation">.</span><span class="token operator">/</span>redis<span class="token punctuation">.</span>conf </code></pre><p>然后再运行一次测试代码,哇,一抹绿色终于出现了,终于连接成功,可以用Java代码来操作redis啦,redis有什么指令,Jedis就有什么方法,所以Jedis的API根本不用去记,只要知道Redis有哪些常用的指令就好啦!</p><p>OK,问题解决啦,继续你的旅程吧!加油。</p>]]></content>
<categories>
<category> 技术文章 </category>
</categories>
<tags>
<tag> redis </tag>
<tag> Jedis </tag>
</tags>
</entry>
<entry>
<title>Linux系统上安装MySQL与远程访问配置</title>
<link href="/2019/05/19/Linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E5%AE%89%E8%A3%85MySQL%E4%B8%8E%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AE%E9%85%8D%E7%BD%AE/"/>
<url>/2019/05/19/Linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E5%AE%89%E8%A3%85MySQL%E4%B8%8E%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AE%E9%85%8D%E7%BD%AE/</url>
<content type="html"><![CDATA[<blockquote><p>今天花了一天的时间给Linux系统装MySQL,中途遇到了不少问题,导致重新开始了好几次,特此总结,以便复查。</p></blockquote><p><strong>首先说明一下环境,用的是VMware虚拟机搭载CentOS6.5的Linux系统,并用CRT远程访问控制,所用MySQL版本为mysql-5.7.23。</strong></p><p>Linux系统所用的MySQL的下载在这里就不赘述了,重点是安装与配置。</p><p>在安装之前有必要先提一下,需要先在Linux上安装一些必要的依赖,对于mysql的依赖安装使用以下命令即可:</p><pre class=" language-javascript"><code class="language-javascript">yum <span class="token operator">-</span>y install libaio<span class="token punctuation">.</span>so<span class="token number">.1</span> libgcc_s<span class="token punctuation">.</span>so<span class="token number">.1</span> libstdc<span class="token operator">++</span><span class="token punctuation">.</span>so<span class="token number">.6</span>yum update libstdc<span class="token operator">++</span><span class="token operator">-</span><span class="token number">4.4</span><span class="token punctuation">.</span><span class="token number">7</span><span class="token operator">-</span><span class="token number">4</span><span class="token punctuation">.</span>el6<span class="token punctuation">.</span>x86_64</code></pre><p>首先将下载好的mysql压缩包(mysql-5.7.23-linux-glibc2.12-x86_64.tar.gz)上传到linux,在CRT中使用快捷键Alt+P弹出上传窗口,将下载好的压缩包拖到该窗口,上传到家目录下(/root)。</p><p>在该目录下执行解压缩命令,解压到指定目录/usr/local下,并重命名为mysql,使用命令为<br><code>tar -zxvf mysql-5.7.23-linux-glibc2.12-x86_64.tar.gz -C /usr/local</code><br>(其中 ‘ -C ’ 是解压到指定目录)</p><p>在mysql目录下创建database 目录 、data 目录 、data/binlog 二进制日志目录 、 data/relay 主从复制的本地文件存放目录 、 data/tmp临时数据存放目录 , 并权限设置为0755:<br><img src="/img/post-img/5-19-4.png" alt="命令截图1"></p><p>然后创建 mysql 用户组和 mysql 用户 , 把 mysql 目录所有者赋给 mysql 用户,(这里显示已经存在,无所谓,不管有没有都执行一下),然后进入mysql目录:<br><img src="/img/post-img/5-19-5.png" alt="命令截图2"></p><p>然后初始化mysql数据库,使用命令如下:<br><code>[root@localhost mysql]# bin/mysql --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/datadase</code></p><p>初始化完成后会显示日志信息,最后一行会显示一个随机生成的初始密码,如果没有任何日志信息,可用cat命令查看data文件夹下的error.log文件,即 cat data/error.log,内容如下:<br><img src="/img/post-img/5-19-6.png" alt="命令截图3"></p><blockquote><p>可以看到随机生成的密码。建议将这个密码先复制到文本文件中,以防待会丢失。</p></blockquote><p>接下来为mysql的启动做一些服务性的准备,首先创建启动服务:<br><code>[root@localhost mysql]# cp support-files/mysql.server /etc/init.d/mysql</code></p><blockquote><p>至于这为什么叫创建启动服务,还有待我去研究,哈哈(滑稽)。</p></blockquote><p>然后配置环境变量,编辑/etc/profile文件:</p><p><code>[root@localhost mysql]# vim /ect/profile</code></p><p>加入如下三行后保存退出:<br><img src="/img/post-img/5-19-7.png" alt="命令截图4"></p><p>重新加载配置文件:<br><code>[root@localhost mysql]# service mysql start</code></p><p>然后配置一个叫my.cnf的mysql启动配置文件,那么它在哪呢?别着急,在/etc目录下新建一个my.cnf就好了,里边的东西自己写,对,就是自己写。主要配置有如下几个:详细配置见本文末尾附录。<br><img src="/img/post-img/5-19-8.png" alt="my.cnf主要配置"></p><p>现在万事俱备,准备启动mysql服务了,使用<code>service mysql start</code> 命令即可启动服务,emmmm,问题出现了,启动失败了,报了个错:<br><img src="/img/post-img/5-19-9.png" alt="启动报错"></p><p>原来是已经有mysql服务进程存在了,那就把已经存在的杀掉吧,使用 <code>ps -ef | grep mysql</code>把这些进程信息揪出来,然后<code>kill -9 PID</code>杀死进程,再执行服务启动指令,就没有问题啦,成功启动后显示如下:<br><img src="/img/post-img/5-19-10.png" alt="启动成功"></p><p>然后,我们就可以用熟悉的 mysql -u root -p来登录mysql了,注意密码是刚刚那个很难看的初始密码哦,认真点,不然容易输错哦。<br><img src="/img/post-img/5-19-11.png" alt="登录成功"></p><p>登陆成功后,随便运行一条SQL语句,例如<code>show databases;</code> 系统会提示强制修改初始密码,那就改呗:<br><code>set password=password('123456')</code> (密码自行设置)</p><p>修改之后就可以正常使用啦。不过我们不希望我们每次手动去启动mysql服务,我们希望它在开机时就自动启动,没有问题,Linux办得到!使用如下两个命令即可让mysql服务变为开机自启:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">[</span>root@localhost mysql<span class="token punctuation">]</span># chkconfig <span class="token operator">--</span>add mysql<span class="token punctuation">[</span>root@localhost mysql<span class="token punctuation">]</span># chkconfig mysql on</code></pre><blockquote><p>到此我们在Linux上安装mysql已经完成啦,但是还有一个问题,就是我们在开发中一般不会使用黑窗口去直接访问数据库,而是使用集成软件远程登录访问,比如可以用熟悉的SQL yog或者其他软件来访问,问题也就正出在这里,如果现在直接使用SQLyog连接Linux的mysql数据库是无法连接成功的,因为此时root用户还没有远程控制权限,只能在Linux本机上称霸。</p></blockquote><p>那么我们就需要给root用户这个权限,操作如下:<br><img src="/img/post-img/5-19-12.png" alt="修改权限"></p><blockquote><p>意为:将所有权限授予root用户,并以密码123456作为认证。刷新权限。</p></blockquote><p>然后我们再尝试远程连接数据库,发现还是连接不上这是怎么回事呢?</p><p>原来是Linux的防火墙默认将3306端口拦截了,我们还需要对Linux的防火墙进行一定的配置,执行如下两条指令:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">[</span>root@localhost mysql<span class="token punctuation">]</span># <span class="token operator">/</span>sbin<span class="token operator">/</span>iptables <span class="token operator">-</span>I INPUT <span class="token operator">-</span>p tcp <span class="token operator">--</span>dport <span class="token number">3306</span> j ACCEPT<span class="token punctuation">[</span>root@localhost mysql<span class="token punctuation">]</span># <span class="token operator">/</span>etc<span class="token operator">/</span>rc<span class="token punctuation">.</span>d<span class="token operator">/</span>init<span class="token punctuation">.</span>d<span class="token operator">/</span>iptables save</code></pre><p>可以看到防火墙将3306端口设为允许,并保存。</p><p>然后我们再尝试连接,就可以连接得上啦。</p><p>搞定!</p><h2 id="附录:my-cnf完整配置"><a href="#附录:my-cnf完整配置" class="headerlink" title="附录:my.cnf完整配置"></a>附录:my.cnf完整配置</h2><pre class=" language-dsconfig"><code class="language-dsconfig">[client]#客户端设置,即客户端默认的连接参数port = 3306#默认连接端口socket = /usr/local/mysql/data/mysql.sock#用于本地连接的socket套接字default-character-set = utf8mb4#编码[mysqld] #服务端基本设置port = 3306#MySQL监听端口socket = /usr/local/mysql/data/mysql.sock#为MySQL客户端程序和服务器之间的本地通讯指定一个套接字文件pid-file = /usr/local/mysql/data/mysql.pid#pid文件所在目录basedir = /usr/local/mysql#使用该目录作为根目录(安装目录)datadir = /usr/local/mysql/database#数据文件存放的目录tmpdir = /usr/local/mysql/data/tmp#MySQL存放临时文件的目录character_set_server = utf8mb4#服务端默认编码(数据库级别)collation_server = utf8mb4_bin#服务端默认的比对规则,排序规则user = mysql#MySQL启动用户log-error=/usr/local/mysql/data/error.log#错误日志配置文件(configure file)secure-file-priv = nulllog_bin_trust_function_creators = 1#This variable applies when binary logging is enabled. It controls whether stored function creators can be trusted not to create stored functions that will cause#unsafe events to be written to the binary log. If set to 0 (the default), users are not permitted to create or alter stored functions unless they have the SUPER#privilege in addition to the CREATE ROUTINE or ALTER ROUTINE privilege. 开启了binlog后,必须设置这个值为1.主要是考虑binlog安全performance_schema = 0#性能优化的引擎,默认关闭#ft_min_word_len = 1#开启全文索引#myisam_recover#自动修复MySQL的myisam表explicit_defaults_for_timestamp#明确时间戳默认null方式event_scheduler#计划任务(事件调度器)skip-external-locking#跳过外部锁定;External-locking用于多进程条件下为MyISAM数据表进行锁定skip-name-resolve#跳过客户端域名解析;当新的客户连接mysqld时,mysqld创建一个新的线程来处理请求。该线程先检查是否主机名在主机名缓存中。如果不在,线程试图解析主机名。#使用这一选项以消除MySQL进行DNS解析的时间。但需要注意,如果开启该选项,则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求!#bind-address = 127.0.0.1#MySQL绑定IPskip-slave-start#为了安全起见,复制环境的数据库还是设置--skip-slave-start参数,防止复制随着mysql启动而自动启动slave_net_timeout = 30#The number of seconds to wait for more data from a master/slave connection before aborting the read. MySQL主从复制的时候,#当Master和Slave之间的网络中断,但是Master和Slave无法察觉的情况下(比如防火墙或者路由问题)。#Slave会等待slave_net_timeout设置的秒数后,才能认为网络出现故障,然后才会重连并且追赶这段时间主库的数据。#1.用这三个参数来判断主从是否延迟是不准确的Slave_IO_Running,Slave_SQL_Running,Seconds_Behind_Master.还是用pt-heartbeat吧。#2.slave_net_timeout不要用默认值,设置一个你能接受的延时时间。local-infile = 0#设定是否支持命令load data local infile。如果指定local关键词,则表明支持从客户主机读文件back_log = 1024#指定MySQL可能的连接数量。当MySQL主线程在很短的时间内得到非常多的连接请求,该参数就起作用,之后主线程花些时间(尽管很短)检查连接并且启动一个新线程。#back_log参数的值指出在MySQL暂时停止响应新请求之前的短时间内多少个请求可以被存在堆栈中。#sql_mode = 'PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'sql_mode = NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER#sql_mode,定义了mysql应该支持的sql语法,数据校验等! NO_AUTO_CREATE_USER:禁止GRANT创建密码为空的用户。#NO_ENGINE_SUBSTITUTION 如果需要的存储引擎被禁用或未编译,可以防止自动替换存储引擎key_buffer_size = 32M#索引块的缓冲区大小,对MyISAM表性能影响最大的一个参数.决定索引处理的速度,尤其是索引读的速度。默认值是16M,通过检查状态值Key_read_requests#和Key_reads,可以知道key_buffer_size设置是否合理max_allowed_packet = 512M#一个查询语句包的最大尺寸。消息缓冲区被初始化为net_buffer_length字节,但是可在需要时增加到max_allowed_packet个字节。#该值太小则会在处理大包时产生错误。如果使用大的BLOB列,必须增加该值。#这个值来限制server接受的数据包大小。有时候大的插入和更新会受max_allowed_packet 参数限制,导致写入或者更新失败。thread_stack = 256K#线程缓存;主要用来存放每一个线程自身的标识信息,如线程id,线程运行时基本信息等等,我们可以通过 thread_stack 参数来设置为每一个线程栈分配多大的内存。sort_buffer_size = 16M#是MySQL执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。#如果不能,可以尝试增加sort_buffer_size变量的大小。read_buffer_size = 16M#是MySQL读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。#如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。join_buffer_size = 16M#应用程序经常会出现一些两表(或多表)Join的操作需求,MySQL在完成某些 Join 需求的时候(all/index join),为了减少参与Join的“被驱动表”的#读取次数以提高性能,需要使用到 Join Buffer 来协助完成 Join操作。当 Join Buffer 太小,MySQL 不会将该 Buffer 存入磁盘文件,#而是先将Join Buffer中的结果集与需要 Join 的表进行 Join 操作,#然后清空 Join Buffer 中的数据,继续将剩余的结果集写入此 Buffer 中,如此往复。这势必会造成被驱动表需要被多次读取,成倍增加 IO 访问,降低效率。read_rnd_buffer_size = 32M#是MySQL的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySQL会首先扫描一遍该缓冲,以避免磁盘搜索,#提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。net_buffer_length = 16K#通信缓冲区在查询期间被重置到该大小。通常不要改变该参数值,但是如果内存不足,可以将它设置为查询期望的大小。#(即,客户发出的SQL语句期望的长度。如果语句超过这个长度,缓冲区自动地被扩大,直到max_allowed_packet个字节。)myisam_sort_buffer_size = 128M#当对MyISAM表执行repair table或创建索引时,用以缓存排序索引;设置太小时可能会遇到” myisam_sort_buffer_size is too small”bulk_insert_buffer_size = 32M#默认8M,当对MyISAM非空表执行insert … select/ insert … values(…),(…)或者load data infile时,使用树状cache缓存数据,每个thread分配一个;#注:当对MyISAM表load 大文件时,调大bulk_insert_buffer_size/myisam_sort_buffer_size/key_buffer_size会极大提升速度thread_cache_size = 384#thread_cahe_size线程池,线程缓存。用来缓存空闲的线程,以至于不被销毁,如果线程缓存在的空闲线程,需要重新建立新连接,#则会优先调用线程池中的缓存,很快就能响应连接请求。每建立一个连接,都需要一个线程与之匹配。query_cache_size = 0#工作原理: 一个SELECT查询在DB中工作后,DB会把该语句缓存下来,当同样的一个SQL再次来到DB里调用时,DB在该表没发生变化的情况下把结果从缓存中返回给Client。#在数据库写入量或是更新量也比较大的系统,该参数不适合分配过大。而且在高并发,写入量大的系统,建系把该功能禁掉。query_cache_type = 0#决定是否缓存查询结果。这个变量有三个取值:0,1,2,分别代表了off、on、demand。tmp_table_size = 1024M#它规定了内部内存临时表的最大值,每个线程都要分配。(实际起限制作用的是tmp_table_size和max_heap_table_size的最小值。)#如果内存临时表超出了限制,MySQL就会自动地把它转化为基于磁盘的MyISAM表,存储在指定的tmpdir目录下max_heap_table_size = 512M#独立的内存表所允许的最大容量.# 此选项为了防止意外创建一个超大的内存表导致永尽所有的内存资源.open_files_limit = 10240#mysql打开最大文件数max_connections = 2000#MySQL无论如何都会保留一个用于管理员(SUPER)登陆的连接,用于管理员连接数据库进行维护操作,即使当前连接数已经达到了max_connections。#因此MySQL的实际最大可连接数为max_connections+1;#这个参数实际起作用的最大值(实际最大可连接数)为16384,即该参数最大值不能超过16384,即使超过也以16384为准;#增加max_connections参数的值,不会占用太多系统资源。系统资源(CPU、内存)的占用主要取决于查询的密度、效率等;#该参数设置过小的最明显特征是出现”Too many connections”错误;max-user-connections = 0#用来限制用户资源的,0不限制;对整个服务器的用户限制max_connect_errors = 100000#max_connect_errors是一个MySQL中与安全有关的计数器值,它负责阻止过多尝试失败的客户端以防止暴力破解密码的情况。max_connect_errors的值与性能并无太大关系。#当此值设置为10时,意味着如果某一客户端尝试连接此MySQL服务器,但是失败(如密码错误等等)10次,则MySQL会无条件强制阻止此客户端连接。table_open_cache = 5120#表描述符缓存大小,可减少文件打开/关闭次数;interactive_timeout = 86400#interactive_time -- 指的是mysql在关闭一个交互的连接之前所要等待的秒数(交互连接如mysql gui tool中的连接wait_timeout = 86400#wait_timeout -- 指的是MySQL在关闭一个非交互的连接之前所要等待的秒数binlog_cache_size = 16M#二进制日志缓冲大小#我们知道InnoDB存储引擎是支持事务的,实现事务需要依赖于日志技术,为了性能,日志编码采用二进制格式。那么,我们如何记日志呢?有日志的时候,就直接写磁盘?#可是磁盘的效率是很低的,如果你用过Nginx,,一般Nginx输出access log都是要缓冲输出的。因此,记录二进制日志的时候,我们是否也需要考虑Cache呢?#答案是肯定的,但是Cache不是直接持久化,于是面临安全性的问题——因为系统宕机时,Cache中可能有残余的数据没来得及写入磁盘。因此,Cache要权衡,要恰到好处:#既减少磁盘I/O,满足性能要求;又保证Cache无残留,及时持久化,满足安全要求。slow_query_log = true#开启慢查询slow_query_log_file = /usr/local/mysql/data/slow_query_log.log#慢查询地址long_query_time = 1#超过的时间为1s;MySQL能够记录执行时间超过参数 long_query_time 设置值的SQL语句,默认是不记录的。log-slow-admin-statementslog-queries-not-using-indexes#记录管理语句和没有使用index的查询记录# 主从复制配置 *****************************************************# *** Replication related settings ***binlog_format = ROW#在复制方面的改进就是引进了新的复制技术:基于行的复制。简言之,这种新技术就是关注表中发生变化的记录,而非以前的照抄 binlog 模式。#从 MySQL 5.1.12 开始,可以用以下三种模式来实现:基于SQL语句的复制(statement-based replication, SBR),基于行的复制(row-based replication, RBR),混合模式复制(mixed-based replication, MBR)。相应地,binlog的格式也有三种:STATEMENT,ROW,MIXED。MBR 模式中,SBR 模式是默认的。#max_binlog_cache_size = 102400# 为每个session 最大可分配的内存,在事务过程中用来存储二进制日志的缓存。log-bin = /usr/local/mysql/data/binlog/mysql-bin#开启二进制日志功能,binlog数据位置log-bin-index = /usr/local/mysql/data/binlog/mysql-bin.indexrelay-log = /usr/local/mysql/data/relay/mysql-relay-bin#relay-log日志记录的是从服务器I/O线程将主服务器的二进制日志读取过来记录到从服务器本地文件,#然后SQL线程会读取relay-log日志的内容并应用到从服务器relay-log-index = /usr/local/mysql/data/relay/mysql-relay-bin.index#binlog传到备机被写道relaylog里,备机的slave sql线程从relaylog里读取然后应用到本地。# *******************主要配置*********************# 主服务器配置server-id = 1#服务端ID,用来高可用时做区分#binlog-ignore-db = mysql#binlog-ignore-db = sys#binlog-ignore-db = binlog#binlog-ignore-db = relay#binlog-ignore-db = tmp#binlog-ignore-db = test#binlog-ignore-db = information_schema#binlog-ignore-db = performance_schema# 不同步哪些数据库#binlog-do-db = game# 只同步哪些数据库,除此之外,其他不同步# 从服务器配置#server-id = 2#服务端ID,用来高可用时做区分#replicate-ignore-db = mysql#replicate-ignore-db = sys#replicate-ignore-db = relay#replicate-ignore-db = tmp#replicate-ignore-db = test#replicate-ignore-db = information_schema#replicate-ignore-db = performance_schema# 不同步哪些数据库#replicate-do-db = game# 只同步哪些数据库,除此之外,其他不同步# *******************主要配置*********************log_slave_updates = 1#log_slave_updates是将从服务器从主服务器收到的更新记入到从服务器自己的二进制日志文件中。expire-logs-days = 15#二进制日志自动删除的天数。默认值为0,表示“没有自动删除”。启动时和二进制日志循环时可能删除。max_binlog_size = 128M#如果二进制日志写入的内容超出给定值,日志就会发生滚动。你不能将该变量设置为大于1GB或小于4096字节。 默认值是1GB。#replicate-wild-ignore-table = mysql.%#replicate-wild-ignore-table参数能同步所有跨数据库的更新,比如replicate-do-db或者replicate-ignore-db不会同步类似#replicate-wild-do-table = db_name.%#设定需要复制的Table#slave-skip-errors = 1062,1053,1146#复制时跳过一些错误;不要胡乱使用这些跳过错误的参数,除非你非常确定你在做什么。当你使用这些参数时候,MYSQL会忽略那些错误,#这样会导致你的主从服务器数据不一致。auto_increment_offset = 1auto_increment_increment = 2#这两个参数一般用在主主同步中,用来错开自增值, 防止键值冲突relay_log_info_repository = TABLE#将中继日志的信息写入表:mysql.slave_realy_log_infomaster_info_repository = TABLE#将master的连接信息写入表:mysql.salve_master_inforelay_log_recovery = on#中继日志自我修复;当slave从库宕机后,假如relay-log损坏了,导致一部分中继日志没有处理,则自动放弃所有未执行的relay-log,#并且重新从master上获取日志,这样就保证了relay-log的完整性# 主从复制配置结束 *****************************************************# *** innodb setting ***innodb_buffer_pool_size = 128M#InnoDB 用来高速缓冲数据和索引内存缓冲大小。 更大的设置可以使访问数据时减少磁盘 I/O。innodb_data_file_path = ibdata1:10M:autoextend#单独指定数据文件的路径与大小innodb_flush_log_at_trx_commit = 2#每次commit 日志缓存中的数据刷到磁盘中。通常设置为 1,意味着在事务提交前日志已被写入磁盘, 事务可以运行更长以及服务崩溃后的修复能力。#如果你愿意减弱这个安全,或你运行的是比较小的事务处理,可以将它设置为 0 ,以减少写日志文件的磁盘 I/O。这个选项默认设置为 0。#sync_binlog = 1000#sync_binlog=n,当每进行n次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。innodb_read_io_threads = 8innodb_write_io_threads = 8#对于多核的CPU机器,可以修改innodb_read_io_threads和innodb_write_io_threads来增加IO线程,来充分利用多核的性能innodb_open_files = 1000#限制Innodb能打开的表的数量innodb_purge_threads = 1#开始碎片回收线程。这个应该能让碎片回收得更及时而且不影响其他线程的操作innodb_log_buffer_size = 8M#InnoDB 将日志写入日志磁盘文件前的缓冲大小。理想值为 1M 至 8M。大的日志缓冲允许事务运行时不需要将日志保存入磁盘而只到事务被提交(commit)。#因此,如果有大的事务处理,设置大的日志缓冲可以减少磁盘I/O。innodb_log_file_size = 128M #日志组中的每个日志文件的大小(单位 MB)。如果 n 是日志组中日志文件的数目,那么理想的数值为 1M 至下面设置的缓冲池(buffer pool)大小的 1/n。较大的值,#可以减少刷新缓冲池的次数,从而减少磁盘 I/O。但是大的日志文件意味着在崩溃时需要更长的时间来恢复数据。innodb_log_files_in_group = 3#指定有三个日志组#innodb_lock_wait_timeout = 120#在回滚(rooled back)之前,InnoDB 事务将等待超时的时间(单位 秒)innodb_max_dirty_pages_pct = 75#innodb_max_dirty_pages_pct作用:控制Innodb的脏页在缓冲中在那个百分比之下,值在范围1-100,默认为90.这个参数的另一个用处:#当Innodb的内存分配过大,致使swap占用严重时,可以适当的减小调整这个值,使达到swap空间释放出来。建义:这个值最大在90%,最小在15%。#太大,缓存中每次更新需要致换数据页太多,太小,放的数据页太小,更新操作太慢。innodb_buffer_pool_instances = 4#innodb_buffer_pool_size 一致 可以开启多个内存缓冲池,把需要缓冲的数据hash到不同的缓冲池中,这样可以并行的内存读写。innodb_io_capacity = 500#这个参数据控制Innodb checkpoint时的IO能力innodb_file_per_table = 1#作用:使每个Innodb的表,有自已独立的表空间。如删除文件后可以回收那部分空间。#分配原则:只有使用不使用。但DB还需要有一个公共的表空间。innodb_change_buffering = inserts#当更新/插入的非聚集索引的数据所对应的页不在内存中时(对非聚集索引的更新操作通常会带来随机IO),会将其放到一个insert buffer中, #当随后页面被读到内存中时,会将这些变化的记录merge到页中。当服务器比较空闲时,后台线程也会做merge操作innodb_adaptive_flushing = 1#该值影响每秒刷新脏页的操作,开启此配置后,刷新脏页会通过判断产生重做日志的速度来判断最合适的刷新脏页的数量;transaction-isolation = READ-COMMITTED#数据库事务隔离级别 ,读取提交内容innodb_flush_method = fsync#innodb_flush_method这个参数控制着innodb数据文件及redo log的打开、刷写模式#InnoDB使用O_DIRECT模式打开数据文件,用fsync()函数去更新日志和数据文件。#innodb_use_sys_malloc = 1#默认设置值为1.设置为0:表示Innodb使用自带的内存分配程序;设置为1:表示InnoDB使用操作系统的内存分配程序。[mysqldump]quick#它强制 mysqldump 从服务器查询取得记录直接输出而不是取得所有记录后将它们缓存到内存中max_allowed_packet = 512M#限制server接受的数据包大小;指代mysql服务器端和客户端在一次传送数据包的过程当中数据包的大小net_buffer_length = 16384#TCP/IP和套接字通信缓冲区大小,创建长度达net_buffer_length的行[mysql]auto-rehash#auto-rehash是自动补全的意思[isamchk]#isamchk数据检测恢复工具key_buffer = 256Msort_buffer_size = 256Mread_buffer = 2Mwrite_buffer = 2M[myisamchk]#使用myisamchk实用程序来获得有关你的数据库桌表的信息、检查和修复他们或优化他们key_buffer = 256Msort_buffer_size = 256Mread_buffer = 2Mwrite_buffer = 2M[mysqlhotcopy]interactive-timeout#mysqlhotcopy使用lock tables、flush tables和cp或scp来快速备份数据库.它是备份数据库或单个表最快的途径,完全属于物理备份,但只能用于备份MyISAM存储引擎和运行在数据库目录所在的机器上.#与mysqldump备份不同,mysqldump属于逻辑备份,备份时是执行的sql语句.使用mysqlhotcopy命令前需要要安装相应的软件依赖包.--------------------- 作者:Little__Sheep 来源:CSDN 原文:https://blog.csdn.net/weixin_41657730/article/details/89763001 版权声明:本文为博主原创文章,转载请附上博文链接!</code></pre>]]></content>
<categories>
<category> 技术文章 </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> 环境搭建 </tag>
<tag> mysql </tag>
</tags>
</entry>
<entry>
<title>Linux系统上安装JDK、Tomcat以及Redis</title>
<link href="/2019/05/19/Linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E5%AE%89%E8%A3%85JDK%E3%80%81Tomcat%E4%BB%A5%E5%8F%8ARedis/"/>
<url>/2019/05/19/Linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E5%AE%89%E8%A3%85JDK%E3%80%81Tomcat%E4%BB%A5%E5%8F%8ARedis/</url>
<content type="html"><![CDATA[<blockquote><p>环境:VMware搭载CentOS6.5版本Linux系统,SecureCRT远程登录控制,安装JDK1.8,Tomcat9.0.10,Redis5.0.4</p></blockquote><h3 id="一、安装JDK1-8"><a href="#一、安装JDK1-8" class="headerlink" title="一、安装JDK1.8"></a>一、安装JDK1.8</h3><p>首先检查Linux系统上是否有JDK,一般Linux系统会有默认的openJDK,将其卸载掉。</p><pre class=" language-javascript"><code class="language-javascript">rpm <span class="token operator">-</span>qa <span class="token operator">|</span> grep <span class="token operator">-</span>i java <span class="token comment" spellcheck="true">// 查询系统上是否存在默认JDK</span>rpm <span class="token operator">-</span>e <span class="token operator">--</span>nodeps 查出来的程序名 <span class="token comment" spellcheck="true">// 将查询出来的默认JDK卸载掉</span></code></pre><p>安装依赖:</p><p><code>yum install glibc.i686</code></p><p>将下载好的.tar.gz压缩包上传到Linux系统,解压到/usr/local/java目录下。</p><pre class=" language-javascript"><code class="language-javascript">mkdir <span class="token operator">-</span>p <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>java <span class="token comment" spellcheck="true">// 循环创建文件夹</span>tar <span class="token operator">-</span>zxvf jdk<span class="token operator">-</span>8u181<span class="token operator">-</span>linux<span class="token operator">-</span>x64<span class="token punctuation">.</span>tar<span class="token punctuation">.</span>gz <span class="token operator">-</span>C <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>java <span class="token comment" spellcheck="true">// 将压缩包解压到指定目录下</span></code></pre><p>配置环境变量:</p><pre class=" language-javascript"><code class="language-javascript">vim <span class="token operator">/</span>etc<span class="token operator">/</span>profile <span class="token comment" spellcheck="true">// 使用vim编辑器查看配置文件</span><span class="token comment" spellcheck="true">// 按下i键进入修改模式</span><span class="token comment" spellcheck="true">//在文件末尾加入以下内容:</span>#<span class="token keyword">set</span> java enviromentJAVA_HOME<span class="token operator">=</span><span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>java<span class="token operator">/</span>jdk1<span class="token number">.8</span><span class="token punctuation">.</span>0_181CLASSPATH<span class="token operator">=</span><span class="token punctuation">.</span><span class="token punctuation">:</span>$JAVA_HOME<span class="token operator">/</span>lib<span class="token punctuation">.</span>tools<span class="token punctuation">.</span>jarPATH<span class="token operator">=</span>$JAVA_HOME<span class="token operator">/</span>bin<span class="token punctuation">:</span>$PATH<span class="token keyword">export</span> JAVA_HOME CLASSPATH PATH<span class="token comment" spellcheck="true">// 按Ctrl+C退出编辑,:wq保存退出</span><span class="token comment" spellcheck="true">// 执行以下命令重新加载环境变量</span>source <span class="token operator">/</span>etc<span class="token operator">/</span>profile </code></pre><p>至此JDK1.8安装完成。</p><h3 id="二、安装Tomcat9-0-10"><a href="#二、安装Tomcat9-0-10" class="headerlink" title="二、安装Tomcat9.0.10"></a>二、安装Tomcat9.0.10</h3><p>将下载好的.tar.gz压缩包上传到Linux系统,并解压到/usr/local/tomcat目录下:</p><pre class=" language-javascript"><code class="language-javascript">mkdir <span class="token operator">-</span>p <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>tomcat <span class="token comment" spellcheck="true">// 创建文件夹</span>tar <span class="token operator">-</span>zxvf apache<span class="token operator">-</span>tomcat<span class="token number">-9.0</span><span class="token punctuation">.</span><span class="token number">10</span><span class="token punctuation">.</span>tar<span class="token punctuation">.</span>gz <span class="token operator">-</span>C <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>tomcat <span class="token comment" spellcheck="true">// 解压到指定文件夹</span></code></pre><p>进入安装目录下的bin目录,运行startup.sh文件,启动服务器。</p><pre class=" language-javascript"><code class="language-javascript">cd <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>tomcat<span class="token operator">/</span>apache<span class="token operator">-</span>tomcat<span class="token number">-9.0</span><span class="token punctuation">.</span><span class="token number">10</span><span class="token punctuation">.</span>tar<span class="token punctuation">.</span>gz<span class="token operator">/</span>bin<span class="token punctuation">.</span><span class="token operator">/</span>startup<span class="token punctuation">.</span>sh <span class="token comment" spellcheck="true">// 启动服务</span></code></pre><p>看到如下信息表示服务启动成功:</p><p><img src="/img/post-img/5-19.png" alt="tomcat启动成功"></p><p>在主机(安装VMware的电脑)上访问ip(Linux虚拟机的ip)+端口号(8080),咦,怎么访问不到?</p><p>原来是Linux防火墙默认拦截了8080端口,只要把端口打开就好了。</p><pre class=" language-javascript"><code class="language-javascript"><span class="token operator">/</span>sbin<span class="token operator">/</span>iptables <span class="token operator">-</span>I INPUT <span class="token operator">-</span>p tcp <span class="token operator">--</span>dprot <span class="token number">8080</span> <span class="token operator">-</span>j ACCEPT <span class="token comment" spellcheck="true">// 打开8080端口</span><span class="token operator">/</span>etc<span class="token operator">/</span>rc<span class="token punctuation">.</span>d<span class="token operator">/</span>init<span class="token punctuation">.</span>d<span class="token operator">/</span>iptables save <span class="token comment" spellcheck="true">// 保存配置</span></code></pre><p>然后再访问ip+8080就可以看到熟悉的tom猫页面了:</p><p><img src="/img/post-img/5-19-1.png" alt="tomcat主页"></p><p>关闭服务器:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">// 在bin目录下执行:</span><span class="token punctuation">.</span><span class="token operator">/</span>shutdowm<span class="token punctuation">.</span>sh</code></pre><p>至此Tomcat9.0.10安装完毕。</p><h3 id="三、安装Redis5-0-4"><a href="#三、安装Redis5-0-4" class="headerlink" title="三、安装Redis5.0.4"></a>三、安装Redis5.0.4</h3><p>安装依赖:</p><p><code>yum install gcc-c++</code></p><p>到官网下载压缩包,上传到Linux系统,并解压。</p><p><code>tar -zxvf redis-5.0.4.tar.gz // 直接解压到当前目录即可</code></p><p>进入刚刚解压的redis-5.0.4目录,在该目录下执行 make 命令,进行编译。</p><p>等一会过后出现如下信息表示编译成功:</p><p><img src="/img/post-img/5-19-2.png" alt="redis编译成功"></p><p>然后在该目录下执行以下命令进行安装:(/usr/local/redis文件夹会自动创建)</p><pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">[</span>root@localhost redis<span class="token number">-5.0</span><span class="token punctuation">.</span><span class="token number">4</span><span class="token punctuation">]</span># make PREFIX<span class="token operator">=</span><span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>redis install<span class="token comment" spellcheck="true">// 将配置文件复制到安装目录下,用以自定义配置</span><span class="token punctuation">[</span>root@localhost redis<span class="token number">-5.0</span><span class="token punctuation">.</span><span class="token number">4</span><span class="token punctuation">]</span># cp redis<span class="token punctuation">.</span>conf <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>redis</code></pre><p>前端启动redis服务:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token punctuation">[</span>root@localhost redis<span class="token number">-5.0</span><span class="token punctuation">.</span><span class="token number">4</span><span class="token punctuation">]</span># cd <span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>redis<span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>redis<span class="token operator">-</span>server</code></pre><p>出现如下信息表示服务启动成功:</p><p><img src="/img/post-img/5-19-3.png" alt="redis启动成功"></p><p>其中6379是Redis默认端口号,PID是进程ID,方便停止进程。</p><p>在另一个窗口(session)中使用如下命令连接Redis服务:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">// 默认连接本机的6379端口</span><span class="token punctuation">[</span>root@localhost bin<span class="token punctuation">]</span># <span class="token punctuation">.</span><span class="token operator">/</span>redis<span class="token operator">-</span>cli <span class="token comment" spellcheck="true">// 根据ip和端口号连接指定redis服务</span><span class="token punctuation">[</span>root@localhost bin<span class="token punctuation">]</span># <span class="token punctuation">.</span><span class="token operator">/</span>redis<span class="token operator">-</span>cli <span class="token operator">-</span>h <span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span> <span class="token operator">-</span>p <span class="token number">6379</span> </code></pre><p>后端启动redis服务:</p><p>进入redis.conf文件修改daemonize属性为yes</p><p>然后启动服务的同时加载配置文件。</p><p><code>[root@localhost redis]# ./bin/redis-server ./redis.conf</code></p><p>页面显示没有前端启动那么花里胡哨,但确实启动了,所谓后端启动。</p><p>关闭redis:</p><pre class=" language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">// 1、查询到pid 使用 kill -9 pid 杀死进程</span><span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># ps <span class="token operator">-</span>ef <span class="token operator">|</span> grep redis<span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># kill <span class="token operator">-</span><span class="token number">9</span> pid<span class="token comment" spellcheck="true">// 2、正常结束</span><span class="token punctuation">[</span>root@localhost redis<span class="token punctuation">]</span># <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>redis<span class="token operator">-</span>cli shutdown</code></pre><p>至此,Redis5.0.4安装完毕。</p><p>开启你的Redis之旅吧。</p>]]></content>
<categories>
<category> 技术文章 </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> 环境搭建 </tag>
<tag> redis </tag>
</tags>
</entry>
<entry>
<title>ArrayList与LinkList对比</title>
<link href="/2019/05/04/ArrayList%E4%B8%8ELinkedList%E5%AF%B9%E6%AF%94/"/>
<url>/2019/05/04/ArrayList%E4%B8%8ELinkedList%E5%AF%B9%E6%AF%94/</url>
<content type="html"><![CDATA[<blockquote><p>本文简要总结一下java中ArrayList与LinkedList的区别,这在面试中也是常常会问到的一个知识点。</p></blockquote><p>先来看一下ArrayList和LinkedList的关系是怎样的:</p><p><img src="/img/post-img/List.png" alt="ArrayList与LinkedList的关系图"></p><p>从继承体系可以看到,ArrayList与LinkedList都是Collection接口下List接口的实现类。可谓是一对双胞胎。</p><p>但由于底层数据结构的不同导致ArrayList与LinkedList有本质上的区别。</p><h4 id="ArrayList与LinkedList的区别"><a href="#ArrayList与LinkedList的区别" class="headerlink" title="ArrayList与LinkedList的区别"></a>ArrayList与LinkedList的区别</h4><ul><li>ArrayList:<br> ArrayList是基于<strong>动态数组</strong>的数据结构。<br> 因为是数组,所以ArrayList在初始化的时候,有<strong>初始大小10</strong>,插入新元素的时候,会判断是否需要扩容,扩容的步长是<strong>0.5倍原容量</strong>,扩容方式是利用<strong>数组的复制</strong>,因此有一定的开销;<br> 另外,ArrayList在进行元素插入的时候,需要<strong>移动插入位置之后的所有元素</strong>,位置越靠前,需要位移的元素越多,开销越大,相反,插入位置越靠后的话,开销就越小了,如果在最后面进行插入,那就不需要进行位移;</li></ul><hr><ul><li>LinkedList:<br> 内部使用基于<strong>链表</strong>的数据结构实现存储,LinkedList有一个内部类作为存放元素的单元,里面有三个属性,用来存放元素本身以及前后2个单元的引用,另外LinkedList内部还有一个header属性,用来标识起始位置,LinkedList的第一个单元和最后一个单元都会指向header,因此形成了一个<strong>双向</strong>的链表结构。<br> LinkedList是采用双向链表实现的。所以它也具有链表的特点,每一个元素(结点)的地址不连续,通过引用找到当前结点的上一个结点和下一个结点,即插入和删除效率较高,只需要常数时间,而get和set则较为低效。</li></ul><blockquote><p>LinkedList的方法和使用和ArrayList大致相同,由于LinkedList是链表实现的,所以额外提供了在头部和尾部添加/删除元素的方法,也没有ArrayList扩容的问题了。另外,ArrayList和LinkedList都可以实现栈、队列等数据结构,但LinkedList本身实现了队列的接口,所以更推荐用LinkedList来实现队列和栈。</p></blockquote><h5 id="总而言之,ArrayList和LinkedList的区别有以下几点:"><a href="#总而言之,ArrayList和LinkedList的区别有以下几点:" class="headerlink" title="总而言之,ArrayList和LinkedList的区别有以下几点:"></a>总而言之,ArrayList和LinkedList的区别有以下几点:</h5><ul><li> ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构;</li><li> 对于随机访问元素,Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大的,因为这需要重排数组中的所有数据。ArrayList想要<strong>get(int index)</strong> 元素时,直接返回index位置上的元素,而LinkedList需要通过for循环进行查找,虽然LinkedList已经在查找方法上做了优化,比如<strong>index < size / 2</strong>,则从左边开始查找,反之从右边开始查找,但是还是比ArrayList要慢。</li><li> 对于添加和删除操作add和remove,LinkedList是更快的。因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。</li><li> ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)。 ArrayList想要在指定位置插入或删除元素时,主要耗时的是<strong>System.arraycopy</strong>动作,会移动index后面所有的元素;LinkedList主耗时的是要先通过for循环找到index,然后直接插入或删除。这就导致了两者并非一定谁快谁慢。</li></ul><blockquote><p>适用场景</p></blockquote><p>很多场景下都是ArrayList更受欢迎。但有些情况下LinkedList更为合适,比如:</p><ol><li><p>你的应用不会随机访问数据。因为如果你需要LinkedList中的第n个元素的时候,你需要从第一个元素顺序数到第n个数据,然后读取数据。</p></li><li><p>你的应用有更多的插入和删除元素操作,更少的读取数据。因为插入和删除元素不涉及重排数据,所以它要比ArrayList要快。</p></li></ol><p>以上就是关于ArrayList和LinkedList的差别。你需要一个不同步的基于索引的数据访问时,请尽量使用ArrayList。ArrayList很快,也很容易使用。但是要记得要给定一个合适的初始大小,尽可能的减少更改数组的大小。</p>]]></content>
<categories>
<category> 技术文章 </category>
<category> 集合 </category>
</categories>
<tags>
<tag> java </tag>
<tag> 集合 </tag>
<tag> ArrayList </tag>
<tag> LinkedList </tag>
</tags>
</entry>
</search>