-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
3440 lines (2060 loc) · 211 KB
/
index.html
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
<!DOCTYPE html>
<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">
<meta name="keywords" content="Hexo, NexT" />
<meta name="description" content="关注公众号「苦逼的码农」,获取更多原创文章,后台回复「礼包」送你一份特别的大礼包">
<meta name="keywords" content="帅地">
<meta property="og:type" content="website">
<meta property="og:title" content="帅地">
<meta property="og:url" content="https://iamshuaidi.github.io/index.html">
<meta property="og:site_name" content="帅地">
<meta property="og:description" content="关注公众号「苦逼的码农」,获取更多原创文章,后台回复「礼包」送你一份特别的大礼包">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="帅地">
<meta name="twitter:description" content="关注公众号「苦逼的码农」,获取更多原创文章,后台回复「礼包」送你一份特别的大礼包">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Gemini',
version: '5.1.4',
sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: '博主'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="https://iamshuaidi.github.io/"/>
<title>帅地</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">帅地</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle">我的眼里只有学习</p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about/" rel="section">
<i class="menu-item-icon fa fa-fw fa-user"></i> <br />
关于
</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section">
<i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
标签
</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section">
<i class="menu-item-icon fa fa-fw fa-th"></i> <br />
分类
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
<li class="menu-item menu-item-search">
<a href="javascript:;" class="popup-trigger">
<i class="menu-item-icon fa fa-search fa-fw"></i> <br />
搜索
</a>
</li>
</ul>
<div class="site-search">
<div class="popup search-popup local-search-popup">
<div class="local-search-header clearfix">
<span class="search-icon">
<i class="fa fa-search"></i>
</span>
<span class="popup-btn-close">
<i class="fa fa-times-circle"></i>
</span>
<div class="local-search-input-wrapper">
<input autocomplete="off"
placeholder="搜索..." spellcheck="false"
type="text" id="local-search-input">
</div>
</div>
<div id="local-search-result"></div>
</div>
</div>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://iamshuaidi.github.io/2018/08/22/java/线程安全(上)--彻底搞懂synchronized(从偏向锁到重量级锁)/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="帅地">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="帅地">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/08/22/java/线程安全(上)--彻底搞懂synchronized(从偏向锁到重量级锁)/" itemprop="url">线程安全(中)--彻底搞懂synchronized(从偏向锁到重量级锁)</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-08-22T00:00:00+08:00">
2018-08-22
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/java/" itemprop="url" rel="index">
<span itemprop="name">java</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2018/08/22/java/线程安全(上)--彻底搞懂synchronized(从偏向锁到重量级锁)/#comments" itemprop="discussionUrl">
<span class="post-comments-count fb-comments-count" data-href="https://iamshuaidi.github.io/2018/08/22/java/线程安全(上)--彻底搞懂synchronized(从偏向锁到重量级锁)/" itemprop="commentCount">0</span> comments
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>接触过线程安全的同学想必都使用过synchronized这个关键字,在java同步代码快中,synchronized的使用方式无非有两个:</p>
<ol>
<li>通过对一个对象进行加锁来实现同步,如下面代码。</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">synchronized(lockObject){</span><br><span class="line"> //代码</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>对一个方法进行synchronized声明,进而对一个方法进行加锁来实现同步。如下面代码</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">public synchornized void test(){</span><br><span class="line"> //代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>但这里需要指出的是,无论是对一个对象进行加锁还是对一个方法进行加锁,实际上,都是对<strong>对象进行加锁</strong>。</p>
<p>也就是说,对于方式2,实际上虚拟机会根据synchronized修饰的是实例方法还是类方法,去取对应的实例对象或者Class对象来进行加锁。</p>
<p>对于synchronized这个关键字,可能之前大家有听过,他是一个<strong>重量级锁</strong>,开销很大,建议大家少用点。但大家可能也听说过,但到了jdk1.6之后,该关键字被进行了很多的优化,已经不像以前那样不给力了,建议大家多使用。</p>
<p>那么它是进行了什么样的优化,才使得synchronized又深得人心呢?为何重量级锁开销就大呢?</p>
<p>想必大家也都听说过轻量级锁,重量级锁,自旋锁,自适应自旋锁,偏向锁等等,他们都有哪些区别呢?</p>
<p>刚才和大家说,锁是加在对象上的,那么一个线程是如何知道这个对象被加了锁呢?又是如何知道它加的是什么类型的锁呢?</p>
<p>基于这些问题,下面我讲一步一步讲解synchronized是如何被优化的,是如何从偏向锁到重量级锁的。</p>
<h4 id="锁对象"><a href="#锁对象" class="headerlink" title="锁对象"></a>锁对象</h4><p>刚才我们说,锁实际上是加在对象上的,那么被加了锁的对象我们称之为<strong>锁对象</strong>,在java中,任何一个对象都能成为锁对象。</p>
<p>为了让大家更好着理解虚拟机是如何知道这个对象就是一个锁对象的,我们下面简单介绍一下java中一个对象的结构。</p>
<p>java对象在内存中的存储结构主要有一下三个部分:</p>
<ol>
<li>对象头</li>
<li>实例数据</li>
<li>填充数据</li>
</ol>
<p>这里强调一下,<strong>对象头</strong>里的数据主要是一些运行时的数据。</p>
<p>其简单的结构如下</p>
<table>
<thead>
<tr>
<th>长度</th>
<th>内容</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>32/64bit</td>
<td>Mark Work</td>
<td>hashCode,GC分代年龄,锁信息</td>
</tr>
<tr>
<td>32/64bit</td>
<td>Class Metadata Address</td>
<td>指向对象类型数据的指针</td>
</tr>
<tr>
<td>32/64bit</td>
<td>Array Length</td>
<td>数组的长度(当对象为数组时)</td>
</tr>
</tbody>
</table>
<p>从该表格中我们可以看到,对象中关于锁的信息是存在Markword里的。</p>
<p>我们来看一段代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">LockObject lockObject = new LockObject();//随便创建一个对象</span><br><span class="line"></span><br><span class="line">synchronized(lockObject){</span><br><span class="line"> //代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>当我们创建一个对象LockObject时,该对象的部分Markword关键数据如下。</p>
<table>
<thead>
<tr>
<th>bit fields</th>
<th>是否偏向锁</th>
<th>锁标志位</th>
</tr>
</thead>
<tbody>
<tr>
<td>hash</td>
<td>0</td>
<td>01</td>
</tr>
</tbody>
</table>
<p>从图中可以看出,<strong>偏向锁</strong>的标志位是“01”,状态是“0”,表示该对象还没有被加上偏向锁。(“1”是表示被加上偏向锁)。该对象被创建出来的那一刻,就有了偏向锁的标志位,这也说明了所有对象都是可偏向的,但所有对象的状态都为“0”,也同时说明所有被创建的对象的偏向锁并没有生效。</p>
<h4 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h4><p>不过,当线程执行到临界区(critical section)时,此时会利用CAS(Compare and Swap)操作,将线程ID插入到Markword中,同时修改偏向锁的标志位。</p>
<blockquote>
<p>所谓临界区,就是只允许一个线程进去执行操作的区域,即同步代码块。CAS是一个原子性操作</p>
</blockquote>
<p>此时的Mark word的结构信息如下:</p>
<table>
<thead>
<tr>
<th>bit fields</th>
<th></th>
<th>是否偏向锁</th>
<th>锁标志位</th>
</tr>
</thead>
<tbody>
<tr>
<td>threadId</td>
<td>epoch</td>
<td>1</td>
<td>01</td>
</tr>
</tbody>
</table>
<p>此时偏向锁的状态为“1”,说明对象的偏向锁生效了,同时也可以看到,哪个线程获得了该对象的锁。</p>
<h5 id="那么,什么是偏向锁"><a href="#那么,什么是偏向锁" class="headerlink" title="那么,什么是偏向锁?"></a>那么,什么是偏向锁?</h5><p>偏向锁是jdk1.6引入的一项锁优化,其中的“偏”是偏心的偏。它的意思就是说,这个锁会偏向于第一个获得它的线程,在接下来的执行过程中,假如该锁没有被其他线程所获取,没有其他线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。</p>
<p>也就是说:</p>
<p>在此线程之后的执行过程中,如果再次进入或者退出同一段同步块代码,并不再需要去进行<strong>加锁</strong>或者<strong>解锁</strong>操作,而是会做以下的步骤:</p>
<ol>
<li>Load-and-test,也就是简单判断一下当前线程id是否与Markword当中的线程id是否一致.</li>
<li>如果一致,则说明此线程已经成功获得了锁,继续执行下面的代码.</li>
<li>如果不一致,则要检查一下对象是否还是可偏向,即“是否偏向锁”标志位的值。</li>
<li>如果还未偏向,则利用CAS操作来竞争锁,也即是第一次获取锁时的操作。</li>
</ol>
<p>如果此对象已经偏向了,并且不是偏向自己,则说明存在了<strong>竞争</strong>。此时可能就要根据另外线程的情况,可能是重新偏向,也有可能是做偏向撤销,但大部分情况下就是升级成<strong>轻量级锁</strong>了。</p>
<p>可以看出,偏向锁是针对于一个线程而言的,线程获得锁之后就不会再有解锁等操作了,这样可以省略很多开销。假如有两个线程来竞争该锁话,那么偏向锁就失效了,进而升级成轻量级锁了。</p>
<p>为什么要这样做呢?因为经验表明,其实大部分情况下,都会是同一个线程进入同一块同步代码块的。这也是为什么会有偏向锁出现的原因。</p>
<p>在Jdk1.6中,偏向锁的开关是默认开启的,适用于只有一个线程访问同步块的场景。</p>
<h4 id="锁膨胀"><a href="#锁膨胀" class="headerlink" title="锁膨胀"></a>锁膨胀</h4><p>刚才说了,当出现有两个线程来竞争锁的话,那么偏向锁就失效了,此时锁就会膨胀,升级为轻量级锁。这也是我们经常所说的<strong>锁膨胀</strong></p>
<h4 id="锁撤销"><a href="#锁撤销" class="headerlink" title="锁撤销"></a>锁撤销</h4><p>由于偏向锁失效了,那么接下来就得把该锁撤销,锁撤销的开销花费还是挺大的,其大概的过程如下:</p>
<ol>
<li>在一个安全点停止拥有锁的线程。</li>
<li>遍历线程栈,如果存在锁记录的话,需要修复锁记录和Markword,使其变成无锁状态。</li>
<li>唤醒当前线程,将当前锁升级成轻量级锁。</li>
</ol>
<p>所以,如果某些同步代码块大多数情况下都是有两个及以上的线程竞争的话,那么偏向锁就会是一种累赘,对于这种情况,我们可以一开始就把偏向锁这个默认功能给关闭</p>
<h4 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h4><p>锁撤销升级为轻量级锁之后,那么对象的Markword也会进行相应的的变化。下面先简单描述下锁撤销之后,升级为轻量级锁的过程:</p>
<ol>
<li>线程在自己的栈桢中创建锁记录 LockRecord。</li>
<li>将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。</li>
<li>将锁记录中的Owner指针指向锁对象。</li>
<li>将锁对象的对象头的MarkWord替换为指向锁记录的指针。</li>
</ol>
<p>对应的图描述如下(图来自周志明深入java虚拟机)</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/8/22/165614893a59be34?w=721&h=344&f=png&s=42033" alt="图片1"></p>
<p><img src="https://user-gold-cdn.xitu.io/2018/8/22/165614960abf2f0e?w=549&h=343&f=png&s=35522" alt="图片2"></p>
<p>之后Markwork如下:</p>
<table>
<thead>
<tr>
<th>bit fields</th>
<th>锁标志位</th>
</tr>
</thead>
<tbody>
<tr>
<td>指向LockRecord的指针</td>
<td>00</td>
</tr>
</tbody>
</table>
<p>注:锁标志位”00”表示<strong>轻量级锁</strong></p>
<p>轻量级锁主要有两种</p>
<ol>
<li>自旋锁</li>
<li>自适应自旋锁</li>
</ol>
<h5 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h5><p>所谓自旋,就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给<strong>阻塞</strong>,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。</p>
<p>注意,锁在原地循环的时候,是会消耗cpu的,就相当于在执行一个啥也没有的for循环。</p>
<p>所以,轻量级锁适用于那些同步代码块执行的很快的场景,这样,线程原地等待很短很短的时间就能够获得锁了。</p>
<p>经验表明,大部分同步代码块执行的时间都是很短很短的,也正是基于这个原因,才有了轻量级锁这么个东西。</p>
<h6 id="自旋锁的一些问题"><a href="#自旋锁的一些问题" class="headerlink" title="自旋锁的一些问题"></a>自旋锁的一些问题</h6><ol>
<li>如果同步代码块执行的很慢,需要消耗大量的时间,那么这个时侯,其他线程在原地等待空消耗cpu,这会让人很难受。</li>
<li>本来一个线程把锁释放之后,当前线程是能够获得锁的,但是假如这个时候有好几个线程都在竞争这个锁的话,那么有可能当前线程会获取不到锁,还得原地等待继续空循环消耗cup,甚至有可能一直获取不到锁。</li>
</ol>
<p>基于这个问题,我们必须给线程空循环设置一个次数,当线程超过了这个次数,我们就认为,继续使用自旋锁就不适合了,此时锁会再次膨胀,升级为<strong>重量级锁</strong>。</p>
<p>默认情况下,自旋的次数为10次,用户可以通过-XX:PreBlockSpin来进行更改。</p>
<blockquote>
<p>自旋锁是在JDK1.4.2的时候引入的</p>
</blockquote>
<h5 id="自适应自旋锁"><a href="#自适应自旋锁" class="headerlink" title="自适应自旋锁"></a>自适应自旋锁</h5><p>所谓自适应自旋锁就是线程空循环等待的自旋次数并非是固定的,而是会动态着根据实际情况来改变自旋等待的次数。</p>
<p>其大概原理是这样的:</p>
<p>假如一个线程1刚刚成功获得一个锁,当它把锁释放了之后,线程2获得该锁,并且线程2在运行的过程中,此时线程1又想来获得该锁了,但线程2还没有释放该锁,所以线程1只能自旋等待,但是虚拟机认为,<strong>由于线程1刚刚获得过该锁,那么虚拟机觉得线程1这次自旋也是很有可能能够再次成功获得该锁的,所以会延长线程1自旋的次数</strong>。</p>
<p>另外,如果对于某一个锁,一个线程自旋之后,很少成功获得该锁,那么以后这个线程要获取该锁时,是有可能直接忽略掉自旋过程,直接升级为重量级锁的,以免空循环等待浪费资源。</p>
<blockquote>
<p>轻量级锁也被称为<strong>非阻塞同步</strong>、<strong>乐观锁</strong>,因为这个过程并没有把线程阻塞挂起,而是让线程空循环等待,串行执行。</p>
</blockquote>
<h4 id="重量级锁"><a href="#重量级锁" class="headerlink" title="重量级锁"></a>重量级锁</h4><p>轻量级锁膨胀之后,就升级为重量级锁了。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为<strong>互斥锁</strong>。</p>
<p>当轻量级所经过锁撤销等步骤升级为重量级锁之后,它的Markword部分数据大体如下</p>
<table>
<thead>
<tr>
<th>bit fields</th>
<th>锁标志位</th>
</tr>
</thead>
<tbody>
<tr>
<td>指向Mutex的指针</td>
<td>10</td>
</tr>
</tbody>
</table>
<h5 id="为什么说重量级锁开销大呢"><a href="#为什么说重量级锁开销大呢" class="headerlink" title="为什么说重量级锁开销大呢"></a>为什么说重量级锁开销大呢</h5><p>主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行<strong>阻塞</strong>,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从<strong>用户态</strong>转换到<strong>内核态</strong>,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。</p>
<p>这就是说为什么重量级线程开销很大的。</p>
<blockquote>
<p>互斥锁(重量级锁)也称为<strong>阻塞同步</strong>、<strong>悲观锁</strong></p>
</blockquote>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>通过上面的分析,我们知道了为什么synchronized关键字为何又深得人心,也知道了锁的演变过程。</p>
<p>也就是说,synchronized关键字并非一开始就该对象加上重量级锁,也是从偏向锁,轻量级锁,再到重量级锁的过程。</p>
<p>这个过程也告诉我们,假如我们一开始就知道某个同步代码块的竞争很激烈、很慢的话,那么我们一开始就应该使用重量级锁了,从而省掉一些锁转换的开销。</p>
<p>讲到这里就大概完了,希望能对你有所帮助</p>
<p><strong>完</strong></p>
<p><strong>参考资料</strong></p>
<ol>
<li>深入理解java虚拟机(JVM高级特性与最佳实践)</li>
<li>java并发编程</li>
<li><a href="http://www.oracle.com/technetwork/java/javase/tech/biasedlocking-oopsla2006-preso-150106.pdf" target="_blank" rel="noopener">Eliminating Synchronization Related Atomic Operations with Biased Locking and Bulk Rebiasing</a></li>
</ol>
<blockquote>
<p>关注我的公众号:<strong>苦逼的码农</strong>,获取更多原创文章,后台回复”礼包”送你一份特别的资源大礼包。</p>
</blockquote>
<p><a href="https://iamshuaidi.github.io/">个人博客</a></p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://iamshuaidi.github.io/2018/08/20/java/线程安全(上)--彻底搞懂volatile关键字/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="帅地">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="帅地">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/08/20/java/线程安全(上)--彻底搞懂volatile关键字/" itemprop="url">线程安全(上)--彻底搞懂volatile关键字</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-08-20T00:00:00+08:00">
2018-08-20
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/java/" itemprop="url" rel="index">
<span itemprop="name">java</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2018/08/20/java/线程安全(上)--彻底搞懂volatile关键字/#comments" itemprop="discussionUrl">
<span class="post-comments-count fb-comments-count" data-href="https://iamshuaidi.github.io/2018/08/20/java/线程安全(上)--彻底搞懂volatile关键字/" itemprop="commentCount">0</span> comments
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>对于volatile这个关键字,相信很多朋友都听说过,甚至使用过,这个关键字虽然字面上理解起来比较简单,但是要用好起来却不是一件容易的事。</p>
<p>这篇文章将从多个方面来讲解volatile,让你对它更加理解。</p>
<h2 id="计算机中为什么会出现线程不安全的问题"><a href="#计算机中为什么会出现线程不安全的问题" class="headerlink" title="计算机中为什么会出现线程不安全的问题"></a>计算机中为什么会出现线程不安全的问题</h2><p>volatile既然是与线程安全有关的问题,那我们先来了解一下计算机在处理数据的过程中为什么会出现线程不安全的问题。</p>
<p>大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中会涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。</p>
<p>为了处理这个问题,在CPU里面就有了高速缓存(Cache)的概念。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。</p>
<p>我举个简单的例子,比如cpu在执行下面这段代码的时候,</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">t = t + 1;</span><br></pre></td></tr></table></figure>
<p>会先从高速缓存中查看是否有t的值,如果有,则直接拿来使用,如果没有,则会从主存中读取,读取之后会复制一份存放在高速缓存中方便下次使用。之后cup进行对t加1操作,然后把数据写入高速缓存,最后会把高速缓存中的数据刷新到主存中。</p>
<p>这一过程在单线程运行是没有问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的,本次讲解以多核cup为主)。这时就会出现同一个变量在两个高速缓存中的<strong>值</strong>不一致问题了。</p>
<p>例如:</p>
<p>两个线程分别读取了t的值,假设此时t的值为0,并且把t的值存到了各自的高速缓存中,然后线程1对t进行了加1操作,此时t的值为1,并且把t的值写回到主存中。但是线程2中高速缓存的值还是0,进行加1操作之后,t的值还是为1,然后再把t的值写回主存。</p>
<p>此时,就出现了线程不安全问题了。</p>
<h2 id="Java中的线程安全问题"><a href="#Java中的线程安全问题" class="headerlink" title="Java中的线程安全问题"></a>Java中的线程安全问题</h2><p>上面那种线程安全问题,可能对于不同的操作系统会有不同的处理机制,例如Windows操作系统和Linux的操作系统的处理方法可能会不同。</p>
<p>我们都知道,Java是一种夸平台的语言,因此Java这种语言在处理线程安全问题的时候,会有自己的处理机制,例如volatile关键字,synchronized关键字,并且这种机制适用于各种平台。</p>
<p>Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。</p>
<p>由于java中的每个线程有自己的工作空间,这种工作空间相当于上面所说的高速缓存,因此多个线程在处理一个<strong>共享变量</strong>的时候,就会出现线程安全问题。</p>
<blockquote>
<p>这里简单解释下<strong>共享变量</strong>,上面我们所说的t就是一个共享变量,也就是说,能够被多个线程访问到的变量,我们称之为共享变量。在java中共享变量包括实例变量,静态变量,数组元素。他们都被存放在堆内存中。</p>
</blockquote>
<h2 id="volatile关键字"><a href="#volatile关键字" class="headerlink" title="volatile关键字"></a>volatile关键字</h2><p>上面扯了一大堆,都没提到volatile关键字的作用,下面开始讲解volatile关键字是如何保证线程安全问题的。</p>
<h4 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h4><h5 id="什么是可见性?"><a href="#什么是可见性?" class="headerlink" title="什么是可见性?"></a>什么是可见性?</h5><p>意思就是说,在多线程环境下,某个共享变量如果被其中一个线程给修改了,其他线程能够立即知道这个共享变量已经被修改了,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的<strong>工作空间</strong>中读取</p>
<p>例如我们上面说的,当线程1对t进行了加1操作并把数据写回到主存之后,线程2就会知道它自己工作空间内的t已经被修改了,当它要执行加1操作之后,就会去主存中读取。这样,两边的数据就能一致了。</p>
<p>假如一个变量被声明为volatile,那么这个变量就具有了<strong>可见性</strong>的性质了。这就是volatile关键的作用之一了。</p>
<h5 id="volatile保证变量可见性的原理"><a href="#volatile保证变量可见性的原理" class="headerlink" title="volatile保证变量可见性的原理"></a>volatile保证变量可见性的原理</h5><p>当一个变量被声明为volatile时,在编译成会变指令的时候,会多出下面一行:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0x00bbacde: lock add1 $0x0,(%esp);</span><br></pre></td></tr></table></figure>
<p>这句指令的意思就是在寄存器执行一个加0的空操作。不过这条指令的前面有一个lock(锁)前缀。</p>
<p>当处理器在处理拥有lock前缀的指令时:</p>
<p>在之前的处理中,lock会导致传输数据的总线被锁定,其他处理器都不能访问总线,从而保证处理lock指令的处理器能够独享操作数据所在的内存区域,而不会被其他处理所干扰。</p>
<p>但由于总线被锁住,其他处理器都会被堵住,从而影响了多处理器的执行效率。为了解决这个问题,在后来的处理器中,处理器遇到lock指令时不会再锁住总线,而是会检查数据所在的内存区域,如果该数据是在处理器的内部缓存中,则会锁定此缓存区域,处理完后把缓存写回到主存中,并且会利用<strong>缓存一致性协议</strong>来保证其他处理器中的缓存数据的一致性。</p>
<h5 id="缓存一致性协议"><a href="#缓存一致性协议" class="headerlink" title="缓存一致性协议"></a>缓存一致性协议</h5><p>刚才我在说可见性的时候,说“如果一个共享变量被一个线程修改了之后,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的<strong>工作空间</strong>中读取”,实际上是这样的:</p>
<p>线程中的处理器会一直在总线上嗅探其内部缓存中的内存地址在其他处理器的操作情况,一旦嗅探到某处处理器打算修改其内存地址中的值,而该内存地址刚好也在自己的内部缓存中,那么处理器就会强制让自己对该缓存地址的无效。所以当该处理器要访问该数据的时候,由于发现自己缓存的数据无效了,就会去主存中访问。</p>
<h4 id="有序性"><a href="#有序性" class="headerlink" title="有序性"></a>有序性</h4><p>实际上,当我们把代码写好之后,虚拟机不一定会按照我们写的代码的顺序来执行。例如对于下面的两句代码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">int a = 1;</span><br><span class="line">int b = 2;</span><br></pre></td></tr></table></figure>
<p>对于这两句代码,你会发现无论是先执行a = 1还是执行b = 2,都不会对a,b最终的值造成影响。所以虚拟机在编译的时候,是有可能把他们进行<strong>重排序</strong>的。</p>
<p>为什么要进行重排序呢?</p>
<p>你想啊,假如执行 int a = 1这句代码需要100ms的时间,但执行int b = 2这句代码需要1ms的时间,并且先执行哪句代码并不会对a,b最终的值造成影响。那当然是先执行int b = 2这句代码了。</p>
<p>所以,虚拟机在进行代码编译优化的时候,对于那些改变顺序之后不会对最终变量的值造成影响的代码,是有可能将他们进行重排序的。</p>
<p>更多代码编译优化可以看我写的另一篇文章:<br><a href="https://mp.weixin.qq.com/s/NHBSv6AmpsKTZPmhj_sU6A" target="_blank" rel="noopener">虚拟机在运行期对代码的优化策略</a></p>
<p>那么重排序之后真的不会对代码造成影响吗?</p>
<p>实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题的。具体请看下面的代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">public class NoVisibility{</span><br><span class="line"> private static boolean ready;</span><br><span class="line"> private static int number;</span><br><span class="line"> </span><br><span class="line"> private static class Reader extends Thread{</span><br><span class="line"> public void run(){</span><br><span class="line"> while(!ready){</span><br><span class="line"> Thread.yield();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(number);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> public static void main(String[] args){</span><br><span class="line"> new Reader().start();</span><br><span class="line"> number = 42;</span><br><span class="line"> ready = true;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码最终打印的一定是42吗?如果没有重排序的话,打印的确实会是42,但如果number = 42和ready = true被进行了重排序,颠倒了顺序,那么就有可能打印出0了,而不是42。(因为number的初始值会是0).</p>
<p>因此,重排序是有可能导致线程安全问题的。</p>
<p>如果一个变量被声明volatile的话,那么这个变量不会被进行重排序,也就是说,虚拟机会保证这个变量之前的代码一定会比它先执行,而之后的代码一定会比它慢执行。</p>
<p>例如把上面中的number声明为volatile,那么number = 42一定会比ready = true先执行。</p>
<blockquote>
<p>不过这里需要注意的是,虚拟机只是保证这个变量之前的代码一定比它先执行,但并没有保证这个变量之前的代码不可以重排序。之后的也一样。</p>
</blockquote>
<p>volatile关键字能够保证代码的<strong>有序性</strong>,这个也是volatile关键字的作用。</p>
<p>总结一下,一个被volatile声明的变量主要有以下两种特性保证保证线程安全。</p>
<ol>
<li>可见性。</li>
<li>有序性。</li>
</ol>
<h4 id="volatile真的能完全保证一个变量的线程安全吗?"><a href="#volatile真的能完全保证一个变量的线程安全吗?" class="headerlink" title="volatile真的能完全保证一个变量的线程安全吗?"></a>volatile真的能完全保证一个变量的线程安全吗?</h4><p>我们通过上面的讲解,发现volatile关键字还是挺有用的,不但能够保证变量的可见性,还能保证代码的有序性。</p>
<p>那么,它真的能够保证一个变量在多线程环境下都能被正确的使用吗?</p>
<p>答案是否定的。原因是因为Java里面的运算并非是<strong>原子操作</strong>。</p>
<h5 id="原子操作"><a href="#原子操作" class="headerlink" title="原子操作"></a>原子操作</h5><p><strong>原子操作</strong>:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。</p>
<p>也就是说,处理器要嘛把这组操作全部执行完,中间不允许被其他操作所打断,要嘛这组操作不要执行。</p>
<p>刚才说Java里面的运行并非是原子操作。我举个例子,例如这句代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int a = b + 1;</span><br></pre></td></tr></table></figure>
<p>处理器在处理代码的时候,需要处理以下三个操作:</p>
<ol>
<li>从内存中读取b的值。</li>
<li>进行a = b + 1这个运算</li>
<li>把a的值写回到内存中</li>
</ol>
<p>而这三个操作处理器是不一定就会连续执行的,有可能执行了第一个操作之后,处理器就跑去执行别的操作的。</p>
<h5 id="证明volatile无法保证线程安全的例子"><a href="#证明volatile无法保证线程安全的例子" class="headerlink" title="证明volatile无法保证线程安全的例子"></a>证明volatile无法保证线程安全的例子</h5><p>由于Java中的运算并非是原子操作,所以导致volatile声明的变量无法保证线程安全。</p>
<p>对于这句话,我给大家举个例子。代码如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">public class Test{</span><br><span class="line"> public static volatile int t = 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"> Thread[] threads = new Thread[10];</span><br><span class="line"> for(int i = 0; i < 10; i++){</span><br><span class="line"> //每个线程对t进行1000次加1的操作</span><br><span class="line"> threads[i] new Thread(new Runnable(){</span><br><span class="line"> @Override</span><br><span class="line"> public void run(){</span><br><span class="line"> for(int j = 0; j < 1000; j++){</span><br><span class="line"> t = t + 1;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> threads[i].start();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //等待所有累加线程都结束</span><br><span class="line"> while(Thread.activeCount() > 1){</span><br><span class="line"> Thread.yield();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //打印t的值</span><br><span class="line"> System.out.println(t);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>最终的打印结果会是1000 * 10 = 10000吗?答案是否定的。</p>
<p>问题就出现在t = t + 1这句代码中。我们来分析一下</p>
<p>例如:</p>
<p>线程1读取了t的值,假如t = 0。之后线程2读取了t的值,此时t = 0。</p>
<p>然后线程1执行了加1的操作,此时t = 1。但是这个时候,处理器还没有把t = 1的值写回主存中。这个时候处理器跑去执行线程2,注意,刚才线程2已经读取了t的值,所以这个时候并不会再去读取t的值了,所以此时t的值还是0,然后线程2执行了对t的加1操作,此时t =1 。</p>
<p>这个时候,就出现了线程安全问题了,两个线程都对t执行了加1操作,但t的值却是1。所以说,volatile关键字并不一定能够保证变量的安全性。</p>
<h4 id="什么情况下volatile能够保证线程安全"><a href="#什么情况下volatile能够保证线程安全" class="headerlink" title="什么情况下volatile能够保证线程安全"></a>什么情况下volatile能够保证线程安全</h4><p>刚才虽然说,volatile关键字不一定能够保证线程安全的问题,其实,在大多数情况下volatile还是可以保证变量的线程安全问题的。所以,在满足以下两个条件的情况下,volatile就能保证变量的线程安全问题:</p>
<ol>
<li>运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。</li>
<li>变量不需要与其他状态变量共同参与不变约束。</li>
</ol>
<p>讲到这里,关于volatile关键字的就算讲完了。如果有哪里讲的不对的地方,非常欢迎你的指点。下篇应该会讲synchronize关键字。</p>
<p><strong>完</strong></p>
<p>参考书籍:</p>
<ol>
<li>深入理解Java虚拟机(JVM高级特性与最佳实践)。</li>
<li>Java并非编程实战</li>
</ol>
<blockquote>
<p>关注公众号:<strong>苦逼的码农</strong>,获取更多原创文章,后台回复”礼包”送你一份资源大礼包。</p>
</blockquote>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://iamshuaidi.github.io/2018/08/19/关于我:一名双非的小混混/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="帅地">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="帅地">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/08/19/关于我:一名双非的小混混/" itemprop="url">关于我:一名来自双非学校的小混混</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-08-19T00:00:00+08:00">
2018-08-19
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/me/" itemprop="url" rel="index">
<span itemprop="name">me</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2018/08/19/关于我:一名双非的小混混/#comments" itemprop="discussionUrl">
<span class="post-comments-count fb-comments-count" data-href="https://iamshuaidi.github.io/2018/08/19/关于我:一名双非的小混混/" itemprop="commentCount">0</span> comments
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">