-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
1499 lines (1449 loc) · 222 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>算法之字符串的排列</title>
<url>/paramountNinja.github.io/2018/04/09/%E7%AE%97%E6%B3%95%E4%B9%8B%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97/</url>
<content><![CDATA[<p><strong>题目描述:</strong><br>输入一个字符串,按字典序打印出该字符串中字符的所有排列。<br>例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。<br><strong>输入描述:</strong><br>输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。</p>
<a id="more"></a>
<p><strong>思路:</strong><br>通过交换得到不同的平行情况,通过固定位置得到当前情况下的不同情况。如下图所示:<br><img src="/img/article/code1.PNG" alt=""></p>
<ol>
<li>首先交换A和A,其实就是固定好A; </li>
<li>然后我们交换B和B得到第一种情况,固定好B,最终只有一个元素未固定那么可以输出ABC; </li>
<li>此时发生回溯,交换回B和B回到上一层,然后交换B和C,其实就是固定住C。最终只有一个元素未固定那么可以输出ACB; </li>
<li>此时发生回溯,往上交换回元素,同样的方法走后面几条路径; </li>
<li>最终通过排序将所有的情况按照字典序排列</li>
</ol>
<p><strong>代码:</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">import java.util.ArrayList;</span><br><span class="line">import java.util.Collections;</span><br><span class="line"></span><br><span class="line">public class Solution {</span><br><span class="line"> public ArrayList<String> Permutation(String str) {</span><br><span class="line"> ArrayList<String> res = new ArrayList<>();</span><br><span class="line"> if (str == null) return res;</span><br><span class="line"> char[] chars = str.toCharArray();</span><br><span class="line"> PermutationHelper(res, chars, 0);</span><br><span class="line"> Collections.sort(res);</span><br><span class="line"> return res;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private void PermutationHelper(ArrayList<String> list, char[] chars, int toFix) {</span><br><span class="line"> if (toFix == chars.length - 1) {//已经到达最后一个数</span><br><span class="line"> String s = String.valueOf(chars);</span><br><span class="line"> if (!list.contains(s)) list.add(s);</span><br><span class="line"> }</span><br><span class="line"> for (int i = toFix; i < chars.length; i++) {</span><br><span class="line"> swap(chars, i, toFix); //图中横向平行的交换的过程,固定当前的位置的值</span><br><span class="line"> PermutationHelper(list, chars, toFix + 1);//纵向延伸</span><br><span class="line"> swap(chars, toFix, i);//回溯</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private void swap(char[] chars, int i, int j) {</span><br><span class="line"> char temp = chars[i];</span><br><span class="line"> chars[i] = chars[j];</span><br><span class="line"> chars[j] = temp;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>数据结构与算法</tag>
<tag>递归</tag>
<tag>回溯</tag>
</tags>
</entry>
<entry>
<title>支付宝当面付对接流程</title>
<url>/paramountNinja.github.io/2018/03/25/%E6%94%AF%E4%BB%98%E5%AE%9D%E5%BD%93%E9%9D%A2%E4%BB%98%E5%AF%B9%E6%8E%A5%E6%B5%81%E7%A8%8B/</url>
<content><![CDATA[<p><img src="/img/article/alipay1.PNG" alt=""></p>
]]></content>
<tags>
<tag>项目点</tag>
</tags>
</entry>
<entry>
<title>Redis实现消息队列</title>
<url>/paramountNinja.github.io/2018/03/16/Redis%E5%AE%9E%E7%8E%B0%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/</url>
<content><![CDATA[<p>主要使用redis数据结构的列表,通过lpush和brpop实现 </p>
<p>画了一张图</p>
<p><img src="/img/article/redis1.PNG" alt=""></p>
]]></content>
<tags>
<tag>redis</tag>
<tag>消息队列</tag>
<tag>异步</tag>
<tag>项目点</tag>
</tags>
</entry>
<entry>
<title>深入理解java虚拟机笔记2</title>
<url>/paramountNinja.github.io/2018/03/05/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3java%E8%99%9A%E6%8B%9F%E6%9C%BA%E7%AC%94%E8%AE%B02/</url>
<content><![CDATA[<blockquote>
<p>哪些内存需要回收?什么时候回收?如何回收?</p>
</blockquote>
<p><strong>程序计数器、 虚拟机栈、 本地方法栈</strong>3个区域随线程而生,随线程而灭;<br>栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。<br>这几个区域的内存分配和回收都<strong>具备确定性</strong>,在这几个区域内就<strong>不需要</strong>过多考虑回收的问<br>题,因为方法结束或者线程结束时,内存自然就跟随着回收了。<br><strong>Java堆和方法区</strong>则不一<br>样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也<br>可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配<br>和回收都是<strong>动态</strong>的,垃圾收集器所关注的是这部分内存。</p>
<a id="more"></a>
<h2 id="判断对象是否存活算法"><a href="#判断对象是否存活算法" class="headerlink" title="判断对象是否存活算法"></a>判断对象是否存活算法</h2><ol>
<li><p><strong>引用计数算法</strong><br>给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。 主流的Java虚拟机里面<strong>没有选用引用计数算法</strong>来管理内存,其中最主要的原因是它<strong>很难解决对象之间相互循环引用</strong>的问题。</p>
</li>
<li><p><strong>可达性分析算法</strong><br>通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots<strong>没有任何引用链相连</strong>(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。 </p>
<p>如图:对象object5、object6、object7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。<br><img src="/img/article/gc1.jpg" alt=""> </p>
<p>在Java语言中,可作为<strong>GC Roots的对象</strong>包括下面几种:</p>
<ul>
<li>虚拟机栈(栈帧中的本地变量表)中引用的对象。</li>
<li>方法区中类静态属性引用的对象。</li>
<li>方法区中常量引用的对象。</li>
<li>本地方法栈中JNI(即一般说的Native方法)引用的对象。</li>
</ul>
</li>
</ol>
<h2 id="再谈引用"><a href="#再谈引用" class="headerlink" title="再谈引用"></a>再谈引用</h2><blockquote>
<p>这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。 很多系统的缓存功能都符合这样的应用场景</p>
</blockquote>
<ol>
<li><strong>强引用</strong><br>指在程序代码之中普遍存在的,类似<code>“Object obj=new Object()”</code>这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。</li>
<li><strong>软引用</strong><br>用来描述一些还有用但并非必需的对象。 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行<strong>第二次</strong>回收。 <strong>如果这次回收还没有足够的内存</strong>,才会抛出内存溢出异常。 在JDK 1.2之后,提供了SoftReference类来实现软引用。</li>
<li><strong>弱引用</strong><br>用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到<strong>下一次垃圾收集</strong>发生之前。 当垃圾收集器工作时,<strong>无论当前内存是否足够</strong>,都会回收掉只被弱引用关联的对象。 在JDK 1.2之后,提供了WeakReference类来实现弱引用。</li>
<li><strong>虚引用</strong><br>也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。 为一个对象设置虚引用关联的<strong>唯一目的就是能在这个对象被收集器回收时收到一个系统通知。</strong> 在JDK 1.2之后,提供了PhantomReference类来实现虚引用。</li>
</ol>
<h2 id="两次标记,一次自救机会"><a href="#两次标记,一次自救机会" class="headerlink" title="两次标记,一次自救机会"></a>两次标记,一次自救机会</h2><blockquote>
<p>即使在可达性分析算法中不可达的对象,也并非是“非死不可”的</p>
</blockquote>
<p><strong>至少要经历两次标记过程:</strong> </p>
<ol>
<li>如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。 当对象没有覆盖finalize()方法,或<br>者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。</li>
</ol>
<blockquote>
<p>注:finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。</p>
</blockquote>
<ol start="2">
<li>如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做<strong>F-Queue</strong>的队列之中,并在稍后由一个由虚拟机自动建立的、 低优先级的Finalizer线程去执行它。 这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会<strong>导致F-Queue队列中其他对象永久处于等待</strong>,甚至导致整个内存回收系统崩溃。 <strong>finalize()方法是对象逃脱死亡命运的最后一次机会</strong>,稍后GC将对F-Queue中的对象<br>进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链<br>上的任何一个对象建立关联即可,<strong>譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量</strong>,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。</li>
</ol>
<ul>
<li>对象可以在被GC时自我拯救。</li>
<li>这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次</li>
</ul>
<h2 id="回收方法区"><a href="#回收方法区" class="headerlink" title="回收方法区"></a>回收方法区</h2><blockquote>
<p>方法区又称永久代;主要回收废弃常量和无用的类</p>
</blockquote>
<p>判断一个类是否为“无用的类”的3个条件:</p>
<ol>
<li>该类<strong>所有的实例</strong>都已经被回收,也就是Java堆中不存在该类的任何实例。</li>
<li>加载该类的<strong>ClassLoader</strong>已经被回收。</li>
<li>对应的java.lang.Class对象没在任何地方被引用,无法在任何地方通过<strong>反射</strong>访问该类的方法。</li>
</ol>
<h2 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h2><h3 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h3><p> 分为“标记”和“清除”两个阶段:<br> 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。<br> 两个不足: </p>
<ul>
<li>效率问题,标记和清除两个过程的效率都不高;</li>
<li>空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 </li>
</ul>
<h3 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h3><p> 将可用内存按容量划分为大小相等的两块,<strong>每次只使用其中的一块</strong>。<br> <strong>当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。</strong> 这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。<br> <strong>只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。</strong><br> <strong>改进:</strong> Eden:Survivor = 8:1</p>
<h3 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h3><p> <em>运用于老年代</em><br> 标记过程同1;后续不直接清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。</p>
<h3 id="分代收集算法"><a href="#分代收集算法" class="headerlink" title="分代收集算法"></a>分代收集算法</h3><p>当前商业虚拟机的垃圾收集<strong>都采用</strong>“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块。<br>把Java堆分为<strong>新生代和老年代</strong><br> - <strong>新生代</strong> 每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用<strong>复制算法</strong>,只需要付出少量存活对象的复制成本就可以完成收集。<br> - <strong>老年代</strong> 因为对象存活率高、 没有额外空间对它进行分配担保,就必须使用<strong>“标记—清理”或者“标记—整理”</strong>算法来进行回收</p>
<h2 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h2><blockquote>
<p>如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。</p>
</blockquote>
<p><img src="/img/article/gc2.jpg" alt=""><br>说明:展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。 虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。</p>
<h3 id="Serial"><a href="#Serial" class="headerlink" title="Serial"></a>Serial</h3><p><strong>单线程</strong>:但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,<strong>必须暂停其他所有的工作线程</strong>,直到它收集结束。<br><img src="/img/article/gc3.jpg" alt=""> </p>
<p>实际上到现在为止,它依然是虚拟机运行在Client模式下的默认<strong>新生代收集器</strong>。<br>优点:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。</p>
<h3 id="ParNew"><a href="#ParNew" class="headerlink" title="ParNew"></a>ParNew</h3><p>ParNew收集器其实就是Serial收集器的<strong>多线程版本</strong>,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、 -XX:<br>PretenureSizeThreshold、 -XX:HandlePromotionFailure等)、 收集算法、 Stop The World、 对象分配规则、 回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。<br><img src="/img/article/gc4.jpg" alt=""></p>
<p>许多运行在Server模式下的虚拟机中首选的<strong>新生代收集器</strong>,其中有一个与性能无关但很重要的原因是,<strong>除了Serial收集器外,目前只有它能与CMS收集器配合工作。</strong></p>
<h3 id="Parallel-Scavenge"><a href="#Parallel-Scavenge" class="headerlink" title="Parallel Scavenge"></a>Parallel Scavenge</h3><p>新生代收集器,使用复制算法的收集器,并行的多线程收集器(同ParNew) </p>
<p>但目标是达到一个<strong>可控制的吞吐量</strong>(Throughput)。<br>所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,<br>即<strong>吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)</strong>,<br>虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。</p>
<h3 id="Serial-Old"><a href="#Serial-Old" class="headerlink" title="Serial Old"></a>Serial Old</h3><p><strong>serial收集器的老年代版本,单线程收集器,使用“标记-整理”算法。</strong></p>
<h3 id="Parallel-Old"><a href="#Parallel-Old" class="headerlink" title="Parallel Old"></a>Parallel Old</h3><p><strong>Parallel Scavenge收集器的老年代版本,多线程和“标记-整理”算法</strong><br>在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old<br>收集器。<br><img src="/img/article/gc5.jpg" alt=""></p>
<h3 id="CMS"><a href="#CMS" class="headerlink" title="CMS"></a>CMS</h3><blockquote>
<p><strong>一种以获取最短回收停顿时间为目标的收集器。</strong></p>
</blockquote>
<p>目前很大一部分的Java应用集中在互联网站或者B/S系统的<strong>服务端</strong>上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。 CMS收集器就非常符合这类应用的需求。 </p>
<p>“Mark Sweep” ——- “标记—清除” </p>
<p>运作过程4个步骤</p>
<ul>
<li><strong>初始标记(CMS initial mark)</strong><br>需要“Stop The World”。<br>标记一下GC Roots能直接关联到的对象,速度很快</li>
<li><strong>并发标记(CMS concurrent mark)</strong><br>进行GC RootsTracing的过程</li>
<li><strong>重新标记(CMS remark)</strong><br>需要“Stop The World”。<br>修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。</li>
<li><strong>并发清除(CMS concurrent sweep)</strong></li>
</ul>
<p><img src="/img/article/gc6.jpg" alt=""></p>
<p>缺点: </p>
<ol>
<li><p>CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。 </p>
</li>
<li><p>CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。 </p>
</li>
<li><p>CMS是一款基于“标记—清除”算法实现的收集器,收集结束时会有大量<br>空间碎片产生。 空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余。</p>
</li>
</ol>
<h3 id="G1"><a href="#G1" class="headerlink" title="G1"></a>G1</h3><p>G1是一款面向服务端应用的垃圾收集器,具有如下特点:</p>
<ul>
<li><strong>并行与并发</strong>:使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,通过并发的方式让Java程序继续执行。</li>
<li><strong>分代收集</strong>:虽然G1可以不需要其他收集器配合就能<strong>独立管理整个GC堆</strong>,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、 熬过多次GC的旧对象以获取更好的收集效果。</li>
<li><strong>空间整合</strong>:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实<br>现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这<br>两种算法都意味着<strong>G1运作期间不会产生内存空间碎片</strong>,收集后能提供规整的可用内存。 这种<br>特性<strong>有利于程序长时间运行</strong>,分配大对象时不会因为无法找到连续内存空间而提前触发下一<br>次GC。</li>
<li><strong>可预测的停顿</strong>:这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。</li>
</ul>
<p>使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分<br>为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和<br>老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。</p>
<p><strong>G1收集器的运作大致可划分为以下几个步骤:</strong></p>
<ul>
<li>初始标记(Initial Marking)</li>
<li>并发标记(Concurrent Marking)</li>
<li>最终标记(Final Marking)</li>
<li>筛选回收(Live Data Counting and Evacuation)</li>
</ul>
<p><img src="/img/article/gc7.jpg" alt=""></p>
]]></content>
<tags>
<tag>JVM</tag>
<tag>垃圾收集</tag>
</tags>
</entry>
<entry>
<title>Nginx反向代理和负载均衡</title>
<url>/paramountNinja.github.io/2018/01/21/Nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%92%8C%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/</url>
<content><![CDATA[<blockquote>
<p>最近,一休在学习分布式架构,作为一个新手,有很多不明白的地方,于是就去找大师请教。</p>
</blockquote>
<a id="more"></a>
<p><strong>一休</strong>:我们在发布网站的时候必须要拥有一台独立的服务器吗?<br><strong>大师</strong>:不一定哦!对于大公司是这样的,但对于小公司并不是的。</p>
<p><strong>一休</strong>:不是独立的服务器,难道还和别人公用一个服务器啊?<br><strong>大师</strong>:是这样的,其实你可以和别人公用一台服务器,只是端口不同罢了。 </p>
<p><strong>一休</strong>:那我在域名绑定的时候不是绑定的是ip地址吗?他怎么知道我想要访问哪个端口呀?<br><strong>大师</strong>:你知道nginx吗?它就可以实现反向代理,然后把你的域名绑定到你想要访问的端口上面。</p>
<p><strong>一休</strong>:我听说过nginx,但什么是反向代理呢?<br><strong>大师</strong>:<strong>反向代理就是作为一台代理服务器来接受internet上客户端的请求,然后将请求转发给内部网络上的服务器,等服务器处理好任务后,再将结果转发给客户端。</strong></p>
<p>我们来看下图<br><img src="/img/article/nginx1.png" alt=""></p>
<p>接着我们来模拟一个场景:<strong>在一个服务器(假设服务器ip为192.168.2.3)上建立两个tomcat,一个端口设置在8080,一个端口设置在8081</strong>。</p>
<p>首先设置一下本地的hosts文件,来模拟绑定域名的过程<br><img src="/img/article/nginx2.png" alt=""><br>其含义是我们将两个不同的域名都绑定到一台相同的服务器上,<strong>目标是</strong>当我们访问<code>8080.ninja.com</code>时,访问的是运行在8080端口的tomcat;当我们访问<code>8081.ninja.com</code>时,访问的是运行在8081端口的tomcat;</p>
<p><strong>一休</strong>:那么nginx怎么做到转发给不同端口的tomcat呢?<br><strong>大师</strong>:这就需要我们配置<code>nginx.conf</code>这个文件了!</p>
<p>我们来看下<code>nginx.conf</code>这个文件的核心部分的结构(省略了部分)<br><img src="/img/article/nginx3.png" alt=""></p>
<p><code>http</code>就像是一个<strong>插座</strong>,我们可以在其中插入多个<code>upstream</code>和<code>server</code></p>
<ul>
<li>upstream对应的是具体的服务</li>
<li>server对应的是一台虚拟主机</li>
</ul>
<p>当我们访问<code>8080.ninja.com</code>的时候,就会通过<code>proxy_pass</code>找到相应的<code>upstream</code>,然后访问该端口上的网站。</p>
<p>同理,我们可以在<code>http</code>上插入8081端口的服务,也就是再加上一个<code>upstream</code>和一个<code>server</code>,最终我们可以用下图来表示转发的过程<br><img src="/img/article/nginx4.png" alt=""></p>
<p><strong>一休</strong>:反向代理的转发过程我懂了,能不能给我讲讲负载均衡呢?<br><strong>大师</strong>:在实际开发中,对于同一功能的工程我们通常使用集群的方式来缓解并发访问的压力。 </p>
<p><strong>也就是使用多个服务器来实现相同的功能模块</strong></p>
<p>例如在我们下订单的时候需要访问<strong>订单模块</strong>的服务器,那么在双十一这些高并发的情况下,我们如何均匀的分发给不同的服务器呢?</p>
<p>nginx就可以通过设置<code>nginx.conf</code>来做到这一点</p>
<p>还记得<code>upstream</code>吗?我们可以在一个<code>upstream</code>里面添加不同的服务器的具体服务来实现负载均衡。配置如下:<br><img src="/img/article/nginx5.png" alt=""></p>
<p>当我们访问<code>www.ninja.com</code>的时候,如果不加<code>weight</code>权重,那么就以轮询的方式访问两个服务器,如果加了权重,那可能4次里面有3次访问的是<code>192.168.2.4</code>这个服务器了。这样就可以减轻服务器的压力了!</p>
<p>总结如下图:<br><img src="/img/article/nginx6.png" alt=""></p>
<p><strong>一休</strong>:总算搞懂了nginx的反向代理和负载均衡,谢谢大师~<br><strong>大师</strong>:nginx支持很高的并发量,很多大公司都在用呢!当然nginx的功能远不止这些,要学的还有很多啊!</p>
<p>本篇完~</p>
]]></content>
<tags>
<tag>分布式</tag>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title>Netty源码剖析之启动初步</title>
<url>/paramountNinja.github.io/2018/01/18/Netty%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90%E4%B9%8B%E5%90%AF%E5%8A%A8%E5%88%9D%E6%AD%A5/</url>
<content><![CDATA[<blockquote>
<p>Netty是对nio进行封装的框架,并改善了nio的不足</p>
</blockquote>
<h1 id="Netty架构"><a href="#Netty架构" class="headerlink" title="Netty架构"></a>Netty架构</h1><p><strong>Netty的模块组件</strong></p>
<ul>
<li><strong>bootstrap</strong>: Netty服务端及客户端启动类</li>
<li><strong>buffer</strong>:缓冲相关,对NIO Buffer做了一些优化、封装</li>
<li><strong>channel</strong>:处理客户端与服务端之间的连接通道</li>
<li><strong>container</strong>:连接其他容器的代码,例如Spring</li>
<li><strong>handler</strong>:实现协议编解码等附加功能,</li>
<li><strong>logging</strong>:日志</li>
<li><strong>util</strong>:工具类</li>
</ul>
<a id="more"></a>
<p><strong>Netty的运作流程</strong></p>
<blockquote>
<p>主从多线程reactor模型<br><img src="/img/article/netty20.png" alt=""></p>
</blockquote>
<p>首先让我们来看看Netty的启动和创建过程吧</p>
<p>编写server端代码,来启动Netty<br><img src="/img/article/netty21.png" alt=""></p>
<p>这个时候,<code>boss</code>和<code>worker</code>两个线程池就被放入Factory工厂里构造Netty啦!</p>
<p>于是我们跟踪进源码里,经过调用一系列的构造方法的重载,看到</p>
<p><img src="/img/article/netty3.png" alt=""></p>
<p><code>bossCount</code>被设置成了1<br><code>workerCount</code>被设置成了8(若为四核电脑:4*2)<br>然后分别创建<code>NioWorkerPool</code>和<code>NioServerBossPool</code>实例。</p>
<p><strong>也就是我们结构图中的sub和main两个Reactor了。</strong></p>
<blockquote>
<p>本文先讲解下<code>NioWorkerPool</code>的创建过程</p>
</blockquote>
<h1 id="NioWorkerPool"><a href="#NioWorkerPool" class="headerlink" title="NioWorkerPool"></a>NioWorkerPool</h1><p>我们继续追踪进源码,经过调用构造器的重载</p>
<p><img src="/img/article/netty4.png" alt=""></p>
<p>这里出现了<code>NioWorkerPool</code>的父类,这里我们列出继承关系图</p>
<p><img src="/img/article/netty5.png" alt=""></p>
<p>调用<code>AbstractNioWorkerPool</code>的构造器,其中核心代码</p>
<p><img src="/img/article/netty6.png" alt=""></p>
<p>我们可以看到在这里,创建了一个AbstractNioWorker的数组,其数量为workerCount个,也就是8个。</p>
<p>那么<code>AbstractNioWorker</code>是什么呢?下面是继承关系</p>
<p><img src="/img/article/netty7.png" alt=""></p>
<p><strong>在一个<code>AbstractNioWorkerPool</code>有八个<code>AbstractNioWorker</code>空间,而每一个<code>AbstractNioWorker</code>又是实现了Runnable接口的。</strong> </p>
<p>然后我们返回到<code>NioWorkerPool</code>的构造函数中,继续执行<code>this.init()</code>方法;</p>
<p>我们发现这个<code>init()</code>方法其实也是执行的父类<code>AbstractNioWorkerPool</code>的方法,这里执行的主要目的是给之前开辟的那个数组的每个元素实例化</p>
<p><img src="/img/article/netty8.png" alt=""></p>
<p>这里的<code>newWorker()</code>方法是干什么的呢?经过跟踪,又在<code>NioWorkerPool</code>中找到,毋庸置疑,就是实例化一个<code>NioWorker</code>了。</p>
<p><strong>到这里,我们创建了一个<code>NioWorkerPool</code>,其中有八个<code>NioWorker</code>,每一个<code>NioWorker</code>都给它配上一个线程池。</strong></p>
<p>我们列出目前几个关键类的结构,来帮助整理思路</p>
<p><img src="/img/article/netty9.png" alt=""><br><img src="/img/article/netty10.png" alt=""><br><img src="/img/article/netty11.png" alt=""></p>
<blockquote>
<p>接下来我们需要做的就是了解一个NioWorker实例到底做了什么?它是如何处理事件的呢?</p>
</blockquote>
<h1 id="NioWorker"><a href="#NioWorker" class="headerlink" title="NioWorker"></a>NioWorker</h1><p>我们去跟踪<code>NioWorker</code>的构造方法,他调用了他的父类<code>AbstractNioWorker</code>的构造器,接着又调用父类<code>AbstractNioSelector</code>的构造器</p>
<p><img src="/img/article/netty12.png" alt=""><br>我们主要看框出来的代码,首先把之前传进去的线程池赋给他,<strong>即一个<code>NioWorker</code>对应一个<code>Executor</code></strong>;然后我们来看<code>openSelector()</code>方法,同样也在<code>AbstractNioSelector</code>当前类下<br><img src="/img/article/netty13.png" alt=""></p>
<p>让我们来研究下<code>start()</code>方法和<code>newThreadRenamingRunnable()</code>方法分别是干嘛的</p>
<p><strong>newThreadRenamingRunnable()</strong></p>
<p>为了方便理解,我们列出<code>AbstractNioWorker</code>以及<code>AbstractNioSelector</code>的类结构,<code>NioWorker</code>的结构上面已经列出</p>
<p><img src="/img/article/netty14.png" alt=""><br><img src="/img/article/netty15.png" alt=""></p>
<p>可以从图中看出在<code>AbstractNioWorker</code>中实现了<code>newThreadRenamingRunnable()</code>的方法<br><img src="/img/article/netty16.png" alt=""></p>
<p>继续跟踪,去看下ThreadRenamingRunnable是干什么用的<br><img src="/img/article/netty17.png" alt=""><br>这里就是赋了一个Runnable变量为一个NioWorker实例,我们这里给他取个名字叫做“<strong>nio工人</strong>”</p>
<blockquote>
<p>到这里貌似走不下去了,我们回过头去看看start到底干了什么事情!</p>
</blockquote>
<p><strong>start()</strong></p>
<p>跟踪进入,即<code>DeadLockProofWorker</code>类<br><img src="/img/article/netty18.png" alt=""></p>
<p>我们惊奇的发现,这里的需要执行一个run()方法,正是start的第二个参数也就是一个<code>ThreadRenamingRunnable</code>实例中的run()方法!<br><img src="/img/article/netty19.png" alt=""><br>在这个方法中,调用本类中的Runnable变量的run()方法,这个变量不就是我们前面所述的“<strong>nio工人</strong>”吗?也就是一个NioWorker实例</p>
<p><strong>饶了半天原来start()方法最终需要执行的就是一个NioWorker实例中的一个run()方法!</strong></p>
<p><strong>或者说,AbstractNioSelector的openSelector()方法核心思路就是去执行NioWorker的run()方法!</strong></p>
<blockquote>
<p>通过代码追踪,其实也就是调用父类的父类<code>AbstractNioSelector</code>的<code>run()</code>方法,其中包含多路复用最核心的思路,我们下次来详细讲解!</p>
</blockquote>
]]></content>
<tags>
<tag>netty</tag>
<tag>nio</tag>
</tags>
</entry>
<entry>
<title>什么是CAS机制</title>
<url>/paramountNinja.github.io/2018/01/07/%E4%BB%80%E4%B9%88%E6%98%AFCAS%E6%9C%BA%E5%88%B6/</url>
<content><![CDATA[<blockquote>
<p>整理自微信公众号:程序员小灰;链接:<a href="http://mp.weixin.qq.com/s/f9PYMnpAgS1gAQYPDuCq-w" target="_blank" rel="noopener">什么是CAS机制</a></p>
</blockquote>
<h1 id="原子操作类"><a href="#原子操作类" class="headerlink" title="原子操作类"></a>原子操作类</h1><p>首先我们来看一个例子,这个例子在上一篇“什么是volatile关键字”中也出现过:</p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class VolatileTest2 {</span><br><span class="line"> public volatile static int count = 0;</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> for (int i = 0; i < 2; i++) {</span><br><span class="line"> new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(10);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> //synchronized (VolatileTest2.class) {</span><br><span class="line"> for (int i = 0; i < 100; i++) {</span><br><span class="line"> count++; //本身不是原子性操作</span><br><span class="line"> }</span><br><span class="line"> //}</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(2000);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println("count = " + count);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们知道volatile关键字不能确保线程安全,所以我们可以使用synchronized关键字来确保线程安全,打开上面代码的注释即可。</p>
<blockquote>
<p>但是在某些情况下,synchronize不是最优选择</p>
</blockquote>
<p>为什么这么说呢?关键在于性能问题。</p>
<p>Synchronized关键字会让没有得到锁资源的线程进入<strong>BLOCKED</strong>状态,而后在争夺到锁资源后恢复为<strong>RUNNABLE</strong>状态,这个过程中涉及到操作系统<strong>用户模式</strong>和<strong>内核模式</strong>的转换,代价比较高。</p>
<p>尽管Java1.6为Synchronized做了优化,增加了从<strong>偏向锁</strong>到<strong>轻量级锁</strong>再到<strong>重量级锁</strong>的过度,但是在最终转变为重量级锁之后,性能仍然较低。</p>
<blockquote>
<p>那么有什么方式可以替代同步锁呢?我们可以使用原子操作类</p>
</blockquote>
<p>所谓原子操作类,指的是<code>java.util.concurrent.atomic</code>包下,一系列以<code>Atomic</code>开头的包装类。例如<code>AtomicBoolean</code>,<code>AtomicInteger</code>,<code>AtomicLong</code>。它们分别用于<code>Boolean</code>,<code>Integer</code>,<code>Long</code>类型的原子性操作。</p>
<p>现在我们尝试在代码中引入<code>AtomicInteger</code>类:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class AtomicTest {</span><br><span class="line"> public static AtomicInteger count = new AtomicInteger(0);</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> //开启两个线程</span><br><span class="line"> for (int i = 0; i < 2; i++) {</span><br><span class="line"> new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> //模拟线程交替的现象</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(10);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> //每个线程当中让count值自增100次</span><br><span class="line"> for (int j = 0; j < 100; j++) {</span><br><span class="line"> count.incrementAndGet();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(2000);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println("count=" + count.get());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在某些情况下,代码的性能会比Synchronized更好。</p>
<blockquote>
<p>Atomic 操作类的底层,正是利用了我们要讲的CAS机制!</p>
</blockquote>
<h1 id="什么是CAS"><a href="#什么是CAS" class="headerlink" title="什么是CAS"></a>什么是CAS</h1><p>CAS是英文单词<strong>Compare And Swap</strong>的缩写,翻译过来就是比较并替换。</p>
<p>CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值<strong>A</strong>,要修改的新值<strong>B</strong>。</p>
<p>更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。</p>
<p>这样说或许有些抽象,我们来看一个例子:</p>
<ol>
<li>在内存地址V当中,存储着值为10的变量。<br><img src="https://i.imgur.com/og9oWA2.jpg" alt=""></li>
<li>此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。<br><img src="https://i.imgur.com/kOYNVNf.jpg" alt=""></li>
<li>在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。<br><img src="https://i.imgur.com/X6haewH.jpg" alt=""></li>
<li>线程1开始提交更新,<strong>首先进行A和地址V的实际值比较(Compare)</strong>,发现A不等于V的实际值,提交失败。<br><img src="https://i.imgur.com/yHjBqan.jpg" alt=""></li>
<li>线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为<strong>自旋</strong>。<br><img src="https://i.imgur.com/NjObY53.jpg" alt=""></li>
<li>这一次比较幸运,没有其他线程改变地址V的值。线程1进行<strong>Compare</strong>,发现A和地址V的实际值是相等的。<br><img src="https://i.imgur.com/TeMC1ny.jpg" alt=""></li>
<li>线程1进行<strong>SWAP</strong>,把地址V的值替换为B,也就是12。<br><img src="https://i.imgur.com/MWbwI0o.jpg" alt=""></li>
</ol>
<p>从思想上来说,Synchronized属于<strong>悲观锁</strong>,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于<strong>乐观锁</strong>,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。</p>
<blockquote>
<p>这两种机制没有绝对的好坏,在并发量非常高的情况下,反而用同步锁更合适一些(ninja注:不停的自旋也消耗性能)</p>
</blockquote>
<p>那么在java中哪些地方用到了CAS机制呢?<br>例:Atomic系列类以及Lock系列类的底层实现;甚至在java1.6以上版本,synchronized转变为重量级锁之前,也会采用CAS机制</p>
<blockquote>
<p>那么CAS机制存在什么样的缺点呢?</p>
</blockquote>
<h1 id="CAS的缺点"><a href="#CAS的缺点" class="headerlink" title="CAS的缺点"></a>CAS的缺点</h1><ol>
<li><p><strong>CPU开销较大</strong><br>在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。</p>
</li>
<li><p><strong>不能保证代码块的原子性</strong><br>CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。</p>
</li>
<li><p><strong>ABA问题</strong><br>这是CAS机制最大的问题所在。</p>
</li>
</ol>
<h1 id="ABA问题的解决方法"><a href="#ABA问题的解决方法" class="headerlink" title="ABA问题的解决方法"></a>ABA问题的解决方法</h1><blockquote>
<p>ninja注</p>
</blockquote>
<p>我们来看下面这个例子:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">public class Atomic02Example {</span><br><span class="line"> static AtomicReference<String> atomicInteger = new AtomicReference<String>("A");</span><br><span class="line"></span><br><span class="line"> //ABA问题</span><br><span class="line"> public static void main(String[] args) throws InterruptedException {</span><br><span class="line"> Thread t1 = new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> atomicInteger.compareAndSet("A", "B");</span><br><span class="line"> atomicInteger.compareAndSet("B", "A");</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> Thread t2 = new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(10);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> boolean flag = atomicInteger.compareAndSet("A", "B");</span><br><span class="line"> System.out.println("是否可以修改成功:" + flag);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> t1.join();</span><br><span class="line"> t2.join();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以上代码展示了ABA问题 </p>
<p>线程1 先执行,将A改成了B,然后再将B改成了A</p>
<p>线程2 后执行,执行代码输出为<code>是否可以修改成功:true</code>;</p>
<p>我们将线程1中第一次A记作<strong>A1</strong>,第二次A记作<strong>A2</strong></p>
<p>若没有ABA问题,线程2应该输出的是<strong>false</strong>,因为我们希望线程2在A1的时候修改,而此时内存中的值是A2,不满足CAS机制,故输出<strong>false</strong> 。<br>但事实上,线程2并不知道此时的A是A1还是A2,所以永远可以修改成功。</p>
<blockquote>
<p>那么我们如何区分此时的A是A1还是A2呢?可以使用版本号的方法</p>
</blockquote>
<p>使用带版本号的原子类重写代码:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class Atomic03Example {</span><br><span class="line"> //使用带版本号的原子类</span><br><span class="line"> static AtomicStampedReference<String> reference = new AtomicStampedReference<String>("A", 0);//这里版本号设置为0</span><br><span class="line"></span><br><span class="line"> /***解决ABA问题**/</span><br><span class="line"> public static void main(String[] args) throws InterruptedException {</span><br><span class="line"> Thread t1 = new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() { //乐观锁</span><br><span class="line"> reference.compareAndSet("A", "B", reference.getStamp(), reference.getStamp() + 1);//后两个参数:当前版本号,新版本号</span><br><span class="line"> reference.compareAndSet("B", "A", reference.getStamp(), reference.getStamp() + 1);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> Thread t2 = new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(10);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> //期望的是在0这个版本的时候进行操作</span><br><span class="line"> boolean flag = reference.compareAndSet("A", "B", 0, reference.getStamp() + 1);</span><br><span class="line"> System.out.println("是否可以修改成功:" + flag);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> t1.join();</span><br><span class="line"> t2.join();</span><br><span class="line"></span><br><span class="line"> System.out.println(reference.getReference());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>当版本号为0的时候,线程2可以执行操作<br>这里输出结果为<code>是否可以修改成功:false</code><br>所以此时参数值为A(线程2未能修改成功)</p>
]]></content>
<tags>
<tag>并发</tag>
</tags>
</entry>
<entry>
<title>什么是volatile关键字</title>
<url>/paramountNinja.github.io/2018/01/05/%E4%BB%80%E4%B9%88%E6%98%AFvolatile%E5%85%B3%E9%94%AE%E5%AD%97/</url>
<content><![CDATA[<blockquote>
<p>整理自微信公众号:程序员小灰;链接:<a href="http://mp.weixin.qq.com/s/DZkGRTan2qSzJoDAx7QJag" target="_blank" rel="noopener">什么是volatile关键字</a></p>
</blockquote>
<h1 id="Java内存模型"><a href="#Java内存模型" class="headerlink" title="Java内存模型"></a>Java内存模型</h1><p>  Java内存模型简称JMM(Java Memory Model),是Java虚拟机所定义的一种抽象规范,用来屏蔽不同硬件和操作系统的内存访问差异,让java程序在各种平台下都能达到一致的内存访问效果。<br>  Java内存模型如下图:<br><img src="https://i.imgur.com/VlA8f9b.png" alt=""></p>
<a id="more"></a>
<p>这里需要解释几个概念:</p>
<ol>
<li><p><strong>主内存(Main Memory)</strong><br>主内存可以简单理解为计算机当中的内存,但又不完全等同。主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊”。</p>
</li>
<li><p><strong>工作内存(Working Memory)</strong><br>工作内存可以简单理解为计算机当中的CPU高速缓存,但又不完全等同。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本”。 </p>
</li>
</ol>
<p>线程对共享变量的所有操作都必须在工作内存进行,不能直接读写主内存中的变量。不同线程之间也无法访问彼此的工作内存,变量值的传递只能通过主内存来进行。 </p>
<blockquote>
<p>直接操纵主内存太慢了,JVM为了提高性能使用了工作内存。如同CPU高速缓存和主内存的关系。</p>
</blockquote>
<p>以上说的这些可能有点抽象,大家来看看下面这个例子:</p>
<p>对于一个静态变量<br><code>static int s = 0;</code></p>
<p>线程A执行如下代码:<br><code>s = 3;</code></p>
<p>那么,JMM的工作流程如下图所示:<br><img src="https://i.imgur.com/l0AuYcT.png" alt=""><br><img src="https://i.imgur.com/M9wRzO7.png" alt=""></p>
<p>通过一系列内存读写的操作指令(JVM内存模型共定义了8种内存操作指令),线程A把静态变量 s=0 从主内存读到工作内存,再把 s=3 的更新结果同步到主内存当中。从单线程的角度来看,这个过程没有任何问题。</p>
<p>这时候我们引入线程B,执行如下代码:</p>
<p><code>System.out.println("s=" + s);</code></p>
<blockquote>
<p>思考:如果线程A先执行,线程B后执行,线程B的输出结果会是什么?</p>
</blockquote>
<p>引入线程B以后,当线程A首先执行,极大的可能是出现下面情况:<br><img src="https://i.imgur.com/t88hnkv.png" alt=""><br><img src="https://i.imgur.com/hvyKDTB.png" alt=""><br>此时线程B从主内存得到的s值是3,理所当然输出 s=3,这种情况不难理解。但是,有较小的几率出现另一种情况:<br><img src="https://i.imgur.com/cLtsXut.png" alt=""><br><img src="https://i.imgur.com/TSvXmw1.png" alt=""><br>因为工作内存所更新的变量并不会立即同步到主内存,所以虽然线程A在工作内存当中已经把变量s的值更新成3,但是线程B从主内存得到的变量s的值仍然是0,从而输出 s=0。</p>
<blockquote>
<p>怎样才能解决这个问题,使用Synchronized同步锁吗?同步锁虽然可以保证线程安全,但是对程序性能的影响太大了,那么volatile这种轻量级的解决方法登场啦!</p>
</blockquote>
<h1 id="volatile"><a href="#volatile" class="headerlink" title="volatile"></a>volatile</h1><h2 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h2><p>volatile关键字具有许多特性,其中最重要的特性就是保证了用volatile修饰的变量对所有线程的可见性。</p>
<p>这里的可见性是什么意思呢?当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。</p>
<blockquote>
<p>为什么volatile关键字可以有这样的特性?这得益于java语言的先行发生原则(happens-before)</p>
</blockquote>
<h2 id="happens-before"><a href="#happens-before" class="headerlink" title="happens-before"></a>happens-before</h2><p>在计算机科学中,先行发生原则是两个事件的结果之间的关系,如果一个事件发生在另一个事件之前,结果必须反映,即使这些事件实际上是乱序执行的(通常是优化程序流程)。</p>
<p>这里所谓的事件,实际上就是各种指令操作,比如读操作、写操作、初始化操作、锁操作等等。</p>
<p>先行发生原则作用于很多场景下,包括同步锁、线程启动、线程终止、volatile。我们这里只列举出volatile相关的规则:</p>
<p>对于一个volatile变量的写操作先行发生于后面对这个变量的读操作。</p>
<p>回到上述的代码例子,如果在静态变量s之前加上volatile修饰符:</p>
<p><code>volatile static int s = 0;</code></p>
<p>线程A执行如下代码:<br><code>s = 3;</code></p>
<p>这时候我们引入线程B,执行如下代码:<br><code>System.out.println("s=" + s);</code></p>
<p>当线程A先执行的时候,把s = 3写入主内存的事件必定会先于读取s的事件。所以线程B的输出一定是s = 3。</p>
<blockquote>
<p>volatile关键字可以保证线程安全了吗?事实上并不是,volatile只能保证变量的可见性,并不能保证变量的原子性</p>
</blockquote>
<p>我们来看一个例子</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class VolatileTest2 {</span><br><span class="line"> public volatile static int count = 0;</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> for (int i = 0; i < 10; i++) {</span><br><span class="line"> new Thread(new Runnable() {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(1);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> //synchronized (VolatileTest2.class) {</span><br><span class="line"> for (int i = 0; i < 100; i++) {</span><br><span class="line"> count++; //本身不是原子性操作</span><br><span class="line"> }</span><br><span class="line"> //}</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(2000);//目的让count最后输出</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println("count = " + count);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码是什么意思呢?很简单,开启10个线程,每个线程当中让静态变量count自增100次。执行之后会发现,最终count的结果值未必是1000,有可能小于1000。</p>
<p>使用volatile修饰的变量,为什么并发自增的时候会出现这样的问题呢?这是因为count++这一行代码本身并不是原子性操作,在字节码层面可以拆分成如下指令:</p>
<p>getstatic //读取静态变量(count)<br>iconst_1 //定义常量1<br>iadd //count增加1<br>putstatic //把count结果同步到主内存</p>
<p>虽然每一次执行 getstatic 的时候,获取到的都是主内存的最新变量值,但是进行iadd的时候,由于并不是原子性操作,其他线程在这过程中很可能让count自增了很多次。这样一来本线程所计算更新的是一个陈旧的count值,自然无法做到线程安全:<br><img src="https://i.imgur.com/a5Rjv7w.png" alt=""><br><img src="https://i.imgur.com/BhTMtBI.png" alt=""><br>因此,什么时候适合用volatile呢?</p>
<p><strong>1. 运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。</strong></p>
<p><strong>2. 变量不需要与其他的状态变量共同参与不变约束。</strong></p>
<p>第一条很好理解,就是上面的代码例子。第二条是什么意思呢?可以看看下面这个场景:<br><code>volatile static int start = 3;</code><br><code>volatile static int end = 6;</code></p>
<p>线程A执行如下代码:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">while (start < end) {</span><br><span class="line"> //do something</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>线程B执行如下代码:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">start += 3;</span><br><span class="line">end += 3;</span><br></pre></td></tr></table></figure>
<p>这种情况下,一旦在线程A的循环中执行了线程B,start有可能先更新成6,造成了一瞬间 start == end,从而跳出while循环的可能性。</p>
<h1 id="volatile对指令重排的影响"><a href="#volatile对指令重排的影响" class="headerlink" title="volatile对指令重排的影响"></a>volatile对指令重排的影响</h1><h2 id="什么是指令重排"><a href="#什么是指令重排" class="headerlink" title="什么是指令重排"></a>什么是指令重排</h2><p>指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。</p>
<p>指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。需要注意的是,这里所说的不改变执行结果,指的是不改变<strong>单线程</strong>下的程序执行结果。</p>
<p>然而,指令重排是一把双刃剑,虽然优化了程序的执行效率,但是在某些情况下,会影响到多线程的执行结果。我们来看看下面的例子:<br><code>boolean contextReady = false;</code></p>
<p>在线程A中执行: </p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">context = loadContext(); </span><br><span class="line">contextReady = true;</span><br></pre></td></tr></table></figure>
<p>在线程B中执行:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">while( ! contextReady ){ </span><br><span class="line"> sleep(200);</span><br><span class="line">}</span><br><span class="line">doAfterContextReady (context);</span><br></pre></td></tr></table></figure>
<p>以上程序看似没有问题。线程B循环等待上下文context的加载,一旦context加载完成,contextReady == true的时候,才执行doAfterContextReady 方法。</p>
<p>但是,如果线程A执行的代码发生了指令重排,初始化和contextReady的赋值交换了顺序:<br><code>boolean contextReady = false;</code></p>
<p>在线程A中执行: </p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">contextReady = true; </span><br><span class="line">context = loadContext();</span><br></pre></td></tr></table></figure>
<p>在线程B中执行:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">while( ! contextReady ){ </span><br><span class="line"> sleep(200);</span><br><span class="line">}</span><br><span class="line">doAfterContextReady (context);</span><br></pre></td></tr></table></figure>
<p>这个时候,很可能context对象还没有加载完成,变量contextReady 已经为true,线程B直接跳出了循环等待,开始执行doAfterContextReady 方法,结果自然会出现错误。</p>
<p>需要注意的是,这里java代码的重排只是为了简单示意,真正的指令重排是在字节码指令的层面。</p>
<blockquote>
<p>如何解决指令重排问题呢?这里有个法宝内存屏障</p>
</blockquote>
<h2 id="什么是内存屏障"><a href="#什么是内存屏障" class="headerlink" title="什么是内存屏障"></a>什么是内存屏障</h2><p>内存屏障(Memory Barrier)是一种CPU指令。</p>
<p>内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。</p>
<p>内存屏障共分为四种类型:</p>
<ul>
<li><p><strong>LoadLoad屏障:</strong><br>抽象场景:Load1; LoadLoad; Load2<br>Load1 和 Load2 代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。</p>
</li>
<li><p><strong>StoreStore屏障:</strong><br>抽象场景:Store1; StoreStore; Store2<br>Store1 和 Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见</p>
</li>
<li><p><strong>LoadStore屏障:</strong><br>抽象场景:Load1; LoadStore; Store2<br>在Store2被写入前,保证Load1要读取的数据被读取完毕。</p>
</li>
<li><p><strong>StoreLoad屏障:</strong><br>抽象场景:Store1; StoreLoad; Load2<br>在Load2读取操作执行前,保证Store1的写入对所有处理器可见<br>StoreLoad屏障的开销是四种屏障中最大的。</p>
</li>
</ul>
<blockquote>
<p>volatile做了什么?</p>
</blockquote>
<p>在一个变量被volatile修饰后,JVM会为我们做两件事:</p>
<ol>
<li>在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。</li>
<li>在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。</li>
</ol>
<p>或许这样说有些抽象,我们看一看刚才线程A代码的例子:</p>
<p><code>boolean contextReady = false;</code></p>
<p>在线程A中执行:<br><code>context = loadContext();</code><br><code>contextReady = true;</code><br>我们给contextReady 增加volatile修饰符,会带来什么效果呢?<br><img src="https://i.imgur.com/C8RfTbU.png" alt=""></p>
<p>由于加入了StoreStore屏障,屏障上方的普通写入语句 <code>context = loadContext()</code> 和屏障下方的volatile写入语句 <code>contextReady = true</code> 无法交换顺序,从而成功阻止了指令重排序。<br><img src="https://i.imgur.com/SAkjk0d.png" alt=""></p>
<blockquote>
<p>那么内存屏障和java语言的happens-before是什么关系呢?</p>
</blockquote>
<p>happens-before是JSR-133规范之一,内存屏障是CPU指令。<br>可以简单认为前者是最终目的,后者是实现手段。</p>
<h2 id="总结特性"><a href="#总结特性" class="headerlink" title="总结特性"></a>总结特性</h2><p><strong>volatile特性之一:</strong></p>
<p>保证变量在线程之间的可见性。可见性的保证是基于CPU的内存屏障指令,被JSR-133抽象为happens-before原则。</p>
<p><strong>volatile特性之二:</strong></p>
<p>阻止编译时和运行时的指令重排。编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。</p>
]]></content>
<tags>
<tag>并发</tag>
<tag>JMM</tag>
</tags>
</entry>
<entry>
<title>每日刷题计划</title>
<url>/paramountNinja.github.io/2018/01/01/%E6%AF%8F%E6%97%A5%E5%88%B7%E9%A2%98%E8%AE%A1%E5%88%92/</url>
<content><![CDATA[<p>github地址:<a href="https://github.com/paramountNinja/nowcoder" target="_blank" rel="noopener">https://github.com/paramountNinja/nowcoder</a></p>
<blockquote>
<p>以下记录coding目录,不定期更新(以github目录为准)</p>
</blockquote>
<h1 id="nowcoder"><a href="#nowcoder" class="headerlink" title="nowcoder"></a>nowcoder</h1><p>每日刷题计划,记录做过的题目,内容包含剑指offer、程序员面试金典(CTCI)、数据结构</p>
<a id="more"></a>
<blockquote>
<p>下面标题括号内的为对应包名</p>
</blockquote>
<h2 id="剑指offer-offer"><a href="#剑指offer-offer" class="headerlink" title="剑指offer(offer)"></a>剑指offer(offer)</h2><p>java实现</p>
<ul>
<li>03二维数组中的查找</li>
<li>04替换空格</li>
<li>05从尾到头打印链表</li>
<li>06重建二叉树</li>
<li>07用两个栈实现队列</li>
<li>08旋转数组的最小数字</li>
<li>09斐波那契数列</li>
<li>09跳台阶</li>
<li>09变态跳台阶</li>
<li>09矩阵覆盖</li>
<li>10二进制中1的个数</li>
<li>11数值的整数次方</li>
<li>14调整数组顺序使奇数位于偶数前面</li>
<li>15链表中倒数第k个结点</li>
<li>16反转链表</li>
<li>17合并两个排序的链表</li>
<li>18树的子结构</li>
<li>19二叉树的镜像</li>
<li>20顺时针打印矩阵</li>
<li>21包含min函数的栈</li>
<li>22栈的压入弹出序列</li>
<li>23从上往下打印二叉树</li>
<li>24二叉搜索树的后序遍历序列</li>
<li>25二叉树中和为某一值的路径</li>
<li>26复杂链表的复制</li>
<li>27二叉搜索树与双向链表</li>
<li>28字符串的排列</li>
<li>29数组中出现次数超过一半的数字</li>
<li>30最小的k个数</li>
<li>31连续子数组的最大和</li>
<li>32从1到n整数中1出现的次数</li>
<li>33把数组排成最小的数</li>
<li>34丑数</li>
<li>35第一个只出现一次的字符</li>
<li>36数组中的逆序对</li>
<li>37两个链表的第一个公共结点</li>
<li>38数字在排序数组中出现的次数</li>
<li>39二叉树的深度</li>
<li>39判断是否为平衡二叉树</li>
<li>40数组中只出现一次的数字</li>
<li>41和为S的连续正数序列</li>
<li>41和为S的两个数字</li>
<li>42左旋转字符串</li>
<li>42翻转单词顺序列</li>
<li>44扑克牌顺子</li>
<li>45圆圈中最后剩下的数</li>
<li>46求1+2+3+…+n</li>
<li>47不用加减乘除做加法</li>
<li>49把字符串转换成整数</li>
<li>50数组中重复的数字</li>
<li>51构建乘积数组</li>
<li>52正则表达式匹配</li>
<li>53表示数值的字符串</li>
<li>54字符流中第一个不重复的字符</li>
<li>55链表中环的入口结点</li>
<li>56删除链表中重复的结点</li>
<li>57二叉树的下一个结点</li>
<li>58对称的二叉树</li>
<li>59按之字形顺序打印二叉树</li>
<li>60把二叉树打印成多行</li>
<li>61序列化二叉树</li>
<li>62二叉搜索树的第k个结点</li>
<li>63数据流中的中位数</li>
<li>64滑动窗口的最大</li>
<li>65矩阵中的路径</li>
<li>66机器人的运行范围</li>
</ul>
<h2 id="程序员金典-ctci"><a href="#程序员金典-ctci" class="headerlink" title="程序员金典(ctci)"></a>程序员金典(ctci)</h2><ul>
<li>1.1确定字符互异</li>
<li>1.2原串反转</li>
<li>1.3确定两串乱序同构</li>
<li>1.4替换空格(同offer04)</li>
<li>1.5基本字符串压缩</li>
<li>1.6像素反转</li>
<li>1.7清除行列</li>
<li>1.8翻转字串</li>
<li>2.2链表中倒数第k个结点(同offer15)</li>
<li>2.3访问单个节点的删除</li>
<li>2.4链表分割</li>
<li>2.5链式A+B</li>
<li>2.7回文链表</li>
<li>3.3集合栈</li>
<li>3.5用两个栈实现队列(同offer07)</li>
<li>3.6双栈排列</li>
<li>3.7猫狗收容所</li>
<li>4.1二叉树平衡检查(同offer39)</li>
<li>4.2有向路径检查</li>
<li>4.3高度最小的BST</li>
<li>4.4输出单层结点</li>
<li>4.5检查是否为BST二叉查找树(类offer62)</li>
<li>4.6寻找下一个结点(类offer57)</li>
<li>4.7二叉树最近公共祖先</li>
<li>4.9二叉树中和为某一值的路径(同offer25)</li>
<li>5.1二进制插入</li>
<li>7.7第k个数</li>
<li>9.1上楼梯(类offer09)</li>
<li>9.2_1机器人走方格(类offer66)</li>
<li>9.2_2机器人走方格有障碍</li>
<li>9.3.1魔术索引(升序)</li>
<li>9.3.2魔术索引(不下降)</li>
<li>9.4集合的子集</li>
<li>9.5字符串排列(类offer28)</li>
<li>9.6合法括号序列判断</li>
<li>9.7洪水</li>
<li>9.8硬币问题</li>
<li>11.9数组中的逆序对(同offer36)</li>
</ul>
<h2 id="公司模拟真题-simulation"><a href="#公司模拟真题-simulation" class="headerlink" title="公司模拟真题(simulation)"></a>公司模拟真题(simulation)</h2><ul>
<li>最多硬币问题</li>
<li>最小不能表示的数(美团)</li>
<li>整数划分最大乘积(招行信用卡)</li>
<li>判断字符串由子串复制组成(招行信用卡)</li>
<li>括号配对所有情况(招行信用卡)</li>
<li>符合条件的子串(58)</li>
<li>翻转数列(腾讯)</li>
<li>歌单组成方式(腾讯)</li>
<li>因式分解(京东)</li>
</ul>
<h2 id="数据结构-base"><a href="#数据结构-base" class="headerlink" title="数据结构(base)"></a>数据结构(base)</h2><ul>
<li>排序算法(src/base/sort)</li>
<li>二叉堆(最大堆)</li>
<li>深度优先搜索DFS</li>
<li>广度优先搜索BFS</li>
</ul>
]]></content>
<tags>
<tag>数据结构与算法</tag>
</tags>
</entry>
<entry>
<title>Java异常处理</title>
<url>/paramountNinja.github.io/2017/11/05/Java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</url>
<content><![CDATA[<h2 id="异常"><a href="#异常" class="headerlink" title="异常"></a>异常</h2><p>程序的异常:Throwable</p>
<ul>
<li>严重问题:Error 比如说内存溢出。</li>
<li>问题:Exception<ul>
<li>编译器问题:不是RuntimeException的异常</li>
<li>运行时问题:RuntimeException <a id="more"></a>
<img src="https://i.imgur.com/2iGT6it.png" alt="异常分类图解"></li>
</ul>
</li>
</ul>
<p><strong>编译时异常和运行时异常的区别</strong></p>
<ul>
<li>编译时异常:Java程序必须显示处理,否则程序就会发生错误,无法通过编译 </li>
<li>运行时异常:无需显示处理,也可以和编译时异常一样处理</li>
</ul>
<p>如果程序出现问题,我们没有做任何处理,最终jvm会做出默认的处理。<br>把异常名字,原因及出现的问题等信息输出在控制台。<br>同时会结束程序。</p>
<p><strong>自己如何处理异常?</strong></p>
<ol>
<li>try…catch…finally</li>
<li>throws 抛出</li>
</ol>
<h2 id="try…catch…"><a href="#try…catch…" class="headerlink" title="try…catch…"></a>try…catch…</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">try{</span><br><span class="line"> 可能出现问题的代码;</span><br><span class="line">}catch(异常名 变量){</span><br><span class="line"> 针对问题的处理;</span><br><span class="line">}catch(异常名 变量){</span><br><span class="line"> 针对问题的处理;</span><br><span class="line">}finally{</span><br><span class="line"> 资源释放;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意:如果try后面跟了多个catch,一旦出现问题,找到和问题匹配的catch,执行catch里面的内容,然后结束try…catch…区域,往后执行。(父类需要放后面,例如Exception需要放最后)</p>
</blockquote>
<hr>
<p><strong>JDK7的处理方案</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">try{</span><br><span class="line"> 可能出现问题的代码;</span><br><span class="line">}catch(异常名 变量 | 异常名 变量){</span><br><span class="line"> 针对问题的处理;</span><br><span class="line">}finally{</span><br><span class="line"> 资源释放;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意:这种写法处理方式是一致的;多个异常间必须是平级关系</p>
</blockquote>
<h3 id="异常类方法"><a href="#异常类方法" class="headerlink" title="异常类方法"></a>异常类方法</h3><p><code>getMessage()</code>:获取异常信息,返回字符串。<br><code>toString()</code>:获取异常类名和异常信息,返回字符串。<br><code>printStackTrace()</code>:获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。<br><code>printStackTrace(PrintStream s)</code>:通常用该方法将异常内容保存在日志文件中,以便查阅。 </p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class EceptionDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> String s = "2017-11-11";</span><br><span class="line"> SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");</span><br><span class="line"> try {</span><br><span class="line"> Date d = sdf.parse(s); // 发生异常,创建了一个ParseException,然后跑出去,和catch进行匹配</span><br><span class="line"> } catch (ParseException e) {// ParseException e = new ParseException();</span><br><span class="line"> System.out.println(e.getMessage());// Unparseable date: "2017-11-11"</span><br><span class="line"> System.out.println(e.toString());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="throws"><a href="#throws" class="headerlink" title="throws"></a>throws</h2><ul>
<li>定义功能方法时,需要把出现的问题暴露出来让<strong>调用者</strong>去处理。那么就通过throws在方法上标识。</li>
<li><strong>一直往上抛则抛给JVM虚拟机</strong></li>
<li>编译期异常抛出,将来调用者必须处理;运行时异常抛出,将来调用者可以不处理。</li>
</ul>
<h2 id="throw"><a href="#throw" class="headerlink" title="throw"></a>throw</h2><ul>
<li>在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。</li>
<li>抛出的是运行时异常不需要处理;抛出的是编译期异常需要上级处理<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class EceptionDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> try {</span><br><span class="line"> method();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private static void method() throws Exception {</span><br><span class="line"> int a = 10;</span><br><span class="line"> int b = 0;</span><br><span class="line"> if (b == 0) {</span><br><span class="line"> throw new Exception();</span><br><span class="line"> } else {</span><br><span class="line"> System.out.println(a / b);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="throws和throw的区别"><a href="#throws和throw的区别" class="headerlink" title="throws和throw的区别"></a>throws和throw的区别</h2><p><strong>throws</strong></p>
<ul>
<li>用在方法声明后面,跟的是异常类名</li>
<li>可以跟多个异常类名,用逗号隔开</li>
<li>表示抛出异常,由该方法的调用者来处理</li>
<li>throws表示出现异常的一种可能性,并不一定会发生这些异常</li>
</ul>
<p><strong>throw</strong></p>
<ul>
<li>用在方法体内,跟的是异常对象名</li>
<li>只能抛出一个异常对象名</li>
<li>表示抛出异常,由方法体内的语句处理</li>
<li>throw则是抛出了异常,执行throw则一定抛出了某种异常</li>
</ul>
<h2 id="finally"><a href="#finally" class="headerlink" title="finally"></a>finally</h2><p><strong>finally的特点</strong></p>
<ul>
<li>被finally控制的语句体一定会执行</li>
<li>特殊情况:在执行到finally之前jvm退出了(比如System.exit(0))</li>
</ul>
<p><strong>finally的作用</strong></p>
<ul>
<li>用于释放资源,在IO流操作和数据库操作中会见到</li>
</ul>
<p><strong>final,finally和finalize的区别</strong></p>
<ul>
<li>final:最终的意思。<ul>
<li>修饰类,类不能继承;</li>
<li>修饰变量,变量是常量;</li>
<li>修饰方法,方法不能被重写。</li>
</ul>
</li>
<li>finally:异常的一部分,用于释放资源。</li>
<li>finalize:是Object类的一个方法,用于垃圾回收。</li>
</ul>
<p><strong>如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后。</strong></p>
<ul>
<li>会,前。准确的说应该在中间。代码演示</li>
</ul>
<blockquote>
<p>输出结果为30</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class EceptionDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> System.out.println(getInt());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private static int getInt() {</span><br><span class="line"> int a = 10;</span><br><span class="line"> try {</span><br><span class="line"> System.out.println(a / 0);</span><br><span class="line"> a = 20;</span><br><span class="line"> } catch (ArithmeticException e) {</span><br><span class="line"> a = 30;</span><br><span class="line"> return a;</span><br><span class="line"> // return a在程序执行到这一步的时候,这里不是return a 而是return 30 这个返回路径就形成了。</span><br><span class="line"> // 但是也要执行finally的内容,执行完执行return 30 的操作</span><br><span class="line"> } finally {</span><br><span class="line"> a = 40;</span><br><span class="line"> //return a;//如果这里打开,最后return注释掉结果就是40了</span><br><span class="line"> }</span><br><span class="line"> return a;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="自定义异常"><a href="#自定义异常" class="headerlink" title="自定义异常"></a>自定义异常</h2><ul>
<li>继承自Exception</li>
<li>继承自RuntimeException</li>
</ul>
<blockquote>
<p>例子:考试成绩必须在0-100之间</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//我的异常类</span><br><span class="line">public class MyException extends Exception {</span><br><span class="line"> public MyException() {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public MyException(String message) {</span><br><span class="line"> super(message);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">//判断分数类,有一个有异常处理的方法////////////////////////</span><br><span class="line">public class Teacher {</span><br><span class="line"> public void check(int score) throws MyException {</span><br><span class="line"> if (score > 100 || score < 0) {</span><br><span class="line"> throw new MyException("分数必须在0-100之间");</span><br><span class="line"> } else {</span><br><span class="line"> System.out.println("分数没有问题");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">//测试类,遇到异常方法需要处理//////////////////////</span><br><span class="line">public class StudentDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Scanner sc = new Scanner(System.in);</span><br><span class="line"> System.out.println("请输入学生成绩:");</span><br><span class="line"> int score = sc.nextInt();</span><br><span class="line"></span><br><span class="line"> Teacher t = new Teacher();</span><br><span class="line"> try {</span><br><span class="line"> t.check(score);</span><br><span class="line"> } catch (MyException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="异常注意事项"><a href="#异常注意事项" class="headerlink" title="异常注意事项"></a>异常注意事项</h2><ul>
<li>子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类。(父亲坏了,儿子不能比父亲更坏)</li>
<li>如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是他的子集,子类不能抛出父类没有的异常</li>
<li>如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws</li>
</ul>
]]></content>
<tags>
<tag>异常处理</tag>
</tags>
</entry>
<entry>
<title>类加载与反射技术</title>
<url>/paramountNinja.github.io/2017/11/04/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E4%B8%8E%E5%8F%8D%E5%B0%84%E6%8A%80%E6%9C%AF/</url>
<content><![CDATA[<h2 id="类的加载"><a href="#类的加载" class="headerlink" title="类的加载"></a>类的加载</h2><blockquote>
<p>当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。</p>
</blockquote>
<a id="more"></a>
<ol>
<li><p><strong>加载</strong></p>
<ul>
<li>将class文件读入内存,创建一个Class对象</li>
<li>任何类被使用时系统都会建立一个Class对象</li>
</ul>
</li>
<li><p><strong>连接</strong></p>
<ul>
<li>验证 是否有正确的内部结构,并和其他类协调一致</li>
<li>准备 负责为类的静态成员分配内存,并设置默认初始化值</li>
<li>解析 将类的二进制数据中的符号引用替换为直接引用</li>
</ul>
</li>
<li><p><strong>初始化</strong></p>
</li>
</ol>
<p><strong>类初始化时机</strong></p>
<ul>
<li>创建类的实例</li>
<li>访问类的静态变量,或者为静态变量赋值</li>
<li>调用类的静态方法</li>
<li>使用反射方式来强制创建某个类或接口对应的java.lang.Class对象<br>初始化某个类的子类</li>
<li>直接使用java.exe命令来运行某个主类</li>
</ul>
<h3 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h3><ul>
<li>负责将.class文件加载到内在中,并为之生成对应的Class对象。</li>
<li>虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。</li>
</ul>
<p><strong>类加载器的组成</strong></p>
<ol>
<li><strong>Bootstrap ClassLoader 根类加载器</strong><br>也被称为引导类加载器,负责Java核心类的加载<br>比如System,String等。在JDK中JRE的lib目录下rt.jar文件中</li>
<li><strong>Extension ClassLoader 扩展类加载器</strong><br>负责JRE的扩展目录中jar包的加载。<br>在JDK中JRE的lib目录下ext目录</li>
<li><strong>Sysetm ClassLoader 系统类加载器</strong><br>负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径</li>
</ol>
<p>获取类加载器:Class对象中的方法<code>getClassLoader()</code>;<br>例如:<code>实例对象.getClass().getClassLoader()</code></p>
<h2 id="反射"><a href="#反射" class="headerlink" title="反射"></a>反射</h2><ul>
<li>JAVA反射机制是在<strong>运行状态</strong>中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。</li>
<li>要想解剖一个类,必须先要获取到该类的<strong>字节码文件对象</strong>。而解剖使用的就是<strong>Class类中的方法</strong>,所以先要获取到每一个字节码文件对应的Class类型的对象。</li>
</ul>
<p><strong>获取class文件对象的方式</strong></p>
<ol>
<li>Object类的<code>getClass()</code>方法</li>
<li>数据类型的静态属性<code>class</code></li>
<li>Class类中的静态方法<code>forName(String className)</code></li>
</ol>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Person p = new Person();</span><br><span class="line">// 方式1</span><br><span class="line">Class c = p.getClass();</span><br><span class="line">// 方式2</span><br><span class="line">Class c2 = Person.class;</span><br><span class="line">// 方式3</span><br><span class="line">Class c3 = Class.forName("cn.ninja.Person");// 全路径名</span><br></pre></td></tr></table></figure>
<p><strong>通过反射获取构造方法并使用</strong> </p>
<blockquote>
<p>构造一个资源类Person </p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">package reflect;</span><br><span class="line">public class Person {</span><br><span class="line"> private String name;</span><br><span class="line"> int age;</span><br><span class="line"> public String address;</span><br><span class="line"> //公有的无参构造函数</span><br><span class="line"> public Person() {</span><br><span class="line"> }</span><br><span class="line"> //私有的有参构造函数</span><br><span class="line"> private Person(String name) {</span><br><span class="line"> this.name = name;</span><br><span class="line"> }</span><br><span class="line"> //默认限定名的有参构造函数</span><br><span class="line"> Person(String name, int age) {</span><br><span class="line"> this.name = name;</span><br><span class="line"> this.age = age;</span><br><span class="line"> }</span><br><span class="line"> //公有的有参构造函数</span><br><span class="line"> public Person(String name, int age, String address) {</span><br><span class="line"> this.name = name;</span><br><span class="line"> this.age = age;</span><br><span class="line"> this.address = address;</span><br><span class="line"> }</span><br><span class="line"> //以下是一些方法</span><br><span class="line"> public void show() {</span><br><span class="line"> System.out.println("show");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void method(String s) {</span><br><span class="line"> System.out.println("method" + s);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String getString(String s, int i) {</span><br><span class="line"> return s + "---" + i;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private void function() {</span><br><span class="line"> System.out.println("function");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public String toString() {</span><br><span class="line"> return "Person [name=" + name + ", age=" + age + ", address=" + address</span><br><span class="line"> + "]";</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>在一个测试类中,有异常的抛出异常</p>
</blockquote>
<ul>
<li>获取字节码文件对象<br><code>Class<?> c = Class.forName("reflect.Person");//括号里为该类的全限定名</code> </li>
<li>获取构造方法<br><code>Constructor<?>[] cons = c.getConstructors();//返回所有公共构造方法</code><br><code>Constructor<?>[] cons = c.getDeclaredConstructors();// 返回所有构造函数</code> </li>
<li>获取单个构造方法<br><code>Constructor<?> con = c.getConstructor();// 无参的构造函数</code><br><code>Constructor<?> con1 = c.getConstructor(String.class, int.class,String.class);// 有参的构造函数</code><br><code>Constructor<?> con2 = c.getDeclaredConstructor(String.class);// 获取指定的构造器,可以用来获取私有构造器</code> </li>
<li>使用该构造函数类创建实例<br><code>Object object = con.newInstance();</code><br><code>Object object1 = con1.newInstance("james", 33, "cleveland");</code><br><code>con2.setAccessible(true);// 遇到私有,可使用暴力访问;指示反射的对象在使用时取消java语言访问检查</code><br><code>Object object2 = con2.newInstance("wade");</code></li>
</ul>
<p><strong>通过反射获取成员变量</strong></p>
<blockquote>
<p>资源类是上面的Person类,在一个测试类中,有异常的抛出异常</p>
</blockquote>
<ul>
<li>获取字节码文件<br><code>Class<?> c = Class.forName("reflect.Person");</code> </li>
<li>获取所有的成员变量<br><code>Field[] fields = c.getFields();//公共的</code><br><code>Field[] fields = c.getDeclaredFields();</code> </li>
<li>通过无参构造方法创建对象<br><code>Constructor<?> con = c.getConstructor();</code><br><code>Object obj = con.newInstance();</code> </li>
<li>获取单个成员变量<br><code>Field addressField = c.getField("address");</code> <code>addressField.set(obj, "上海");// 给obj这个实例对象的address字段设置为上海</code><br><code>Field nameField = c.getDeclaredField("name");// 获取私有的</code><br><code>nameField.setAccessible(true);// 暴力访问</code><br><code>nameField.set(obj, "ninja");</code> </li>
</ul>
<p><strong>通过反射获取成员方法</strong></p>
<blockquote>
<p>资源类是上面的Person类,在一个测试类中,有异常的抛出异常</p>
</blockquote>
<p><code>Class<?> c = Class.forName("reflect.Person");</code> </p>
<ul>
<li>获取所有方法<br><code>Method[] methods = c.getMethods();//获取自己的包括父亲的公共方法</code><br><code>Method[] methods = c.getDeclaredMethods();// 获取自己的所有类</code> </li>
<li>构造实例对象<br><code>Constructor<?> con = c.getConstructor();</code><br><code>Object obj = con.newInstance();</code> </li>
<li>获取单个方法并使用<br><code>Method m1 = c.getMethod("show");// 第一个参数:方法名;第二个参数往后:方法参数的class类型</code><br><code>m1.invoke(obj);// 第一个参数:第二个参数往后:调用该方法的实际参数</code><br><code>Method m2 = c.getMethod("method", String.class);</code><br><code>m2.invoke(obj, "hello");</code><br><code>Method m3 = c.getMethod("getString", String.class, int.class);</code><br><code>Object objString = m3.invoke(obj, "hello", 100);</code><br><code>Method m4 = c.getDeclaredMethod("function");</code> <code>m4.setAccessible(true);</code><br><code>m4.invoke(obj);</code></li>
</ul>
<h2 id="反射案例"><a href="#反射案例" class="headerlink" title="反射案例"></a>反射案例</h2><blockquote>
<p>体现反射的优越性,很多框架运用的就是反射的原理</p>
</blockquote>
<p><strong>通过反射运行配置文件内容</strong></p>
<blockquote>
<p>优化代码,不需要频繁修改java代码,只需要修改配置文件信息即可</p>
</blockquote>
<p>创建如下结构的文件<br>|-Teacher.java<br>|-Student.java<br>|-Test.java<br>|-class.txt</p>
<p>Student类:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">package reflect;</span><br><span class="line"></span><br><span class="line">public class Student {</span><br><span class="line"> public void love() {</span><br><span class="line"> System.out.println("爱生活,爱学习");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Teacher类:<br>将Student类中的爱生活,爱学习,改成爱生活,爱工作,即两个类输出内容不同方法名相同 </p>
<p>class.txt配置文件:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">className=reflect.Teacher</span><br><span class="line">methodName=love</span><br></pre></td></tr></table></figure>
<p>Test.java测试类</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">package reflect;</span><br><span class="line"></span><br><span class="line">import java.io.FileReader;</span><br><span class="line">import java.lang.reflect.Method;</span><br><span class="line">import java.util.Properties;</span><br><span class="line"></span><br><span class="line">public class Test {</span><br><span class="line"> public static void main(String[] args) throws Exception {</span><br><span class="line"> Properties p = new Properties();</span><br><span class="line"> FileReader fr = new FileReader("class.txt");</span><br><span class="line"> p.load(fr);</span><br><span class="line"> fr.close();</span><br><span class="line"></span><br><span class="line"> String className = p.getProperty("className");</span><br><span class="line"> String methodName = p.getProperty("methodName");</span><br><span class="line"> Class<?> c = Class.forName(className);</span><br><span class="line"> Object obj = c.newInstance();</span><br><span class="line"> Method method = c.getMethod(methodName);</span><br><span class="line"> method.invoke(obj);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>如果想打印老师的love方法内容,那么就在配置信息中修改内容就可以了!</p>
</blockquote>
<p><strong>给ArrrayList<Integer>的一个对象,加入一个字符串数据</strong></p>
<blockquote>
<p>不能直接调用add方法,因为泛型其实是给编译器看的,但我们可以通过反射越过泛型检查!</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">import java.lang.reflect.Method;</span><br><span class="line">import java.util.ArrayList;</span><br><span class="line"></span><br><span class="line">public class ArrayListDemo {</span><br><span class="line"> public static void main(String[] args) throws Exception {</span><br><span class="line"> ArrayList<Integer> array = new ArrayList<Integer>();</span><br><span class="line"> Class<? extends ArrayList> c = array.getClass();</span><br><span class="line"> Method method = c.getMethod("add", Object.class);</span><br><span class="line"> method.invoke(array, "hello");</span><br><span class="line"> System.out.println(array);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>实现设置某对象属性值的功能</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">import java.lang.reflect.Field;</span><br><span class="line"></span><br><span class="line">public class Tool {</span><br><span class="line"> public static void setProperty(Object obj, String propertyName, Object value)</span><br><span class="line"> throws Exception {</span><br><span class="line"> // 根据对象获取字节码文件对象</span><br><span class="line"> Class<? extends Object> c = obj.getClass();</span><br><span class="line"> // 获取该对象的propertyName的成员变量</span><br><span class="line"> Field field = c.getDeclaredField(propertyName);</span><br><span class="line"> // 取消访问检查</span><br><span class="line"> field.setAccessible(true);</span><br><span class="line"> // 给该对象的成员变量赋值为指定的值</span><br><span class="line"> field.set(obj, value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) throws Exception {</span><br><span class="line"> Worker w = new Worker();</span><br><span class="line"> Tool.setProperty(w, "name", "james");</span><br><span class="line"> System.out.println(w.toString());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class Worker {</span><br><span class="line"> private String name;</span><br><span class="line"> public int age;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public String toString() {</span><br><span class="line"> return "Worker [name=" + name + ", age=" + age + "]";</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="动态代理"><a href="#动态代理" class="headerlink" title="动态代理"></a>动态代理</h2><p><strong>代理</strong>:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。举例:春季回家买票让人代买 </p>
<p><strong>动态代理</strong>:在程序运行过程中产生的这个对象 </p>
<ul>
<li>而程序运行过程中产生对象其实就是反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理</li>
<li>在Java中<code>java.lang.reflect</code>包下提供了一个<code>Proxy</code>类和一个<code>InvocationHandler</code>接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对<strong>接口</strong>做代理。我们有更强大的代理cglib</li>
<li><code>Proxy</code>类中的方法创建动态代理类对象<br><code>public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)</code><br>最终会调用<code>InvocationHandler</code>的方法</li>
<li>InvocationHandler<br><code>Object invoke(Object proxy,Method method,Object[]</code> </li>
</ul>
<blockquote>
<p>任务需求:在登录功能前后,多打印权限校验以及日志记录<br>代码清单: </p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//StudentDao接口///////////////////////////////</span><br><span class="line">public interface StudentDao {</span><br><span class="line"> public abstract void login();</span><br><span class="line">}</span><br><span class="line">//StudentDao接口的实现//////////////////////////</span><br><span class="line">public class StudentDaoImpl implements StudentDao {</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void login() {</span><br><span class="line"> System.out.println("登录功能");</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">//InvocationHandler处理类进行一些其他操作////////</span><br><span class="line">public class MyInvocationHandler implements InvocationHandler {</span><br><span class="line"> private Object target;// 目标对象</span><br><span class="line"></span><br><span class="line"> public MyInvocationHandler(Object target) {</span><br><span class="line"> this.target = target;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public Object invoke(Object proxy, Method method, Object[] args)</span><br><span class="line"> throws Throwable {</span><br><span class="line"> System.out.println("权限校验");</span><br><span class="line"> Object result = method.invoke(target, args);</span><br><span class="line"> System.out.println("日志记录");</span><br><span class="line"> return result;// 返回的是代理对象</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line">//测试类////////////////////////////////////////</span><br><span class="line">import java.lang.reflect.Proxy;</span><br><span class="line"></span><br><span class="line">public class Test {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> StudentDao s = new StudentDaoImpl();</span><br><span class="line"> // 创建一个对象作为s的代理对象</span><br><span class="line"> MyInvocationHandler handler = new MyInvocationHandler(s);</span><br><span class="line"> StudentDao proxy = (StudentDao) Proxy.newProxyInstance(s.getClass()</span><br><span class="line"> .getClassLoader(), s.getClass().getInterfaces(), handler);</span><br><span class="line"> proxy.login();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>若之后新建了一个老师类,同样可以进行代理操作。</p>
</blockquote>
]]></content>
<tags>
<tag>类加载</tag>
<tag>反射技术</tag>
<tag>动态代理</tag>
</tags>
</entry>
<entry>
<title>多线程基础知识点</title>
<url>/paramountNinja.github.io/2017/11/02/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9/</url>
<content><![CDATA[<h2 id="并发、并行、同步"><a href="#并发、并行、同步" class="headerlink" title="并发、并行、同步"></a>并发、并行、同步</h2><ul>
<li>并发:逻辑上同时发生,指在某一时间段内同时运行多个程序。</li>
<li>并行:物理上同时发生,指在某一时间点同时运行多个程序。</li>
<li>同步:特点:多个线程使用的是<strong>同一个锁对象</strong>。同步的出现解决了多线程的安全问题。当线程相当多时,因为每个线程都会判断同步上的锁,这是很耗费资源的,无形中降低程序的运行效率。</li>
</ul>
<a id="more"></a>
<h2 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h2><ul>
<li>通过任务管理器看到进程的存在。</li>
<li>只有运行的程序才会出现进程。</li>
<li>就是<strong>正在运行的程序。是系统进行资源分配和调用的独立单位</strong>。</li>
<li>每一个进程都有它自己的内存空间和系统资源。</li>
</ul>
<p><strong>多进程的意义</strong></p>
<p>单进程的计算机只能做一件事情,现在的计算机可以做多件事情。<br>比如:一边玩游戏(游戏进程),一边听音乐(音乐进程)。<br>现在的计算机都支持多进程,可以在一个时间段内执行多个任务,提高CPU的使用率。</p>
<p><strong><em>问题:一边玩游戏一边听音乐是同时进行的吗?</em></strong><br>不是,单核CPU在某一时间点上只能做一件事情。只是CPU做着程序间的高效切换让我们觉得是同时进行的。</p>
<h2 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h2><blockquote>
<p>线程依赖于进程而存在</p>
</blockquote>
<ul>
<li>在同一个进程内又可以执行多个任务。每一个任务是一个线程。</li>
<li>线程是<strong>程序的执行单元,执行路径</strong>。是<strong>程序使用CPU的最基本的调度单位</strong>。</li>
<li>单线程:程序只有一条执行路径。</li>
<li>多线程:程序有多条执行路径。</li>
</ul>
<p><strong>多线程的意义</strong></p>
<p>多线程的存在,不是提高程序的执行速度,而是为了提高应用程序的使用率。<br>程序的执行其实都是在抢CPU的资源,CPU的执行权。<br>多个进程是在抢这个资源,某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。<br>不敢保证哪个线程能够在哪个时刻抢到,所以线程的执行有<strong>随机性</strong>。<br>比如:扫雷程序(一个计时器,一个鼠标点击);迅雷下载</p>
<p><strong>线程两种调度模型</strong></p>
<ul>
<li>分时调度模型 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。</li>
<li>抢占式调度模型 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么随机选择一个,优先级高的线程获取的CPU时间片相对多一些。</li>
</ul>
<h2 id="多线程的原理与实现"><a href="#多线程的原理与实现" class="headerlink" title="多线程的原理与实现"></a>多线程的原理与实现</h2><h3 id="Java程序运行原理"><a href="#Java程序运行原理" class="headerlink" title="Java程序运行原理"></a>Java程序运行原理</h3><p>由java命令启动JVM,JVM启动相当于启动了一个进程。<br>由该进程创建一个主线程去调用main方法。</p>
<blockquote>
<p>JVM虚拟机启动是<strong>多线程</strong>的。原因是垃圾回收线程也要先启动,否则很容易出现内存溢出。(主线程和垃圾回收线程已经由两个线程了,还有其他的线程)</p>
</blockquote>
<h3 id="如何实现多线程"><a href="#如何实现多线程" class="headerlink" title="如何实现多线程"></a>如何实现多线程</h3><blockquote>
<p><strong>由C/C++去调用系统功能创建进程,然后由Java提供一些类(Thread)去调用线程</strong>,就实现了多线程程序。</p>
</blockquote>
<p>由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。<br>而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。<br>Java不能直接调用系统功能,所以没有办法直接实现多线程程序。<br>但是,java可以调用C/C+ +写好的程序来实现多线程程序。</p>
<p><em>查看API,发现有几种方式实现多线程程序。</em><br><strong>方式一:继承Thread类</strong></p>
<ol>
<li>自定义MyThread继承Thread类</li>
<li>重写<code>run()</code>方法,用来包含那些被线程执行的代码<br><code>run()</code>方法的调用其实就是普通方法的调用,看到的是单线程的效果。</li>
<li>创建对象</li>
<li><code>start()</code>方法,首先启动了线程,然后再由jvm去调用该线程的<code>run()</code>方法。</li>
</ol>
<p><em>获取线程名字的方法</em>:</p>
<blockquote>
<p><code>public final String getName()</code>方法:(thread子类)获取线程的名称</p>
<p><code>public final String setName()</code>方法:设置线程名称,创建对象之后可以调用修改名称</p>
<p>创建线程时,有参构造设置线程名字,注意自定义线程类需要调用父类的有参构造器。</p>
<p>通过返回当前执行的线程对象获取名字<code>Thread.currentThread().getName()</code></p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class MyThread extends Thread {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> for (int i = 0; i < 200; i++) {//用循环模拟耗时</span><br><span class="line"> System.out.println(getName() + "---" + i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">/////////////////////////////////////////</span><br><span class="line"></span><br><span class="line">public class ThreadDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> //创建两个线程对象,分别启动,每个线程只能启动一次,否则IllegalThreadStateException异常</span><br><span class="line"> MyThread m1 = new MyThread();</span><br><span class="line"> MyThread m2 = new MyThread();</span><br><span class="line"> m1.start();</span><br><span class="line"> m2.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>结果是两个线程抢资源,两个线程的400个数字(1~200)交替打印出。</p>
</blockquote>
<p>如下获取和设置优先级,优先级高线程获取的CPU时间片相对多,但由于随机性,不一定先出现。<br>优先级为1~10,数字越大优先级越高。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">m1.getPriority();</span><br><span class="line">m2.getPriority();</span><br><span class="line">m1.setPriority(1);</span><br><span class="line">m2.setPriority(10) ;</span><br></pre></td></tr></table></figure>
<p><strong>方式二:实现Runnable接口</strong>(常用)</p>
<ol>
<li>自定义类MyRunnable实现Runnable接口</li>
<li>重写run方法</li>
<li>创建MyRunnable类的对象</li>
<li>创建Thread类的对象,并把3步骤的对象作为构造参数传递</li>
</ol>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class MyRunnable implements Runnable {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> for (int i = 0; i < 100; i++) {</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":" + i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">////////////////////////////////////////////////</span><br><span class="line">public class MyRunnableDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> MyRunnable my = new MyRunnable();</span><br><span class="line"> Thread t1 = new Thread(my,"james");</span><br><span class="line"> Thread t2 = new Thread(my,"wade");</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>问题:方式2比方式1的优点</p>
<p>答:</p>
<p>A:可以避免由于java单继承带来的局限性。</p>
<p>B:适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。</p>
</blockquote>
<p><strong>方式三:实现Callable接口</strong></p>
<blockquote>
<p>需要用线程池配合,请先阅读后面线程池章节内容</p>
</blockquote>
<p>多线程计算求和案例:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//Callable带泛型的,其实指的是call()方法的返回值类型</span><br><span class="line">public class MyCallable implements Callable<Integer> {</span><br><span class="line"> private int number;</span><br><span class="line"></span><br><span class="line"> public MyCallable(int number) {</span><br><span class="line"> this.number = number;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public Integer call() throws Exception {</span><br><span class="line"> int sum = 0;</span><br><span class="line"> for (int x = 1; x <= number; x++) {</span><br><span class="line"> sum += x;</span><br><span class="line"> }</span><br><span class="line"> return sum;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line">//////////////////////////////////////////</span><br><span class="line">public class TestDemo {</span><br><span class="line"> public static void main(String[] args) throws InterruptedException,</span><br><span class="line"> ExecutionException {</span><br><span class="line"> // 创建线程池对象</span><br><span class="line"> ExecutorService pool = Executors.newFixedThreadPool(2);</span><br><span class="line"> Future<Integer> f1 = pool.submit(new MyCallable(100));</span><br><span class="line"> Future<Integer> f2 = pool.submit(new MyCallable(200));</span><br><span class="line"></span><br><span class="line"> Integer i1 = f1.get();</span><br><span class="line"> Integer i2 = f2.get();</span><br><span class="line"> System.out.println(i1);</span><br><span class="line"> System.out.println(i2);</span><br><span class="line"> // 结束</span><br><span class="line"> pool.shutdown();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>方式四:匿名内部类方法创建并开启线程</strong></p>
<blockquote>
<p>本质是该类或者接口的子类对象</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">public class Demo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> new Thread() {</span><br><span class="line"> // 重写run方法</span><br><span class="line"> public void run() {</span><br><span class="line"> for (int i = 0; i < 100; i++) {</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":"+ i);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }.start();</span><br><span class="line"> // ////////////////////////////////////////////////</span><br><span class="line"> new Thread(new Runnable() {</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> for (int i = 0; i < 100; i++) {</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":"+ i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }) {</span><br><span class="line"> }.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="线程控制"><a href="#线程控制" class="headerlink" title="线程控制"></a>线程控制</h2><blockquote>
<p><strong>休眠线程</strong>(隔一段时间运行一次,sleep,线程类中);</p>
<p><strong>加入线程</strong>(运行完该线程才去运行其他线程,join,测试过程中);</p>
<p><strong>礼让线程</strong>(使得运行次序尽量交替进行,yield,线程类中)</p>
<p><strong>守护线程</strong>(当一个线程结束之后,其他线程也将不在运行,坦克大战,测试过程中)</p>
<p><strong>中断线程</strong>(超过一段时间可以将其停止,interrupt,测试过程中)</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//休眠线程</span><br><span class="line">import java.util.Date;</span><br><span class="line">public class ThreadSleep extends Thread {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> for (int i = 0; i < 100; i++) {</span><br><span class="line"> System.out.println(getName() + "---" + i + " 日期" + new Date());</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(1000);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//加入线程(测试的时候创建了三个线程对象分别为t1,t2,t3,这三个对象不一定是同一个线程类)</span><br><span class="line">//注意顺序,只有当t3在最上面调用start()并且紧跟join()时,才为t3执行完成后,再进行其他两个线程的执行</span><br><span class="line">t3.start();</span><br><span class="line">try {</span><br><span class="line"> t3.join();</span><br><span class="line">} catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">}</span><br><span class="line">t2.start();</span><br><span class="line">t1.start();</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//礼让线程</span><br><span class="line">//使得这个线程类添加礼让机制,在测试中,如果创建的两个线程都有礼让机制,那么可以在一定程度上使得次序变得更加规则。</span><br><span class="line">public class ThreadYield extends Thread {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> for (int i = 0; i < 1000; i++) {</span><br><span class="line"> System.out.println(getName() + "---" + i);</span><br><span class="line"> Thread.yield();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//守护线程</span><br><span class="line">//在测试类中创建了三个线程类对象t1,t2,t3,后两个设置为守护线程,则t1结束之后,后两者也结束运行了</span><br><span class="line">t1.setName("刘备");</span><br><span class="line">t2.setName("关羽");</span><br><span class="line">t3.setName("张飞");</span><br><span class="line">t2.setDaemon(true);</span><br><span class="line">t3.setDaemon(true);</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">//中断线程</span><br><span class="line">public class ThreadStop extends Thread {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> System.out.println("开始执行");</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(10000);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> System.out.println("线程终止了");</span><br><span class="line"> }</span><br><span class="line"> System.out.println("结束执行");</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">////////////////////////////////////////////</span><br><span class="line">public class TestDemo {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> ThreadStop t = new ThreadStop();</span><br><span class="line"> t.start();</span><br><span class="line"> // 超过三秒不醒过来,就终止它</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(3000);</span><br><span class="line"> t.interrupt();</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="线程的生命周期"><a href="#线程的生命周期" class="headerlink" title="线程的生命周期"></a>线程的生命周期</h2><blockquote>
<p><strong>1.新建</strong>:创建线程对象</p>
<p><strong>2.就绪</strong>:有执行资格,没有执行权</p>
<p><strong>3.运行</strong>:有执行资格,有执行权<br>—>(<strong>阻塞</strong>):由于一些操作让线程处于该状态,没有执行资格,没有执行权,另一些操作可以把它激活,激活后处于就绪状态。</p>
<p><strong>4.死亡</strong>:线程对象变成垃圾,等待被回收</p>
</blockquote>
<p><img src="https://i.imgur.com/SVhdgjd.png" alt="线程生命周期初步"></p>
<h2 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h2><p><strong>判断一个程序是否会有线程安全问题的标准</strong></p>
<ol>
<li>是否有多线程环境</li>
<li>是否有共享数据</li>
<li>是否有多条语句操作共享数据</li>
</ol>
<p><strong><em>如何确保线程安全呢?由判断是否有线程安全问题的标准可知,<br>因为12点改变不了,我们只能对3这一点进行修改!</em></strong></p>
<h3 id="同步(锁机制)"><a href="#同步(锁机制)" class="headerlink" title="同步(锁机制)"></a>同步(锁机制)</h3><p><strong>法1:同步代码块</strong></p>
<blockquote>
<p>对象obj作为一把锁的功能</p>