-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
560 lines (366 loc) · 441 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>『人生一世,草木一秋』</title>
<link href="/atom.xml" rel="self"/>
<link href="http://www.guowenbo.top/"/>
<updated>2019-01-04T07:51:48.790Z</updated>
<id>http://www.guowenbo.top/</id>
<author>
<name>Guo</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>分布式锁及实现</title>
<link href="http://www.guowenbo.top/2019/01/04/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%8F%8A%E5%AE%9E%E7%8E%B0/"/>
<id>http://www.guowenbo.top/2019/01/04/分布式锁及实现/</id>
<published>2019-01-04T07:01:57.000Z</published>
<updated>2019-01-04T07:51:48.790Z</updated>
<content type="html"><![CDATA[<blockquote><p>目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。</p></blockquote><p>在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。</p><ul><li>基于数据库实现分布式锁;</li><li>基于缓存(Redis等)实现分布式锁;</li><li>基于Zookeeper实现分布式锁;</li></ul><p>在分析这几种实现方案之前我们先来想一下,我们需要的分布式锁应该是怎么样的?(这里以方法锁为例,资源锁同理)</p><blockquote><p>可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。<br>这把锁要是一把可重入锁(避免死锁)<br>这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)<br>有高可用的获取锁和释放锁功能<br>获取锁和释放锁的性能要好</p></blockquote><a id="more"></a><h1 id="基于数据库实现"><a href="#基于数据库实现" class="headerlink" title="基于数据库实现"></a>基于数据库实现</h1><blockquote><p>基于数据库表实现(唯一性索引)</p></blockquote><p>最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。</p><p>当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。</p><p>(1)创建这样一张数据库表:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE `method_lock` (</span><br><span class="line"> `id` <span class="keyword">int</span>(<span class="number">11</span>) unsigned NOT NULL AUTO_INCREMENT COMMENT <span class="string">'主键'</span>,</span><br><span class="line"> `method_name` varchar(<span class="number">64</span>) NOT NULL COMMENT <span class="string">'锁定的方法名'</span>,</span><br><span class="line"> `desc` varchar(<span class="number">255</span>) NOT NULL COMMENT <span class="string">'备注信息'</span>,</span><br><span class="line"> `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,</span><br><span class="line"> <span class="function">PRIMARY <span class="title">KEY</span> <span class="params">(`id`)</span>,</span></span><br><span class="line"><span class="function"> UNIQUE KEY `uidx_method_name` <span class="params">(`method_name`)</span> USING BTREE</span></span><br><span class="line"><span class="function">) ENGINE</span>=InnoDB AUTO_INCREMENT=<span class="number">3</span> DEFAULT CHARSET=utf8 COMMENT=<span class="string">'锁定中的方法'</span>;</span><br></pre></td></tr></table></figure><p>(2)当我们想要锁住某个方法时,执行以下SQL:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">INSERT INTO <span class="title">method_lock</span> <span class="params">(method_name, desc)</span> <span class="title">VALUES</span> <span class="params">(<span class="string">'methodName'</span>, <span class="string">'测试的methodName'</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。</p><p>(3)成功插入则获取锁,执行完成后删除对应的行数据释放锁:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">delete from method_lock where method_name =<span class="string">'methodName'</span>;</span><br></pre></td></tr></table></figure><p>上面这种简单的实现有以下几个问题:</p><ol><li>这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。</li><li>这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。</li><li>这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。</li><li>这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。</li></ol><p>当然,我们也可以有其他方式解决上面的问题。</p><ul><li>数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。</li><li>没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。</li><li>非阻塞的?搞一个while循环,直到insert成功再返回成功。</li><li>非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。</li></ul><h1 id="基于redis实现"><a href="#基于redis实现" class="headerlink" title="基于redis实现"></a>基于redis实现</h1><p>(1)Redis有很高的性能;<br>(2)Redis命令对此支持较好,实现起来比较方便</p><p>命令介绍:</p><p>(1)SETNX<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回<span class="number">1</span>;若key存在,则什么都不做,返回<span class="number">0</span>。</span><br></pre></td></tr></table></figure></p><p>(2)expire<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。</span><br></pre></td></tr></table></figure></p><p>(3)delete<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">delete key:删除key</span><br></pre></td></tr></table></figure></p><p>代码实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Redis Lock:redis锁(注意:必须在事务外使用,否则解锁-事务提交之间,可能被其他线程lock,导致脏读)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisLock</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Logger logger = LoggerFactory.getLogger(RedisLock.class);</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String[] keys;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> expireSeconds; <span class="comment">// 过期时间</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> retrySeconds; <span class="comment">// 重试时间</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">long</span> timeValue = <span class="number">0</span>; <span class="comment">// lock设置的时间戳:0 失败;>0 时间戳</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String lockPrefix;<span class="comment">// 前缀</span></span><br><span class="line"><span class="keyword">private</span> List<String> lockKeys; <span class="comment">// 已加锁的key列表</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">RedisLock</span><span class="params">(String[] keys, <span class="keyword">int</span> expireSeconds, <span class="keyword">int</span> retrySeconds)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.keys= keys;</span><br><span class="line"><span class="keyword">this</span>.expireSeconds = expireSeconds;</span><br><span class="line"><span class="keyword">this</span>.retrySeconds = retrySeconds;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getExpireSeconds</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> expireSeconds;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setExpireSeconds</span><span class="params">(<span class="keyword">int</span> expireSeconds)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.expireSeconds = expireSeconds;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getRetrySeconds</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> retrySeconds;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setRetrySeconds</span><span class="params">(<span class="keyword">int</span> retrySeconds)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.retrySeconds = retrySeconds;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> String[] getKeys() {</span><br><span class="line"><span class="keyword">return</span> keys;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getKeyString</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> StringUtils.join(<span class="keyword">this</span>.keys, <span class="string">","</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">getTimeValue</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> timeValue;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> List<String> <span class="title">getLockKeys</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> lockKeys;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 排序自旋锁:setnx && expire</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">acquire</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="comment">//双重检查单例Redis</span></span><br><span class="line">Jedis jedis = Redis.getInstance().getJedis();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">Arrays.sort(keys);</span><br><span class="line">lockKeys.clear();</span><br><span class="line"><span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line"><span class="comment">// 避免死锁:value = 当前时间 + 过期时间 + 1毫秒</span></span><br><span class="line"><span class="keyword">this</span>.timeValue = now + expireSeconds * <span class="number">1000</span> + <span class="number">1</span>;</span><br><span class="line"><span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"><span class="keyword">for</span> (String key : keys) {</span><br><span class="line">String lockKey = lockPrefix + key;</span><br><span class="line"><span class="keyword">if</span> (lockKeys.contains(lockKey))</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">Long ret = jedis.setnx(lockKey, String.valueOf(timeValue));</span><br><span class="line"><span class="keyword">if</span> (ret == <span class="number">1</span>) {</span><br><span class="line"><span class="comment">// 使用expire,如果代码运行至此,redis连接崩溃,该key没有设置超时时间 => 死锁</span></span><br><span class="line">jedis.expire(lockKey, expireSeconds);</span><br><span class="line">lockKeys.add(lockKey);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 解决死锁:判断key对应的value是否超时</span></span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line">String currentValueStr = jedis.get(lockKey);</span><br><span class="line"><span class="keyword">if</span> (currentValueStr != <span class="keyword">null</span> && System.currentTimeMillis() > Long.valueOf(currentValueStr)) {</span><br><span class="line"><span class="comment">// 并发竞争:getset取出旧值再判断一次,避免被其他线程set</span></span><br><span class="line">String oldValueStr = jedis.getSet(lockKey, String.valueOf(timeValue));</span><br><span class="line"><span class="keyword">if</span> (oldValueStr == <span class="keyword">null</span> || oldValueStr.equals(currentValueStr)) {</span><br><span class="line">lockKeys.add(lockKey);</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 全部锁住</span></span><br><span class="line"><span class="keyword">if</span> (lockKeys.size() == keys.length) {</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 超时未成功:释放加的锁</span></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (System.currentTimeMillis() - now > retrySeconds * <span class="number">1000</span>) {</span><br><span class="line"><span class="keyword">if</span> (lockKeys.size() > <span class="number">0</span>) {</span><br><span class="line">jedis.del(lockKeys.toArray(<span class="keyword">new</span> String[lockKeys.size()]));</span><br><span class="line">lockKeys.clear();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Thread.sleep(<span class="number">200</span>);</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">logger.warn(<span class="string">"获取redis lock异常"</span>, e);</span><br><span class="line"><span class="keyword">if</span> (lockKeys.size() > <span class="number">0</span>) {</span><br><span class="line">jedis.del(lockKeys.toArray(<span class="keyword">new</span> String[lockKeys.size()]));</span><br><span class="line">lockKeys.clear();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line">Redis.getInstance().close(jedis);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 解锁:删除 key(从redis线程池未获取到jedis => 解锁失败)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">release</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="comment">//双重检查单例Redis</span></span><br><span class="line">Jedis jedis = Redis.getInstance().getJedis();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="keyword">for</span> (String lockKey : lockKeys) {</span><br><span class="line">String valueStr = jedis.get(lockKey);</span><br><span class="line"><span class="comment">// timeValue不等:可能发生当前线程lock后,业务操作比较耗时,导致超时后被其他线程lock</span></span><br><span class="line"><span class="keyword">if</span> (valueStr != <span class="keyword">null</span> && timeValue == Long.parseLong(valueStr))</span><br><span class="line">jedis.del(lockKey);</span><br><span class="line">}</span><br><span class="line">lockKeys.clear();</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">logger.warn(<span class="string">"释放redis lock异常"</span>, e);</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"><span class="keyword">this</span>.timeValue = <span class="number">0</span>;</span><br><span class="line">Redis.getInstance().close(jedis);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 匿名类包装:无返回值</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> runnable 需要锁住的运行代码</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> true 获锁成功;false 获锁失败</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">wrap</span><span class="params">(Runnable runnable)</span> </span>{</span><br><span class="line"><span class="keyword">if</span> (acquire()) {</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">runnable.run();</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">logger.error(e.getMessage(), e);</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line">release();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"><span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 匿名类包装:带返回值</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> callable 需要锁住的运行代码</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <V> <span class="function">V <span class="title">wrap</span><span class="params">(Callable<V> callable)</span> </span>{</span><br><span class="line"><span class="keyword">if</span> (acquire()) {</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="keyword">return</span> callable.call();</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">logger.error(e.getMessage(), e);</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line">release();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"><span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="基于ZK实现"><a href="#基于ZK实现" class="headerlink" title="基于ZK实现"></a>基于ZK实现</h1><p>ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:</p><p>(1)创建一个目录mylock;<br>(2)线程A想获取锁就在mylock目录下创建临时顺序节点;<br>(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;<br>(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;<br>(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。</p><p>这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。</p><p>优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。</p><p>缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>上面的三种实现方式,没有在所有场合都是完美的,所以,应根据不同的应用场景选择最适合的实现方式。</p><p>在分布式环境中,对资源进行上锁有时候是很重要的,比如抢购某一资源,这时候使用分布式锁就可以很好地控制资源。<br>当然,在具体使用中,还需要考虑很多因素,比如超时时间的选取,获取锁时间的选取对并发量都有很大的影响,上述实现的分布式锁也只是一种简单的实现,主要是一种思想,以上包括文中的代码可能并不适用于正式的生产环境,只做入门参考!</p>]]></content>
<summary type="html">
<blockquote>
<p>目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。</p>
</blockquote>
<p>在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。</p>
<ul>
<li>基于数据库实现分布式锁;</li>
<li>基于缓存(Redis等)实现分布式锁;</li>
<li>基于Zookeeper实现分布式锁;</li>
</ul>
<p>在分析这几种实现方案之前我们先来想一下,我们需要的分布式锁应该是怎么样的?(这里以方法锁为例,资源锁同理)</p>
<blockquote>
<p>可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。<br>这把锁要是一把可重入锁(避免死锁)<br>这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)<br>有高可用的获取锁和释放锁功能<br>获取锁和释放锁的性能要好</p>
</blockquote>
</summary>
<category term="redis" scheme="http://www.guowenbo.top/tags/redis/"/>
<category term="分布式锁" scheme="http://www.guowenbo.top/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/"/>
</entry>
<entry>
<title>mysql知识点整理</title>
<link href="http://www.guowenbo.top/2018/12/03/mysql%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%86/"/>
<id>http://www.guowenbo.top/2018/12/03/mysql知识点整理/</id>
<published>2018-12-03T12:07:56.000Z</published>
<updated>2019-01-03T09:43:41.572Z</updated>
<content type="html"><![CDATA[<p>事务,隔离级别,存储引擎,索引等</p><a id="more"></a><h1 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h1><h2 id="什么是事务"><a href="#什么是事务" class="headerlink" title="什么是事务"></a>什么是事务</h2><p>事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。</p><p>事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交。如果其中一个步骤失败,将发生回滚操作,撤消撤消之前到事务开始时的所以操作。</p><h2 id="事务的ACID"><a href="#事务的ACID" class="headerlink" title="事务的ACID"></a>事务的ACID</h2><p>事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持久性( Durability )。这四个特性简称为 ACID 特性。</p><ol><li><p>原子性。事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做</p></li><li><p>一致性。事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。</p></li><li><p>隔离性。一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。</p></li><li><p>持久性。指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。</p></li></ol><h1 id="隔离级别"><a href="#隔离级别" class="headerlink" title="隔离级别"></a>隔离级别</h1><p>SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。</p><h2 id="四种隔离级别"><a href="#四种隔离级别" class="headerlink" title="四种隔离级别"></a>四种隔离级别</h2><ol><li><p><strong><em>Read Uncommitted(读取未提交内容)</em></strong></p><p> 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。</p></li><li><p><strong><em>Read Committed(读取提交内容)</em></strong></p><p> 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。</p></li><li><p><strong><em>Repeatable Read(可重读)</em></strong></p><p> 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。</p></li><li><p><strong><em>Serializable(可串行化)</em></strong></p><p> 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。</p></li></ol><h2 id="脏读,不可重复读,幻读"><a href="#脏读,不可重复读,幻读" class="headerlink" title="脏读,不可重复读,幻读"></a>脏读,不可重复读,幻读</h2><ul><li><p><strong><em>脏读(Drity Read)</em></strong><br> 某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。</p></li><li><p><strong><em>不可重复读(Non-repeatable read)</em></strong><br> 在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。</p></li><li><p><strong><em>幻读(Phantom Read)</em></strong><br> 在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。</p></li></ul><p>这四种隔离级别,分别有可能产生问题如下所示:</p><table><thead><tr><th style="text-align:center">隔离级别</th><th style="text-align:center">脏读</th><th style="text-align:center">不可重复读</th><th style="text-align:center">幻读</th></tr></thead><tbody><tr><td style="text-align:center"><strong>Read Uncommitted</strong></td><td style="text-align:center"><strong>√</strong></td><td style="text-align:center"><strong>√</strong></td><td style="text-align:center"><strong>√</strong></td></tr><tr><td style="text-align:center"><strong>Read Committed(RC)</strong></td><td style="text-align:center"><strong>×</strong></td><td style="text-align:center"><strong>√</strong></td><td style="text-align:center"><strong>√</strong></td></tr><tr><td style="text-align:center"><strong>Repeatable Read(RR)</strong></td><td style="text-align:center"><strong>×</strong></td><td style="text-align:center"><strong>×</strong></td><td style="text-align:center"><strong>√</strong></td></tr><tr><td style="text-align:center"><strong>Serializable</strong></td><td style="text-align:center"><strong>×</strong></td><td style="text-align:center"><strong>×</strong></td><td style="text-align:center"><strong>×</strong></td></tr></tbody></table><h2 id="MVCC(多版本并发控制)"><a href="#MVCC(多版本并发控制)" class="headerlink" title="MVCC(多版本并发控制)"></a>MVCC(多版本并发控制)</h2><p>mysql的innodb采用的是行锁,而且采用了多版本并发控制来提高读操作的性能。</p><p><strong>其实就是在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号</strong>,</p><p>而每一个事务在启动的时候,都有一个唯一的递增的版本号。 </p><p>举个栗子:</p><ul><li><strong>Insert</strong><br>比如插入一条记录, 事务id是1 ,那么记录如下,记录的创建版本号就是事务版本号。<br>insert into table values (1, ‘test’);</li></ul><table><thead><tr><th style="text-align:center">id(主键)</th><th style="text-align:center">name(name)</th><th style="text-align:center">create version(创建版本号)</th><th style="text-align:center">delete version(删除版本号)</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td style="text-align:center">test</td><td style="text-align:center">1</td><td style="text-align:center"></td></tr></tbody></table><ul><li><strong>Update</strong><br>采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。<br>针对上面那行记录,事务Id为2 要把name字段更新。<br>update table set name= ‘new_value’ where id=1;</li></ul><table><thead><tr><th style="text-align:center">id(主键)</th><th style="text-align:center">name(name)</th><th style="text-align:center">create version(创建版本号)</th><th style="text-align:center">delete version(删除版本号)</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td style="text-align:center">test</td><td style="text-align:center">1</td><td style="text-align:center">2</td></tr><tr><td style="text-align:center">1</td><td style="text-align:center">new_value</td><td style="text-align:center">2</td><td style="text-align:center"></td></tr></tbody></table><ul><li><strong>Delete</strong><br>就把事务版本号作为删除版本号。<br>delete from table where id=1; </li></ul><table><thead><tr><th style="text-align:center">id(主键)</th><th style="text-align:center">name(name)</th><th style="text-align:center">create version(创建版本号)</th><th style="text-align:center">delete version(删除版本号)</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td style="text-align:center">new_value</td><td style="text-align:center">2</td><td style="text-align:center">3</td></tr></tbody></table><ul><li><p><strong>Select</strong><br>查询时要符合以下两个条件的记录才能被事务查询出来:</p> <font color="red"><strong>1) 删除版本号大于当前事务版本号,就是说删除操作是在当前事务启动之后做的。</strong></font><br> <font color="red"><strong>2) 创建版本号小于或者等于当前事务版本号,就是说记录创建是在事务中(等于的情况)或者事务启动之前。</strong></font> </li></ul><p>保证了各个事务互不影响。从这里也可以体会到一种提高系统性能的思路就是: 通过版本号来减少锁的争用。</p><p>另外,只有read-committed和 repeatable-read 两种事务隔离级别才能使用MVCC</p><p>read-uncommited由于是读到未提交的,所以不存在版本的问题</p><p>而serializable 则会对所有读取的行加锁。 </p><h1 id="存储引擎"><a href="#存储引擎" class="headerlink" title="存储引擎"></a>存储引擎</h1><p>此处只列举两种,MyISAM 与 InnoDb</p><h2 id="InnoDb"><a href="#InnoDb" class="headerlink" title="InnoDb"></a>InnoDb</h2><ol><li>支持ACID的事务,支持事务的四种隔离级别;</li><li>支持行级锁及外键约束:因此可以支持写并发;</li><li>不存储总行数;</li><li>一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;</li><li>主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,防止插入数据时,为维持B+树结构,文件的大调整。</li></ol><h2 id="MyISAM"><a href="#MyISAM" class="headerlink" title="MyISAM"></a>MyISAM</h2><ol><li>不支持事务,但是每次查询都是原子的;</li><li>支持表级锁,即每次操作是对整个表加锁;</li><li>存储表的总行数;</li><li>一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;</li><li>采用菲聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。</li></ol><h2 id="InnoDb与MyISAM索引的区别"><a href="#InnoDb与MyISAM索引的区别" class="headerlink" title="InnoDb与MyISAM索引的区别"></a>InnoDb与MyISAM索引的区别</h2><p>两种存储引擎都是采用B+Tree的结构</p><p>MYISAM的主索引结构如下:<br><img src="/images/2018-12-03/mysql_1.png" alt=""></p><p>辅索引如下:<br><img src="/images/2018-12-03/mysql_2.png" alt=""></p><p>InnoDb的主索引如下:<br><img src="/images/2018-12-03/mysql_3.png" alt=""></p><p>辅索引如下:<br><img src="/images/2018-12-03/mysql_4.png" alt=""></p><p>两种索引数据查找过程如下:<br><img src="/images/2018-12-03/mysql_5.png" alt=""></p><h1 id="索引相关"><a href="#索引相关" class="headerlink" title="索引相关"></a>索引相关</h1>]]></content>
<summary type="html">
<p>事务,隔离级别,存储引擎,索引等</p>
</summary>
<category term="mysql" scheme="http://www.guowenbo.top/tags/mysql/"/>
</entry>
<entry>
<title>linux下的软件安装&环境配置</title>
<link href="http://www.guowenbo.top/2018/11/07/linux%E4%B8%8B%E7%9A%84%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85-%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
<id>http://www.guowenbo.top/2018/11/07/linux下的软件安装-环境配置/</id>
<published>2018-11-07T08:08:30.000Z</published>
<updated>2019-01-03T09:43:41.572Z</updated>
<content type="html"><![CDATA[<p>文件传输,jdk,mysql,jenkins,git,redis,nginx<br>【存档备份】</p><a id="more"></a><h1 id="linux与Window之间的文件传输"><a href="#linux与Window之间的文件传输" class="headerlink" title="linux与Window之间的文件传输"></a>linux与Window之间的文件传输</h1><h2 id="rz-sz-上传下载"><a href="#rz-sz-上传下载" class="headerlink" title="rz sz 上传下载"></a>rz sz 上传下载</h2><p>安装rz,sz,操作很简单。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install lrzsz</span><br></pre></td></tr></table></figure><p>安装之后,就可以进行基本的上传下载操作:</p><p>通过SecureCRT执行rz,进行上传操作。</p><figure class="highlight bash"><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">[root@iZ25ltqcjzrZ ~]<span class="comment"># rz (上传,会弹出窗口,选择上传文件,等待完成。)</span></span><br><span class="line">[root@iZ25ltqcjzrZ ~]<span class="comment"># sz 文件名(下载)</span></span><br></pre></td></tr></table></figure><h2 id="scp传输文件"><a href="#scp传输文件" class="headerlink" title="scp传输文件"></a>scp传输文件</h2><p>由于部分服务器安全考虑,不让安装rz软件,只好使用scp传输。scp的操作命令其实很简单。<br>将本地文件传输的到目标服务器的指定路径下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 文件复制</span></span><br><span class="line"><span class="variable">$scp</span> local_file remote_username@remote_ip:remote_folder</span><br><span class="line"><span class="comment"># 目录复制</span></span><br><span class="line"><span class="variable">$scp</span> -r local_folder remote_username@remote_ip:remote_folder</span><br></pre></td></tr></table></figure><p>其中local_file为本地文件,remote_username目标服务器登录名称,remote_ip目标服务器密码,remote_folder目标服务器下的目标路径。</p><p>将远程文件cp到本地:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$scp</span> remote_username@remote_ip:remote_file local_folder</span><br></pre></td></tr></table></figure><h2 id="SFTP"><a href="#SFTP" class="headerlink" title="SFTP"></a>SFTP</h2><p>SecureCRT可以通过快捷键Alt+p进入sftp连接模式。</p><p>下载文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sftp>get 文件绝对路径</span><br></pre></td></tr></table></figure><p>查看下载到本地的路径,得到下载到本地的路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sftp>lpwd</span><br></pre></td></tr></table></figure><p>上传文件:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sftp>put 本地文件绝对路径</span><br></pre></td></tr></table></figure><h1 id="jdk安装"><a href="#jdk安装" class="headerlink" title="jdk安装"></a>jdk安装</h1><blockquote><p>由于使用 yum 或者 apt-get 命令 安装 openjdk 可能存在类库不全,从而导致用户在安装后运行相关工具时可能报错的问题,推荐采用手动解压安装的方式来安装 JDK。</p></blockquote><ol><li>oracle官网下载jdk<a href="https://www.oracle.com/technetwork/java/javase/overview/index.html" target="_blank" rel="noopener">官网下载</a></li><li><p>创建目录<br> 在/usr/目录下创建java目录</p> <figure class="highlight bash"><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">mkdir /usr/java</span><br><span class="line"><span class="built_in">cd</span> /usr/java</span><br></pre></td></tr></table></figure><p> 把下载的文件 jdk-8u151-linux-x64.tar.gz 复制到在/usr/java/目录下。</p></li><li><p>解压jdk</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf jdk-8u151-linux-x64.tar.gz</span><br></pre></td></tr></table></figure></li><li><p>设置环境变量</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/profile</span><br></pre></td></tr></table></figure><p> 在 profile 文件中添加如下内容并保存:</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">JAVA_HOME=/usr/java/jdk1.8.0_191 </span><br><span class="line">JRE_HOME=/usr/java/jdk1.8.0_191/jre </span><br><span class="line">CLASS_PATH=.:<span class="variable">$JAVA_HOME</span>/lib/dt.jar:<span class="variable">$JAVA_HOME</span>/lib/tools.jar:<span class="variable">$JRE_HOME</span>/lib</span><br><span class="line">PATH=<span class="variable">$PATH</span>:<span class="variable">$JAVA_HOME</span>/bin:<span class="variable">$JRE_HOME</span>/bin</span><br><span class="line"><span class="built_in">export</span> JAVA_HOME JRE_HOME CLASS_PATH PATH</span><br></pre></td></tr></table></figure><p> 注意:其中 JAVA_HOME, JRE_HOME 请根据自己的实际安装路径及 JDK 版本配置。</p><p> 让修改生效(或者直接重启):</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> /etc/profile</span><br></pre></td></tr></table></figure></li><li><p>验证</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -version</span><br></pre></td></tr></table></figure><p> 结果:</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">java version <span class="string">"1.8.0_191"</span></span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_191-b12)</span><br><span class="line">Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)</span><br></pre></td></tr></table></figure></li></ol><h1 id="mysql安装"><a href="#mysql安装" class="headerlink" title="mysql安装"></a>mysql安装</h1><ol><li><p>mysql官网下载<a href="https://www.mysql.com/downloads/" target="_blank" rel="noopener">官网下载</a></p></li><li><p>卸载旧mysql并安装<br> 如果有旧版本mysql删除:</p> <figure class="highlight bash"><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">find / -name mysql</span><br><span class="line">rm -rf <span class="comment">#上边查找到的路径</span></span><br></pre></td></tr></table></figure><p> 安装:</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf mysql-5.6.42-linux-glibc2.12-x86_64.tar.gz <span class="comment">#解压</span></span><br><span class="line">rm -rf mysql-5.6.42-linux-glibc2.12-x86_64.tar.gz <span class="comment">#删除安装包</span></span><br><span class="line">mv mysql-5.6.42-linux-glibc2.12-x86_64/ mysql <span class="comment">#更名mysql</span></span><br></pre></td></tr></table></figure></li><li><p>添加mysql用户组和mysql用户<br> 先检查是否有mysql用户组和mysql用户</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">groups mysql</span><br></pre></td></tr></table></figure><p> 若无,则添加</p> <figure class="highlight bash"><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">groupadd mysql</span><br><span class="line">useradd -r -g mysql mysql</span><br></pre></td></tr></table></figure></li><li><p>进入mysql目录更改权限</p> <figure class="highlight bash"><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"><span class="built_in">cd</span> mysql/</span><br><span class="line">chown -R mysql:mysql ./ <span class="comment">#修改目录拥有者为mysql用户</span></span><br></pre></td></tr></table></figure></li><li><p>执行安装脚本</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./scripts/mysql_install_db --user=mysql --basedir=/usr/<span class="built_in">local</span>/mysql --datadir=/usr/<span class="built_in">local</span>/mysql/data</span><br></pre></td></tr></table></figure><p> 5.7.6之后的版本初始化数据库不再使用mysql_install_db,而是使用: bin/mysqld –initialize</p><p> <strong><em>出现以下异常信息</em></strong>:</p> <font color="red">FATAL ERROR: please install the following Perl modules before executing scripts/mysql_install_db:Data::Dumper</font> <figure class="highlight bash"><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"><span class="comment">#解决方法(缺少安装包perl-Data-Dumper):</span></span><br><span class="line">yum install -y perl-Data-Dumper</span><br></pre></td></tr></table></figure><p> <font color="red">error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory</font></p> <figure class="highlight bash"><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"><span class="comment">#解决方法(缺少安装包libaio和libaio-devel.):</span></span><br><span class="line">yum install libaio*</span><br></pre></td></tr></table></figure><p> 安装完之后修改当前目录拥有者为root用户,修改data目录拥有者为mysql</p> <figure class="highlight bash"><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">chown -R root:root ./ <span class="comment">#修改当前目录拥有者为root用户</span></span><br><span class="line">chown -R mysql:mysql data <span class="comment">#修改当前data目录拥有者为mysql用户</span></span><br></pre></td></tr></table></figure></li><li><p>添加mysql自启动</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">cp support-files/mysql.server /etc/init.d/mysql</span><br><span class="line">chkconfig --add mysql <span class="comment">#添加系统服务</span></span><br><span class="line">chkconfig mysql on</span><br><span class="line"><span class="comment">#创建缺少的文件</span></span><br><span class="line">mkdir /var/<span class="built_in">log</span>/mariadb</span><br><span class="line">touch /var/<span class="built_in">log</span>/mariadb/mariadb.log</span><br><span class="line"><span class="comment">#添加mysql命令</span></span><br><span class="line">ln -s /usr/<span class="built_in">local</span>/mysql/bin/mysql /usr/bin</span><br><span class="line">service mysql start <span class="comment">#启动mysql服务</span></span><br></pre></td></tr></table></figure></li><li><p>更改mysql密码</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./bin/mysqladmin -u root password <span class="string">'root'</span> <span class="comment">#更改密码</span></span><br></pre></td></tr></table></figure><p> <strong><em>更改密码出现以下异常信息</em></strong>:</p> <font color="red">Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)</font> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#解决方法:打开/etc/my.cnf,看看里面配置的socket位置是什么目录。</span></span><br><span class="line"><span class="comment">#“socket=/var/lib/mysql/mysql.sock”路径和“/tmp/mysql.sock”不一致。建立一个软连接:</span></span><br><span class="line">ln -s /var/lib/mysql/mysql.sock /tmp/mysql.sock</span><br></pre></td></tr></table></figure></li><li><p>增加远程登录权限</p><p> 本地登陆MySQL后执行如下命令</p> <figure class="highlight bash"><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">grant all privileges on *.* to root@<span class="string">'%'</span> identified by <span class="string">'root'</span>;</span><br><span class="line">flush privileges;</span><br></pre></td></tr></table></figure><ul><li>grant all privileges on 库名.表名 to ‘用户名‘@’IP地址’ identified by ‘密码’ with grant option;</li><li>库名:要远程访问的数据库名称,所有的数据库使用“*” </li><li>表名:要远程访问的数据库下的表的名称,所有的表使用“*” </li><li>用户名:要赋给远程访问权限的用户名称 </li><li>IP地址:可以远程访问的电脑的IP地址,所有的地址使用“%” </li><li>密码:要赋给远程访问权限的用户对应使用的密码</li></ul></li><li><p>配置my.cnf</p><p> 参考:<a href="http://www.cnblogs.com/lyq863987322/p/8074749.html" target="_blank" rel="noopener">my.cnf配置参数</a></p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">vim my.cnf</span><br><span class="line"><span class="comment">#添加以下语句并保存退出</span></span><br><span class="line">default-charater-set=utf8</span><br><span class="line">lower_case_table_names=1</span><br><span class="line">max_allowed_packet=100M</span><br><span class="line"><span class="comment">#配置好之后,重启mysqld服务</span></span><br><span class="line">service mysql restart</span><br></pre></td></tr></table></figure></li></ol><h1 id="jenkins安装"><a href="#jenkins安装" class="headerlink" title="jenkins安装"></a>jenkins安装</h1><blockquote><p>前置准备java环境,安装JDK,安装方式:rpm包下载安装</p></blockquote><ol><li><p>官网下载<a href="https://jenkins.io/download/" target="_blank" rel="noopener">官网下载</a></p></li><li><p>解压安装</p> <figure class="highlight bash"><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">rpm -ih jenkins-1.562-1.1.noarch.rpm</span><br><span class="line">rm -rf jenkins-1.562-1.1.noarch.rpm</span><br></pre></td></tr></table></figure><p> 自动安装完成之后: </p><ul><li>/usr/lib/jenkins/jenkins.war WAR包 </li><li>/etc/sysconfig/jenkins 配置文件</li><li>/var/lib/jenkins/ 默认的JENKINS_HOME目录</li><li>/var/log/jenkins/jenkins.log Jenkins日志文件</li></ul></li><li><p>配置jenkins</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/sysconfig/jenkins</span><br></pre></td></tr></table></figure><p> 进入jenkins的系统配置文件并修改相关端口号(也可以不修改)<br> jenkins的默认JENKINS_PORT是8080,JENKINS_AJP_PORT默认端口是8009,这同tomcat的默认端口冲突。我这更改为8088和8089。</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/init.d/jenkins</span><br></pre></td></tr></table></figure><p> 加入java环境变量<br> candidates=”/usr/java/jdk1.8.0_191/bin/java”</p></li><li><p>启动</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">service jenkins start</span><br></pre></td></tr></table></figure></li><li><p>jenkins服务安装</p><p> 提示安装自定义插件还是推荐插件,选择推荐插件:<br> <img src="/images/2018-11-08/jenkins_1.png" alt=""><br> <img src="/images/2018-11-08/jenkins_2.png" alt=""><br> 创建管理员用户<br> <img src="/images/2018-11-08/jenkins_3.png" alt=""><br> <img src="/images/2018-11-08/jenkins_4.png" alt=""></p></li></ol><h1 id="git安装"><a href="#git安装" class="headerlink" title="git安装"></a>git安装</h1><ol><li><p>下载<br> 获取github最新的Git安装包下载链接并解压</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wget https://github.com/git/git/archive/v2.17.0.tar.gz</span><br><span class="line">tar -zxvf v2.17.0.tar.gz</span><br><span class="line">rm -rf v2.17.0.tar.gz</span><br></pre></td></tr></table></figure></li><li><p>安装编译源码所需依赖</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker</span><br></pre></td></tr></table></figure><p> 耐心等待安装,出现提示输入y即可;</p><p> 安装依赖时,yum自动安装了Git,需要卸载旧版本Git出现提示输入y即可;</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum remove git</span><br></pre></td></tr></table></figure><p> 出现提示输入y即可</p></li><li><p>编译安装</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> git-2.17.0</span><br><span class="line">make prefix=/usr/<span class="built_in">local</span>/git all</span><br><span class="line"><span class="comment">#安装Git至/usr/local/git路径</span></span><br><span class="line">make prefix=/usr/<span class="built_in">local</span>/git install</span><br></pre></td></tr></table></figure></li><li><p>配置环境变量<br> 打开环境变量配置文件,</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/profile</span><br></pre></td></tr></table></figure><p> 在底部加上Git相关配置信息</p><hr><p> PATH=$PATH:/usr/local/git/bin<br> export PATH</p><hr> <figure class="highlight bash"><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"><span class="comment">#让更改立刻生效</span></span><br><span class="line"><span class="built_in">source</span> /etc/profile</span><br></pre></td></tr></table></figure></li><li><p>验证</p><p> 输入命令 git version ,查看安装的git版本,安装成功。</p></li></ol><h1 id="redis安装"><a href="#redis安装" class="headerlink" title="redis安装"></a>redis安装</h1><ol><li><p>下载</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget http://download.redis.io/releases/redis-3.0.1.tar.gz</span><br></pre></td></tr></table></figure></li><li><p>解压安装</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ tar xzf redis-3.0.1.tar.gz</span><br><span class="line">$ <span class="built_in">cd</span> redis-3.0.1</span><br><span class="line">$ make</span><br><span class="line"><span class="comment">#直接make 编译</span></span><br><span class="line"><span class="comment">#可使用root用户执行`make install`,将可执行文件拷贝到/usr/local/bin目录下。这样就可以直接敲名字运行程序了。</span></span><br><span class="line">$ make install</span><br></pre></td></tr></table></figure><p> 验证 执行redis-server<br> <img src="/images/2018-11-08/redis.png" alt=""></p></li><li><p>redis后台运行<br> redis 默认是在前台运行,这样操作起来很不方便,通过设置redis.conf 可让redis在后台运行</p><p> a)首先编辑conf文件,将daemonize属性改为yes(表明需要在后台运行)</p> <figure class="highlight bash"><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"><span class="built_in">cd</span> etc/</span><br><span class="line">Vim redis.conf</span><br></pre></td></tr></table></figure></li></ol><h1 id="nginx-安装"><a href="#nginx-安装" class="headerlink" title="nginx 安装"></a>nginx 安装</h1><ol><li><p>下载</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget http://nginx.org/download/nginx-1.13.7.tar.gz</span><br></pre></td></tr></table></figure></li><li><p>安装</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">tar -xvf nginx-1.13.7.tar.gz</span><br><span class="line">mv nginx-1.13.7/ nginx</span><br><span class="line"><span class="built_in">cd</span> nginx</span><br><span class="line"><span class="comment">#执行命令</span></span><br><span class="line">./configure</span><br><span class="line"><span class="comment">#执行make命令</span></span><br><span class="line">make</span><br><span class="line"><span class="comment">#执行make install命令</span></span><br><span class="line">make install</span><br></pre></td></tr></table></figure></li><li><p>nginx常用命令</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#测试配置文件</span></span><br><span class="line">安装路径下的/nginx/sbin/nginx -t</span><br><span class="line"><span class="comment">#启动命令</span></span><br><span class="line">安装路径下的/nginx/sbin/nginx</span><br><span class="line"><span class="comment">#停止命令</span></span><br><span class="line">安装路径下的/nginx/sbin/nginx -s stop</span><br><span class="line">或者 : nginx -s quit</span><br><span class="line"><span class="comment">#重启命令</span></span><br><span class="line">安装路径下的/nginx/sbin/nginx -s reload</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<summary type="html">
<p>文件传输,jdk,mysql,jenkins,git,redis,nginx<br>【存档备份】</p>
</summary>
<category term="linux" scheme="http://www.guowenbo.top/tags/linux/"/>
</entry>
<entry>
<title>git命令使用</title>
<link href="http://www.guowenbo.top/2018/11/01/git%E5%91%BD%E4%BB%A4%E4%BD%BF%E7%94%A8/"/>
<id>http://www.guowenbo.top/2018/11/01/git命令使用/</id>
<published>2018-11-01T03:53:15.000Z</published>
<updated>2019-01-03T09:43:41.568Z</updated>
<content type="html"><![CDATA[<p>Git图形化界面我用的还可以,但是命令就不太会了,索性和大家一起学习下Git命令的用法…</p><p>一般来说,日常使用只要记住下图6个命令,就可以了。</p><p><img src="/images/2018-11-01/git.png" alt=""></p><p>下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。</p><blockquote><ul><li>Workspace:工作区</li><li>Index / Stage:暂存区</li><li>Repository:仓库区(或本地仓库)</li><li>Remote:远程仓库</li></ul></blockquote><a id="more"></a><h2 id="一、新建代码库"><a href="#一、新建代码库" class="headerlink" title="一、新建代码库"></a>一、新建代码库</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在当前目录新建一个Git代码库</span></span><br><span class="line">$ git init</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个目录,将其初始化为Git代码库</span></span><br><span class="line">$ git init [project-name]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 下载一个项目和它的整个代码历史</span></span><br><span class="line">$ git <span class="built_in">clone</span> [url]</span><br></pre></td></tr></table></figure><h2 id="二、配置"><a href="#二、配置" class="headerlink" title="二、配置"></a>二、配置</h2><p>Git的设置文件为<code>.gitconfig</code>,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 显示当前的Git配置</span></span><br><span class="line">$ git config --list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编辑Git配置文件</span></span><br><span class="line">$ git config -e [--global]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置提交代码时的用户信息</span></span><br><span class="line">$ git config [--global] user.name <span class="string">"[name]"</span></span><br><span class="line">$ git config [--global] user.email <span class="string">"[email address]"</span></span><br></pre></td></tr></table></figure><h2 id="三、增加-删除文件"><a href="#三、增加-删除文件" class="headerlink" title="三、增加/删除文件"></a>三、增加/删除文件</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加指定文件到暂存区</span></span><br><span class="line">$ git add [file1] [file2] ...</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加指定目录到暂存区,包括子目录</span></span><br><span class="line">$ git add [dir]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加当前目录的所有文件到暂存区</span></span><br><span class="line">$ git add .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加每个变化前,都会要求确认</span></span><br><span class="line"><span class="comment"># 对于同一个文件的多处变化,可以实现分次提交</span></span><br><span class="line">$ git add -p</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除工作区文件,并且将这次删除放入暂存区</span></span><br><span class="line">$ git rm [file1] [file2] ...</span><br><span class="line"></span><br><span class="line"><span class="comment"># 停止追踪指定文件,但该文件会保留在工作区</span></span><br><span class="line">$ git rm --cached [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改名文件,并且将这个改名放入暂存区</span></span><br><span class="line">$ git mv [file-original] [file-renamed]</span><br></pre></td></tr></table></figure><h2 id="四、代码提交"><a href="#四、代码提交" class="headerlink" title="四、代码提交"></a>四、代码提交</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 提交暂存区到仓库区</span></span><br><span class="line">$ git commit -m [message]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交暂存区的指定文件到仓库区</span></span><br><span class="line">$ git commit [file1] [file2] ... -m [message]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交工作区自上次commit之后的变化,直接到仓库区</span></span><br><span class="line">$ git commit -a</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交时显示所有diff信息</span></span><br><span class="line">$ git commit -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用一次新的commit,替代上一次提交</span></span><br><span class="line"><span class="comment"># 如果代码没有任何新变化,则用来改写上一次commit的提交信息</span></span><br><span class="line">$ git commit --amend -m [message]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重做上一次commit,并包括指定文件的新变化</span></span><br><span class="line">$ git commit --amend [file1] [file2] ...</span><br></pre></td></tr></table></figure><h2 id="五、分支"><a href="#五、分支" class="headerlink" title="五、分支"></a>五、分支</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出所有本地分支</span></span><br><span class="line">$ git branch</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出所有远程分支</span></span><br><span class="line">$ git branch -r</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出所有本地分支和远程分支</span></span><br><span class="line">$ git branch -a</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个分支,但依然停留在当前分支</span></span><br><span class="line">$ git branch [branch-name]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个分支,并切换到该分支</span></span><br><span class="line">$ git checkout -b [branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个分支,指向指定commit</span></span><br><span class="line">$ git branch [branch] [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个分支,与指定的远程分支建立追踪关系</span></span><br><span class="line">$ git branch --track [branch] [remote-branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换到指定分支,并更新工作区</span></span><br><span class="line">$ git checkout [branch-name]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换到上一个分支</span></span><br><span class="line">$ git checkout -</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立追踪关系,在现有分支与指定的远程分支之间</span></span><br><span class="line">$ git branch --<span class="built_in">set</span>-upstream [branch] [remote-branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 合并指定分支到当前分支</span></span><br><span class="line">$ git merge [branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 选择一个commit,合并进当前分支</span></span><br><span class="line">$ git cherry-pick [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除分支</span></span><br><span class="line">$ git branch -d [branch-name]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除远程分支</span></span><br><span class="line">$ git push origin --delete [branch-name]</span><br><span class="line">$ git branch -dr [remote/branch]</span><br></pre></td></tr></table></figure><h2 id="六、标签"><a href="#六、标签" class="headerlink" title="六、标签"></a>六、标签</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出所有tag</span></span><br><span class="line">$ git tag</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个tag在当前commit</span></span><br><span class="line">$ git tag [tag]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个tag在指定commit</span></span><br><span class="line">$ git tag [tag] [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除本地tag</span></span><br><span class="line">$ git tag -d [tag]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除远程tag</span></span><br><span class="line">$ git push origin :refs/tags/[tagName]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看tag信息</span></span><br><span class="line">$ git show [tag]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交指定tag</span></span><br><span class="line">$ git push [remote] [tag]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交所有tag</span></span><br><span class="line">$ git push [remote] --tags</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个分支,指向某个tag</span></span><br><span class="line">$ git checkout -b [branch] [tag]</span><br></pre></td></tr></table></figure><h2 id="七、查看信息"><a href="#七、查看信息" class="headerlink" title="七、查看信息"></a>七、查看信息</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 显示有变更的文件</span></span><br><span class="line">$ git status</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示当前分支的版本历史</span></span><br><span class="line">$ git <span class="built_in">log</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示commit历史,以及每次commit发生变更的文件</span></span><br><span class="line">$ git <span class="built_in">log</span> --<span class="built_in">stat</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 搜索提交历史,根据关键词</span></span><br><span class="line">$ git <span class="built_in">log</span> -S [keyword]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某个commit之后的所有变动,每个commit占据一行</span></span><br><span class="line">$ git <span class="built_in">log</span> [tag] HEAD --pretty=format:%s</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件</span></span><br><span class="line">$ git <span class="built_in">log</span> [tag] HEAD --grep feature</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某个文件的版本历史,包括文件改名</span></span><br><span class="line">$ git <span class="built_in">log</span> --follow [file]</span><br><span class="line">$ git whatchanged [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示指定文件相关的每一次diff</span></span><br><span class="line">$ git <span class="built_in">log</span> -p [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示过去5次提交</span></span><br><span class="line">$ git <span class="built_in">log</span> -5 --pretty --oneline</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示所有提交过的用户,按提交次数排序</span></span><br><span class="line">$ git shortlog -sn</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示指定文件是什么人在什么时间修改过</span></span><br><span class="line">$ git blame [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示暂存区和工作区的代码差异</span></span><br><span class="line">$ git diff</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示暂存区和上一个commit的差异</span></span><br><span class="line">$ git diff --cached [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示工作区与当前分支最新commit之间的差异</span></span><br><span class="line">$ git diff HEAD</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示两次提交之间的差异</span></span><br><span class="line">$ git diff [first-branch]...[second-branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示今天你写了多少行代码</span></span><br><span class="line">$ git diff --shortstat <span class="string">"@{0 day ago}"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某次提交的元数据和内容变化</span></span><br><span class="line">$ git show [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某次提交发生变化的文件</span></span><br><span class="line">$ git show --name-only [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某次提交时,某个文件的内容</span></span><br><span class="line">$ git show [commit]:[filename]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示当前分支的最近几次提交</span></span><br><span class="line">$ git reflog<span class="comment"># 从本地master拉取代码更新当前分支:branch 一般为master$ git rebase [branch]</span></span><br></pre></td></tr></table></figure><h2 id="八、远程同步"><a href="#八、远程同步" class="headerlink" title="八、远程同步"></a>八、远程同步</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">$ git remote update --更新远程仓储</span><br><span class="line"><span class="comment"># 下载远程仓库的所有变动</span></span><br><span class="line">$ git fetch [remote]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示所有远程仓库</span></span><br><span class="line">$ git remote -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示某个远程仓库的信息</span></span><br><span class="line">$ git remote show [remote]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 增加一个新的远程仓库,并命名</span></span><br><span class="line">$ git remote add [shortname] [url]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 取回远程仓库的变化,并与本地分支合并</span></span><br><span class="line">$ git pull [remote] [branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 上传本地指定分支到远程仓库</span></span><br><span class="line">$ git push [remote] [branch]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 强行推送当前分支到远程仓库,即使有冲突</span></span><br><span class="line">$ git push [remote] --force</span><br><span class="line"></span><br><span class="line"><span class="comment"># 推送所有分支到远程仓库</span></span><br><span class="line">$ git push [remote] --all</span><br></pre></td></tr></table></figure><h2 id="九、撤销"><a href="#九、撤销" class="headerlink" title="九、撤销"></a>九、撤销</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 恢复暂存区的指定文件到工作区</span></span><br><span class="line">$ git checkout [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 恢复某个commit的指定文件到暂存区和工作区</span></span><br><span class="line">$ git checkout [commit] [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 恢复暂存区的所有文件到工作区</span></span><br><span class="line">$ git checkout .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变</span></span><br><span class="line">$ git reset [file]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置暂存区与工作区,与上一次commit保持一致</span></span><br><span class="line">$ git reset --hard</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变</span></span><br><span class="line">$ git reset [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致</span></span><br><span class="line">$ git reset --hard [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置当前HEAD为指定commit,但保持暂存区和工作区不变</span></span><br><span class="line">$ git reset --keep [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新建一个commit,用来撤销指定commit</span></span><br><span class="line"><span class="comment"># 后者的所有变化都将被前者抵消,并且应用到当前分支</span></span><br><span class="line">$ git revert [commit]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 暂时将未提交的变化移除,稍后再移入</span></span><br><span class="line">$ git stash</span><br><span class="line">$ git stash pop</span><br></pre></td></tr></table></figure><h2 id="十、其他"><a href="#十、其他" class="headerlink" title="十、其他"></a>十、其他</h2><figure class="highlight bash"><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"><span class="comment"># 生成一个可供发布的压缩包</span></span><br><span class="line">$ git archive</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>Git图形化界面我用的还可以,但是命令就不太会了,索性和大家一起学习下Git命令的用法…</p>
<p>一般来说,日常使用只要记住下图6个命令,就可以了。</p>
<p><img src="/images/2018-11-01/git.png" alt=""></p>
<p>下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。</p>
<blockquote>
<ul>
<li>Workspace:工作区</li>
<li>Index / Stage:暂存区</li>
<li>Repository:仓库区(或本地仓库)</li>
<li>Remote:远程仓库</li>
</ul>
</blockquote>
</summary>
<category term="git" scheme="http://www.guowenbo.top/tags/git/"/>
</entry>
<entry>
<title>SpringBoot 6.定时任务</title>
<link href="http://www.guowenbo.top/2018/07/06/SpringBoot%206.%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/"/>
<id>http://www.guowenbo.top/2018/07/06/SpringBoot 6.定时任务/</id>
<published>2018-07-06T08:54:01.000Z</published>
<updated>2019-01-03T09:43:41.568Z</updated>
<content type="html"><![CDATA[<h1 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h1><p>项目开发过程中,经常需要定时任务来帮助我们来做一些内容,springboot默认已经帮我们实行了,只需要添加相应的注解就可以实现。比如定时发送邮件。</p><h2 id="引入pom"><a href="#引入pom" class="headerlink" title="引入pom"></a>引入pom</h2><p>pom包里面只需要引入springboot starter包即可</p><figure class="highlight"><table><tr><td class="gutter"><pre><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"><dependency></span><br><span class="line"><groupId>org.springframework.boot</groupId></span><br><span class="line"><artifactId>spring-boot-starter</artifactId></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="启动类启用定时"><a href="#启动类启用定时" class="headerlink" title="启动类启用定时"></a>启动类启用定时</h2><p>在启动类上面加上 <code>@EnableScheduling</code> 即可开启定时</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableScheduling</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Application</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">SpringApplication.run(Application.class, args);</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><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestScheduler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> UserController userController;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Scheduled</span>(cron = <span class="string">"*/6 * * * * ?"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendEmail</span><span class="params">()</span></span>{</span><br><span class="line"> userController.sendHtmlEmail(<span class="string">"XXXX@qq.com"</span>,<span class="string">"[TestScheduler]"</span>,<span class="string">"####TestScheduler####"</span>);</span><br><span class="line"> System.out.println(<span class="string">"send success!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Scheduled</span>(fixedRate = <span class="number">6000</span>)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span>{</span><br><span class="line"> System.out.println(<span class="string">"test"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>启动项目后</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">启动时间:<span class="number">9</span>秒</span><br><span class="line">...............................................................</span><br><span class="line">..................Service starts successfully..................</span><br><span class="line">...............................................................</span><br><span class="line">send success!</span><br><span class="line">current time:<span class="number">17</span>:<span class="number">01</span>:<span class="number">16</span></span><br><span class="line">send success!</span><br><span class="line">current time:<span class="number">17</span>:<span class="number">01</span>:<span class="number">22</span></span><br><span class="line">send success!</span><br><span class="line">current time:<span class="number">17</span>:<span class="number">01</span>:<span class="number">28</span></span><br><span class="line">send success!</span><br><span class="line">current time:<span class="number">17</span>:<span class="number">01</span>:<span class="number">34</span></span><br></pre></td></tr></table></figure><h2 id="Scheduled-参数说明"><a href="#Scheduled-参数说明" class="headerlink" title="@Scheduled 参数说明"></a><code>@Scheduled</code> 参数说明</h2><p>上面两种定时的设置,一种是我们常用的 <code>cron="*/6 * * * * ?"</code> ,一种是 <code>fixedRate = 6000</code> ,两种都表示每隔六秒打印一下内容。</p><p><strong>说明</strong></p><ul><li><code>cron</code>:指定cron表达式</li><li><code>zone</code>:默认使用服务器默认时区。可以设置为java.util.TimeZone中的zoneId</li><li><code>fixedDelay</code>:从上一个任务完成开始到下一个任务开始的间隔,单位毫秒</li><li><code>fixedDelayString</code>:同上,时间值是String类型</li><li><code>fixedRate</code>:从上一个任务开始到下一个任务开始的间隔,单位毫秒</li><li><code>fixedRateString</code>:同上,时间值是String类型</li><li><code>initialDelay</code>:任务首次执行延迟的时间,单位毫秒</li><li><code>initialDelayString</code>:同上,时间值是String类型</li></ul><h1 id="cron表达式格式"><a href="#cron表达式格式" class="headerlink" title="cron表达式格式"></a>cron表达式格式</h1><p><code>{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}</code><br>例 “0 0 12 ? * WED” 在每星期三下午12:00 执行(年份通常 省略)<br>先了解每个位置代表的含义,在了解每个位置允许的范围,以及一些特殊写法,还有常用的案例,足够你掌握cron表达式</p><h2 id="每个字段的允许值"><a href="#每个字段的允许值" class="headerlink" title="每个字段的允许值"></a>每个字段的允许值</h2><table><thead><tr><th>字段</th><th>允许值</th><th>允许的特殊字符</th></tr></thead><tbody><tr><td>秒</td><td>0-59</td><td>- * /</td></tr><tr><td>分</td><td>0-59</td><td>- * /</td></tr><tr><td>小时</td><td>0-23</td><td>- * /</td></tr><tr><td>日期</td><td>1-31</td><td>- * ? / L W C</td></tr><tr><td>月份</td><td>1-12 或者 JAN-DEC</td><td>- * /</td></tr><tr><td>星期</td><td>1-7 或者 SUN-SAT</td><td>- * ? / L C #</td></tr><tr><td>年(可选)</td><td>留空, 1970-2099</td><td>- * /</td></tr></tbody></table><h2 id="允许值的意思"><a href="#允许值的意思" class="headerlink" title="允许值的意思"></a>允许值的意思</h2><p>Seconds (秒) :可以用数字0-59 表示,</p><p>Minutes(分) :可以用数字0-59 表示,</p><p>Hours(时) :可以用数字0-23表示,</p><p>Day-of-Month(天) :可以用数字1-31 中的任一一个值,但要注意一些特别的月份</p><p>Month(月) :可以用0-11 或用字符串 “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示</p><p>Day-of-Week(每周):可以用数字1-7表示(1 = 星期日)或用字符口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示</p><h2 id="每个符号的意义"><a href="#每个符号的意义" class="headerlink" title="每个符号的意义"></a>每个符号的意义</h2><ul><li><code>*</code> 表示所有值; </li><li><code>?</code> 表示未说明的值,即不关心它为何值; </li><li><code>-</code> 表示一个指定的范围; </li><li><code>,</code> 表示附加一个可能值; </li><li><code>/</code> 符号前表示开始时间,符号后表示每次递增的值; </li><li><code>L</code>(“last”) (“last”) “L” 用在day-of-month字段意思是 “这个月最后一天”;用在 day-of-week字段, 它简单意思是 “7” or “SAT”。 如果在day-of-week字段里和数字联合使用,它的意思就是 “这个月的最后一个星期几” – 例如: “6L” means “这个月的最后一个星期五”. 当我们用“L”时,不指明一个列表值或者范围是很重要的,不然的话,我们会得到一些意想不到的结果。 </li><li><code>W</code>(“weekday”) 只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个 月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第 16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在 day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。 </li><li><code>#</code> 只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用”6#3”指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。<br>C 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。</li></ul><h2 id="一些cron表达式案例"><a href="#一些cron表达式案例" class="headerlink" title="一些cron表达式案例"></a>一些cron表达式案例</h2><table><thead><tr><th>栗子</th><th>解释</th></tr></thead><tbody><tr><td><em>/5 </em> <em> </em> * ?</td><td>每隔5秒执行一次</td></tr><tr><td>0 <em>/1 </em> <em> </em> ?</td><td>每隔1分钟执行一次</td></tr><tr><td>0 0 5-15 <em> </em> ?</td><td>每天5-15点整点触发</td></tr><tr><td>0 0/3 <em> </em> * ?</td><td>每三分钟触发一次</td></tr><tr><td>0 0-5 14 <em> </em> ?</td><td>在每天下午2点到下午2:05期间的每1分钟触发</td></tr><tr><td>0 0/5 14 <em> </em> ?</td><td>在每天下午2点到下午2:55期间的每5分钟触发</td></tr><tr><td>0 0/5 14,18 <em> </em> ?</td><td>在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发</td></tr><tr><td>0 0/30 9-17 <em> </em> ?</td><td>朝九晚五工作时间内每半小时</td></tr><tr><td>0 0 10,14,16 <em> </em> ?</td><td>每天上午10点,下午2点,4点</td></tr><tr><td>0 0 12 ? * WED</td><td>表示每个星期三中午12点</td></tr><tr><td>0 0 17 ? * TUES,THUR,SAT</td><td>每周二、四、六下午五点</td></tr><tr><td>0 10,44 14 ? 3 WED</td><td>每年三月的星期三的下午2:10和2:44触发</td></tr><tr><td>0 15 10 ? * MON-FRI</td><td>周一至周五的上午10:15触发</td></tr><tr><td>0 0 23 L * ?</td><td>每月最后一天23点执行一次</td></tr><tr><td>0 15 10 L * ?</td><td>每月最后一日的上午10:15触发</td></tr><tr><td>0 15 10 ? * 6L</td><td>每月的最后一个星期五上午10:15触发</td></tr><tr><td>0 15 10 <em> </em> ? 2005</td><td>2005年的每天上午10:15触发</td></tr><tr><td>0 15 10 ? * 6L 2002-2005</td><td>2002年至2005年的每月的最后一个星期五上午10:15触发</td></tr><tr><td>0 15 10 ? * 6#3</td><td>每月的第三个星期五上午10:15触发</td></tr><tr><td>30 <em> </em> <em> </em> ?</td><td>每半分钟触发任务</td></tr><tr><td>30 10 <em> </em> * ?</td><td>每小时的10分30秒触发任务</td></tr><tr><td>30 10 1 <em> </em> ?</td><td>每天1点10分30秒触发任务</td></tr><tr><td>30 10 1 20 * ?</td><td>每月20号1点10分30秒触发任务</td></tr><tr><td>30 10 1 20 10 ? *</td><td>每年10月20号1点10分30秒触发任务</td></tr><tr><td>30 10 1 20 10 ? 2011</td><td>2011年10月20号1点10分30秒触发任务</td></tr><tr><td>30 10 1 ? 10 * 2011</td><td>2011年10月每天1点10分30秒触发任务</td></tr><tr><td>30 10 1 ? 10 SUN 2011</td><td>2011年10月每周日1点10分30秒触发任务</td></tr><tr><td>15,30,45 <em> </em> <em> </em> ?</td><td>每15秒,30秒,45秒时触发任务</td></tr><tr><td>15-45 <em> </em> <em> </em> ?</td><td>15到45秒内,每秒都触发任务</td></tr><tr><td>15/5 <em> </em> <em> </em> ?</td><td>每分钟的每15秒开始触发,每隔5秒触发一次</td></tr><tr><td>15-30/5 <em> </em> <em> </em> ?</td><td>每分钟的15秒到30秒之间开始触发,每隔5秒触发一次</td></tr><tr><td>0 0/3 <em> </em> * ?</td><td>每小时的第0分0秒开始,每三分钟触发一次</td></tr><tr><td>0 15 10 ? * MON-FRI</td><td>星期一到星期五的10点15分0秒触发任务</td></tr><tr><td>0 15 10 L * ?</td><td>每个月最后一天的10点15分0秒触发任务</td></tr><tr><td>0 15 10 LW * ?</td><td>每个月最后一个工作日的10点15分0秒触发任务</td></tr><tr><td>0 15 10 ? * 5L</td><td>每个月最后一个星期四的10点15分0秒触发任务</td></tr><tr><td>0 15 10 ? * 5#3</td><td>每个月第三周的星期四的10点15分0秒触发任务</td></tr></tbody></table>]]></content>
<summary type="html">
<h1 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h1><p>项目开发过程中,经常需要定时任务来帮助我们来做一些内容,springboot默认已经帮我们实行了,只需要添加相应的注解就可以实现。比如定时发送邮件。</p>
<h2 id="引入pom"><a href="#引入pom" class="headerlink" title="引入pom"></a>引入pom</h2><p>pom包里面只需要引入springboot starter包即可</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><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">&lt;dependency&gt;</span><br><span class="line"> &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line"> &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>
</summary>
<category term="springboot" scheme="http://www.guowenbo.top/categories/springboot/"/>
<category term="springboot" scheme="http://www.guowenbo.top/tags/springboot/"/>
<category term="scheduler" scheme="http://www.guowenbo.top/tags/scheduler/"/>
</entry>
<entry>
<title>面试必问-ThreadLocal</title>
<link href="http://www.guowenbo.top/2018/07/05/%E9%9D%A2%E8%AF%95%E5%BF%85%E9%97%AE-ThreadLocal/"/>
<id>http://www.guowenbo.top/2018/07/05/面试必问-ThreadLocal/</id>
<published>2018-07-05T06:14:04.000Z</published>
<updated>2019-01-03T09:43:41.573Z</updated>
<content type="html"><![CDATA[<ul><li>“知道ThreadLocal吗?”</li><li>“讲讲你对ThreadLocal的理解”</li><li>“在多线程环境下,如何防止自己的变量被其它线程篡改”</li></ul><p>那么ThreadLocal可以做什么,在了解它的应用场景之前,我们先看看它的实现原理,只有知道了实现原理,才好判断它是否符合自己的业务场景。</p><blockquote><p>转:<a href="https://www.jianshu.com/p/377bb840802f" target="_blank" rel="noopener">https://www.jianshu.com/p/377bb840802f</a></p></blockquote><a id="more"></a><h2 id="ThreadLocal是什么"><a href="#ThreadLocal是什么" class="headerlink" title="ThreadLocal是什么"></a>ThreadLocal是什么</h2><p>首先,它是一个数据结构,有点像HashMap,可以保存”key : value”键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ThreadLocal<String> localName = <span class="keyword">new</span> ThreadLocal();</span><br><span class="line">localName.set(<span class="string">"占小狼"</span>);</span><br><span class="line">String name = localName.get();</span><br></pre></td></tr></table></figure><p>在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个值占小狼,同时在线程1中通过localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。</p><p>这是为什么,如何实现?不过之前也说了,ThreadLocal保证了各个线程的数据互不干扰。</p><p>看看set(T value)和get()方法的源码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(T value)</span> </span>{</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> <span class="keyword">if</span> (map != <span class="keyword">null</span>)</span><br><span class="line"> map.set(<span class="keyword">this</span>, value);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> createMap(t, value);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> T <span class="title">get</span><span class="params">()</span> </span>{</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> <span class="keyword">if</span> (map != <span class="keyword">null</span>) {</span><br><span class="line"> ThreadLocalMap.Entry e = map.getEntry(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> T result = (T)e.value;</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> setInitialValue();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">ThreadLocalMap <span class="title">getMap</span><span class="params">(Thread t)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> t.threadLocals;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以发现,每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行set方法中,是从当前线程的threadLocals变量获取。</p><p>所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。</p><p>那每个线程中的ThreadLoalMap究竟是什么?</p><h2 id="ThreadLoalMap"><a href="#ThreadLoalMap" class="headerlink" title="ThreadLoalMap"></a>ThreadLoalMap</h2><p>本文分析的是1.7的源码。</p><p>从名字上看,可以猜到它也是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口。</p><p>在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,是不是很神奇,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。</p><p>这里需要注意的是,ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。</p><h2 id="hash冲突"><a href="#hash冲突" class="headerlink" title="hash冲突"></a>hash冲突</h2><p>没有链表结构,那发生hash冲突了怎么办?</p><p>先看看ThreadLoalMap中插入一个key-value的实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(ThreadLocal<?> key, Object value)</span> </span>{</span><br><span class="line"> Entry[] tab = table;</span><br><span class="line"> <span class="keyword">int</span> len = tab.length;</span><br><span class="line"> <span class="keyword">int</span> i = key.threadLocalHashCode & (len-<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Entry e = tab[i];</span><br><span class="line"> e != <span class="keyword">null</span>;</span><br><span class="line"> e = tab[i = nextIndex(i, len)]) {</span><br><span class="line"> ThreadLocal<?> k = e.get();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (k == key) {</span><br><span class="line"> e.value = value;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (k == <span class="keyword">null</span>) {</span><br><span class="line"> replaceStaleEntry(key, value, i);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> tab[i] = <span class="keyword">new</span> Entry(key, value);</span><br><span class="line"> <span class="keyword">int</span> sz = ++size;</span><br><span class="line"> <span class="keyword">if</span> (!cleanSomeSlots(i, sz) && sz >= threshold)</span><br><span class="line"> rehash();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647。</p><p>在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下:</p><ol><li>如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;</li><li>不巧,位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;</li><li>很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;</li></ol><p>这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置</p><p>可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。</p><h2 id="内存泄露"><a href="#内存泄露" class="headerlink" title="内存泄露"></a>内存泄露</h2><p>ThreadLocal可能导致内存泄漏,为什么?<br>先看看Entry的实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span> <span class="keyword">extends</span> <span class="title">WeakReference</span><<span class="title">ThreadLocal</span><?>> </span>{</span><br><span class="line"> <span class="comment">/** The value associated with this ThreadLocal. */</span></span><br><span class="line"> Object value;</span><br><span class="line"> Entry(ThreadLocal<?> k, Object v) {</span><br><span class="line"> <span class="keyword">super</span>(k);</span><br><span class="line"> value = v;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过之前的分析已经知道,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。</p><p>这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。</p><h2 id="如何避免内存泄露"><a href="#如何避免内存泄露" class="headerlink" title="如何避免内存泄露"></a>如何避免内存泄露</h2><p>既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。</p><p>如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ThreadLocal<String> localName = <span class="keyword">new</span> ThreadLocal();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> localName.set(<span class="string">"占小狼"</span>);</span><br><span class="line"> <span class="comment">// 其它业务逻辑</span></span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> localName.remove();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<ul>
<li>“知道ThreadLocal吗?”</li>
<li>“讲讲你对ThreadLocal的理解”</li>
<li>“在多线程环境下,如何防止自己的变量被其它线程篡改”</li>
</ul>
<p>那么ThreadLocal可以做什么,在了解它的应用场景之前,我们先看看它的实现原理,只有知道了实现原理,才好判断它是否符合自己的业务场景。</p>
<blockquote>
<p>转:<a href="https://www.jianshu.com/p/377bb840802f" target="_blank" rel="noopener">https://www.jianshu.com/p/377bb840802f</a></p>
</blockquote>
</summary>
<category term="高并发" scheme="http://www.guowenbo.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/"/>
<category term="ThreadLocal" scheme="http://www.guowenbo.top/tags/ThreadLocal/"/>
</entry>
<entry>
<title>ReentrantLock与synchronize(synchronize锁优化)</title>
<link href="http://www.guowenbo.top/2018/07/05/ReentrantLock%E4%B8%8Esynchronize%EF%BC%88synchronize%E9%94%81%E4%BC%98%E5%8C%96%EF%BC%89/"/>
<id>http://www.guowenbo.top/2018/07/05/ReentrantLock与synchronize(synchronize锁优化)/</id>
<published>2018-07-05T03:27:38.000Z</published>
<updated>2019-01-03T09:43:41.558Z</updated>
<content type="html"><![CDATA[<p><img src="/images/2018-07-05/smile.jpg" alt=""></p><a id="more"></a><h1 id="前置"><a href="#前置" class="headerlink" title="前置"></a>前置</h1><h2 id="CAS-Compare-And-Swap"><a href="#CAS-Compare-And-Swap" class="headerlink" title="CAS(Compare And Swap)"></a>CAS(Compare And Swap)</h2><p>CAS算法的过程是这样:它包含3个参数 <code>CAS(V,A,B)</code> 。 <code>V</code> 表示要更新的变量,<code>A</code> 表示预期值,<code>B</code> 表示新值。仅当V值等于A值时,才会将V的值设为B,如果V值和A值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。</p><p><strong>CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作</strong></p><ol><li>ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。</li></ol><p>从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。</p><ol start="2"><li><p>循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。</p></li><li><p>只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。</p></li></ol><h2 id="AQS-AbstractQueuedSynchronizer"><a href="#AQS-AbstractQueuedSynchronizer" class="headerlink" title="AQS(AbstractQueuedSynchronizer)"></a>AQS(AbstractQueuedSynchronizer)</h2><p><code>AQS</code> 是 <code>Java</code> 并发包里实现锁、同步的一个重要的基础框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 <code>CountDownLatch</code>类的源码实现,会发现其内部有一个继承了 <code>AbstractQueuedSynchronizer</code> 的内部类 <code>Sync</code> 。可见 <code>CountDownLatch</code> 是基于AQS框架来实现的一个同步器.</p><h1 id="ReentrantLock-实现原理"><a href="#ReentrantLock-实现原理" class="headerlink" title="ReentrantLock 实现原理"></a>ReentrantLock 实现原理</h1><p>使用 <code>synchronize</code> 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。</p><p>而 <code>ReentrantLock</code> 就是一个普通的类,它是基于 <code>AQS(AbstractQueuedSynchronizer)</code>来实现的。</p><p>是一个<strong>重入锁</strong>:一个线程获得了锁之后仍然可以<strong>反复</strong>的加锁,不会出现自己阻塞自己的情况。</p><h2 id="锁类型"><a href="#锁类型" class="headerlink" title="锁类型"></a>锁类型</h2><p>ReentrantLock 分为<strong>公平锁和非公平锁</strong>,可以通过构造方法来指定具体类型:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//默认非公平锁</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">()</span> </span>{</span><br><span class="line"> sync = <span class="keyword">new</span> NonfairSync();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//公平锁</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">(<span class="keyword">boolean</span> fair)</span> </span>{</span><br><span class="line"> sync = fair ? <span class="keyword">new</span> FairSync() : <span class="keyword">new</span> NonfairSync();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>默认一般使用<strong>非公平锁</strong>,它的效率和吞吐量都比公平锁高的多。</p><h2 id="获取锁"><a href="#获取锁" class="headerlink" title="获取锁"></a>获取锁</h2><p>通常的使用方式如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> ReentrantLock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//do bussiness</span></span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>公平锁获取锁</p><p>首先看下获取锁的过程:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> sync.lock();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>可以看到是使用 sync的方法,而这个方法是一个抽象方法,具体是由其子类(FairSync)来实现的,以下是公平锁的实现:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> acquire(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//AbstractQueuedSynchronizer 中的 acquire()</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!tryAcquire(arg) &&</span><br><span class="line"> acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line"> selfInterrupt();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>第一步是尝试获取锁(tryAcquire(arg)),这个也是由其子类实现:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line"> <span class="keyword">int</span> c = getState();</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (!hasQueuedPredecessors() &&</span><br><span class="line"> compareAndSetState(<span class="number">0</span>, acquires)) {</span><br><span class="line"> setExclusiveOwnerThread(current);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) {</span><br><span class="line"> <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line"> <span class="keyword">if</span> (nextc < <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);</span><br><span class="line"> setState(nextc);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>首先会判断 <code>AQS</code> 中的 <code>state</code> 是否等于 0,0 表示目前没有其他线程获得锁,当前线程就可以尝试获取锁。</p><p>注意:尝试之前会利用 <code>hasQueuedPredecessors()</code> 方法来判断 AQS 的队列中中是否有其他线程,如果有则不会尝试获取锁(这是公平锁特有的情况)。</p><p>如果队列中没有线程就利用 <code>CAS</code> 来将 <code>AQS</code> 中的 <code>state</code> 修改为1,也就是获取锁,获取成功则将当前线程置为获得锁的独占线程(<code>setExclusiveOwnerThread(current)</code>)。</p><p>如果 <code>state</code> 大于 0 时,说明锁已经被获取了,则需要判断获取锁的线程是否为当前线程(ReentrantLock 支持重入),是则需要将 <code>state + 1</code>,并将值更新。</p><h3 id="写入队列"><a href="#写入队列" class="headerlink" title="写入队列"></a>写入队列</h3><p>如果 <code>tryAcquire(arg)</code> 获取锁失败,则需要用 <code>addWaiter(Node.EXCLUSIVE)</code> 将当前线程写入队列中。</p><p>写入之前需要将当前线程包装为一个 <code>Node</code> 对象(<code>addWaiter(Node.EXCLUSIVE)</code>)。</p><blockquote><p>AQS 中的队列是由 Node 节点组成的双向链表实现的。</p></blockquote><p>包装代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">addWaiter</span><span class="params">(Node mode)</span> </span>{</span><br><span class="line"> Node node = <span class="keyword">new</span> Node(Thread.currentThread(), mode);</span><br><span class="line"> <span class="comment">// Try the fast path of enq; backup to full enq on failure</span></span><br><span class="line"> Node pred = tail;</span><br><span class="line"> <span class="keyword">if</span> (pred != <span class="keyword">null</span>) {</span><br><span class="line"> node.prev = pred;</span><br><span class="line"> <span class="keyword">if</span> (compareAndSetTail(pred, node)) {</span><br><span class="line"> pred.next = node;</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> enq(node);</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先判断队列是否为空,不为空时则将封装好的 <code>Node</code> 利用 <code>CAS</code> 写入队尾,如果出现并发写入失败就需要调用 <code>enq(node);</code> 来写入了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">enq</span><span class="params">(<span class="keyword">final</span> Node node)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> Node t = tail;</span><br><span class="line"> <span class="keyword">if</span> (t == <span class="keyword">null</span>) { <span class="comment">// Must initialize</span></span><br><span class="line"> <span class="keyword">if</span> (compareAndSetHead(<span class="keyword">new</span> Node()))</span><br><span class="line"> tail = head;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> node.prev = t;</span><br><span class="line"> <span class="keyword">if</span> (compareAndSetTail(t, node)) {</span><br><span class="line"> t.next = node;</span><br><span class="line"> <span class="keyword">return</span> t;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个处理逻辑就相当于<code>自旋</code>加上 <code>CAS</code> 保证一定能写入队列。</p><h3 id="挂起等待线程"><a href="#挂起等待线程" class="headerlink" title="挂起等待线程"></a>挂起等待线程</h3><p>写入队列之后需要将当前线程挂起(利用<code>acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</code>):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line"> <span class="keyword">if</span> (p == head && tryAcquire(arg)) {</span><br><span class="line"> setHead(node);</span><br><span class="line"> p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line"> failed = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">return</span> interrupted;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &&</span><br><span class="line"> parkAndCheckInterrupt())</span><br><span class="line"> interrupted = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (failed)</span><br><span class="line"> cancelAcquire(node);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先会根据 <code>node.predecessor()</code> 获取到上一个节点是否为头节点,如果是则尝试获取一次锁,获取成功就万事大吉了。</p><p>如果不是头节点,或者获取锁失败,则会根据上一个节点的 <code>waitStatus</code> 状态来处理(<code>shouldParkAfterFailedAcquire(p, node)</code>)。</p><p><code>waitStatus</code> 用于记录当前节点的状态,如节点取消、节点等待等。</p><p><code>shouldParkAfterFailedAcquire(p, node)</code> 返回当前线程是否需要挂起,如果需要则调用 <code>parkAndCheckInterrupt()</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>{</span><br><span class="line"> LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">return</span> Thread.interrupted();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>他是利用 <code>LockSupport</code> 的 <code>part</code> 方法来挂起当前线程的,直到被唤醒。</p><h3 id="非公平锁获取锁"><a href="#非公平锁获取锁" class="headerlink" title="非公平锁获取锁"></a>非公平锁获取锁</h3><p>公平锁与非公平锁的差异主要在获取锁:</p><p>公平锁就相当于买票,后来的人需要排到队尾依次买票,<strong>不能插队</strong>。</p><p>而非公平锁则没有这些规则,是<strong>抢占模式</strong>,每来一个人不会去管队列如何,直接尝试获取锁。</p><p>非公平锁:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//直接尝试获取锁</span></span><br><span class="line"> <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line"> setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> acquire(<span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>公平锁:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{</span><br><span class="line"> acquire(<span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>还要一个重要的区别是在尝试获取锁时<code>tryAcquire(arg)</code>,非公平锁是不需要判断队列中是否还有其他线程,也是直接尝试获取锁:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">nonfairTryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line"> <span class="keyword">int</span> c = getState();</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">//没有 !hasQueuedPredecessors() 判断</span></span><br><span class="line"> <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) {</span><br><span class="line"> setExclusiveOwnerThread(current);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) {</span><br><span class="line"> <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line"> <span class="keyword">if</span> (nextc < <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);</span><br><span class="line"> setState(nextc);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="释放锁"><a href="#释放锁" class="headerlink" title="释放锁"></a>释放锁</h2><p>公平锁和非公平锁的释放流程都是一样的:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>{</span><br><span class="line"> sync.release(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (tryRelease(arg)) {</span><br><span class="line"> Node h = head;</span><br><span class="line"> <span class="keyword">if</span> (h != <span class="keyword">null</span> && h.waitStatus != <span class="number">0</span>)</span><br><span class="line"> <span class="comment">//唤醒被挂起的线程</span></span><br><span class="line"> unparkSuccessor(h);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//尝试释放锁</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> releases)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> c = getState() - releases;</span><br><span class="line"> <span class="keyword">if</span> (Thread.currentThread() != getExclusiveOwnerThread())</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line"> <span class="keyword">boolean</span> free = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) {</span><br><span class="line"> free = <span class="keyword">true</span>;</span><br><span class="line"> setExclusiveOwnerThread(<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> setState(c);</span><br><span class="line"> <span class="keyword">return</span> free;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先会判断当前线程是否为获得锁的线程,由于是重入锁所以需要将 <code>state</code> 减到 0 才认为完全释放锁。</p><p>释放之后需要调用 <code>unparkSuccessor(h)</code> 来唤醒被挂起的线程。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>由于公平锁需要关心队列的情况,得按照队列里的先后顺序来获取锁(会造成大量的线程上下文切换),而非公平锁则没有这个限制。</p><p>所以也就能解释非公平锁的效率会被公平锁更高。</p><h1 id="Synchronize-关键字原理"><a href="#Synchronize-关键字原理" class="headerlink" title="Synchronize 关键字原理"></a>Synchronize 关键字原理</h1><p>众所周知 <code>Synchronize</code> 关键字是解决并发问题常用解决方案,有以下三种使用方式:</p><ul><li>同步普通方法,锁的是当前对象。</li><li>同步静态方法,锁的是当前 <code>Class</code> 对象。</li><li>同步块,锁的是 <code>{}</code> 中的对象。</li></ul><p>实现原理:<br><code>JVM</code> 是通过进入、退出对象监视器( <code>Monitor</code> )来实现对方法、同步块的同步的。</p><p>具体实现是在编译之后在同步方法调用前加入一个 <code>monitor.enter</code> 指令,在退出方法和异常处插入 <code>monitor.exit</code> 的指令。</p><p>其本质就是对一个对象监视器( <code>Monitor</code> )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。</p><p>而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 <code>monitor.exit</code> 之后才能尝试继续获取锁。</p><p>流程图如下:</p><p><img src="images/2018-07-05/synchronize.jpg" alt=""></p><p>通过一段代码来演示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (Synchronize.class){</span><br><span class="line"> System.out.println(<span class="string">"Synchronize"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用 <code>javap -c Synchronize</code> 可以查看编译之后的具体信息。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">com</span>.<span class="title">crossoverjie</span>.<span class="title">synchronize</span>.<span class="title">Synchronize</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> com.crossoverjie.synchronize.Synchronize();</span><br><span class="line"> Code:</span><br><span class="line"> <span class="number">0</span>: aload_0</span><br><span class="line"> 1: invokespecial #1 // Method java/lang/Object."<init>":()V</span><br><span class="line"> <span class="number">4</span>: <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(java.lang.String[])</span></span>;</span><br><span class="line"> Code:</span><br><span class="line"> 0: ldc #2 // class com/crossoverjie/synchronize/Synchronize</span><br><span class="line"> <span class="number">2</span>: dup</span><br><span class="line"> <span class="number">3</span>: astore_1</span><br><span class="line"> **<span class="number">4</span>: monitorenter**</span><br><span class="line"> 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;</span><br><span class="line"> 8: ldc #4 // String Synchronize</span><br><span class="line"> 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V</span><br><span class="line"> <span class="number">13</span>: aload_1</span><br><span class="line"> **<span class="number">14</span>: monitorexit**</span><br><span class="line"> <span class="number">15</span>: goto <span class="number">23</span></span><br><span class="line"> <span class="number">18</span>: astore_2</span><br><span class="line"> <span class="number">19</span>: aload_1</span><br><span class="line"> <span class="number">20</span>: monitorexit</span><br><span class="line"> <span class="number">21</span>: aload_2</span><br><span class="line"> <span class="number">22</span>: athrow</span><br><span class="line"> <span class="number">23</span>: <span class="keyword">return</span></span><br><span class="line"> Exception table:</span><br><span class="line"> from to target type</span><br><span class="line"> <span class="number">5</span> <span class="number">15</span> <span class="number">18</span> any</span><br><span class="line"> <span class="number">18</span> <span class="number">21</span> <span class="number">18</span> any</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到在同步块的入口和出口分别有 <code>monitorenter,monitorexit</code><br>指令。</p><h2 id="锁优化"><a href="#锁优化" class="headerlink" title="锁优化"></a>锁优化</h2><p><code>synchronize</code> 很多都称之为重量锁,<code>JDK1.6</code> 中对 <code>synchronize</code> 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了<code>偏向锁</code>和<code>轻量锁</code>。</p><h3 id="轻量锁"><a href="#轻量锁" class="headerlink" title="轻量锁"></a>轻量锁</h3><p>当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(<code>Lock Record</code>)区域,同时将锁对象的对象头中 <code>Mark Word</code> 拷贝到锁记录中,再尝试使用 <code>CAS</code> 将 <code>Mark Word</code> 更新为指向锁记录的指针。</p><p>如果更新<strong>成功</strong>,当前线程就获得了锁。</p><p>如果更新<strong>失败</strong> <code>JVM</code> 会先检查锁对象的 <code>Mark Word</code> 是否指向当前线程的锁记录。</p><p>如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。</p><p>不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,<strong>轻量锁就会膨胀为重量锁</strong>。</p><h4 id="解锁"><a href="#解锁" class="headerlink" title="解锁"></a>解锁</h4><p>轻量锁的解锁过程也是利用 <code>CAS</code> 来实现的,会尝试锁记录替换回锁对象的 <code>Mark Word</code> 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为<code>重量锁</code>)</p><p>轻量锁能提升性能的原因是:</p><p>认为大多数锁在整个同步周期都不存在竞争,所以使用 <code>CAS</code> 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 <code>CAS</code> 的开销,甚至比重量锁更慢。</p><h3 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h3><p>为了进一步的降低获取锁的代价,<code>JDK1.6</code> 之后还引入了偏向锁。</p><p>偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。</p><p>当线程访问同步块时,会使用 <code>CAS</code> 将线程 ID 更新到锁对象的 <code>Mark Word</code> 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。</p><h4 id="释放锁-1"><a href="#释放锁-1" class="headerlink" title="释放锁"></a>释放锁</h4><p>当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 <code>Mark Word</code> 设置为无锁或者是轻量锁状态。</p><p>偏向锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 <code>-XX:-userBiasedLocking=false</code> 来关闭偏向锁,并默认进入轻量锁。</p><h3 id="其他优化"><a href="#其他优化" class="headerlink" title="其他优化"></a>其他优化</h3><h4 id="适应性自旋"><a href="#适应性自旋" class="headerlink" title="适应性自旋"></a>适应性自旋</h4><p>在使用 <code>CAS</code> 时,如果操作失败,<code>CAS</code> 会自旋再次尝试。由于自旋是需要消耗 <code>CPU</code> 资源的,所以如果长期自旋就白白浪费了 <code>CPU</code>。<code>JDK1.6</code>加入了适应性自旋:</p><blockquote><p>如果某个锁自旋很少成功获得,那么下一次就会减少自旋。</p></blockquote><h1 id="5-Lock与synchronized的区别"><a href="#5-Lock与synchronized的区别" class="headerlink" title="5. Lock与synchronized的区别"></a>5. Lock与synchronized的区别</h1><ol><li><p>Lock的加锁和解锁都是由java代码配合native方法(调用操作系统的相关方法)实现的,而synchronize的加锁和解锁的过程是由JVM管理的。</p></li><li><p>当一个线程使用synchronize获取锁时,若锁被其他线程占用着,那么当前只能被阻塞,直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式,在未能获取锁的条件下提供一种退出的机制。</p></li><li><p>一个锁内部可以有多个Condition实例,即有多路条件队列,而synchronize只有一路条件队列;同样Condition也提供灵活的阻塞方式,在未获得通知之前可以通过中断线程以 及设置等待时限等方式退出条件队列。</p></li><li><p>synchronize对线程的同步仅提供独占模式,而Lock即可以提供独占模式,也可以提供共享模式。</p></li></ol>]]></content>
<summary type="html">
<p><img src="/images/2018-07-05/smile.jpg" alt=""></p>
</summary>
<category term="高并发" scheme="http://www.guowenbo.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/"/>
<category term="ReentrantLock" scheme="http://www.guowenbo.top/tags/ReentrantLock/"/>
<category term="synchronize" scheme="http://www.guowenbo.top/tags/synchronize/"/>
<category term="重量锁" scheme="http://www.guowenbo.top/tags/%E9%87%8D%E9%87%8F%E9%94%81/"/>
<category term="轻量锁" scheme="http://www.guowenbo.top/tags/%E8%BD%BB%E9%87%8F%E9%94%81/"/>
<category term="偏向锁" scheme="http://www.guowenbo.top/tags/%E5%81%8F%E5%90%91%E9%94%81/"/>
</entry>
<entry>
<title>volatile 关键字</title>
<link href="http://www.guowenbo.top/2018/07/05/volatile-%E5%85%B3%E9%94%AE%E5%AD%97/"/>
<id>http://www.guowenbo.top/2018/07/05/volatile-关键字/</id>
<published>2018-07-05T03:11:39.000Z</published>
<updated>2019-01-03T09:43:41.573Z</updated>
<content type="html"><![CDATA[<p>不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能。</p><blockquote><p>内存可见性&禁止指令重排</p></blockquote><h2 id="内存可见性"><a href="#内存可见性" class="headerlink" title="内存可见性"></a>内存可见性</h2><p>由于 <code>Java</code> 内存模型(<code>JMM</code>)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。</p><p>线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。</p><blockquote><p>这里所提到的主内存可以简单认为是<strong>堆内存</strong>,而工作内存则可以认为是<strong>栈内存</strong>。</p></blockquote><a id="more"></a><p>如下图所示:</p><p><img src="/images/2018-07-05/volatile.jpg" alt=""></p><p>所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。</p><p>显然这肯定是会出问题的,因此 <code>volatile</code> 的作用出现了:</p><blockquote><p>当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。</p></blockquote><p><code>volatile</code> 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中。</p><p><strong><code>volatile</code> 保证了内存可见性,每个线程拿到的值都是最新值,并不能保证线程安全性!</strong></p><ul><li><p>所以想到达到线程安全可以使这三个线程串行执行(其实就是单线程,没有发挥多线程的优势)。</p></li><li><p>也可以使用 synchronize 或者是锁的方式来保证原子性。</p></li><li><p>还可以用 Atomic 包中 AtomicInteger 来替换 int,它利用了 CAS 算法来保证了原子性。</p></li></ul><h2 id="禁止指令重排序"><a href="#禁止指令重排序" class="headerlink" title="禁止指令重排序"></a>禁止指令重排序</h2><p>内存可见性只是 <code>volatile</code> 的其中一个语义,它还可以防止 <code>JVM</code> 进行指令重排优化。</p><p>举一个伪代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> a=<span class="number">10</span> ;<span class="comment">//1</span></span><br><span class="line"><span class="keyword">int</span> b=<span class="number">20</span> ;<span class="comment">//2</span></span><br><span class="line"><span class="keyword">int</span> c= a+b ;<span class="comment">//3</span></span><br></pre></td></tr></table></figure></p><p>一段特别简单的代码,理想情况下它的执行顺序是:<code>1>2>3</code>。但有可能经过 JVM 优化之后的执行顺序变为了 <code>2>1>3</code>。</p><p>可以发现不管 JVM 怎么优化,前提都是保证单线程中最终结果不变的情况下进行的。</p><p>可能这里还看不出有什么问题,那看下一段伪代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Map<String,String> value ;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> <span class="keyword">boolean</span> flag = fasle ;</span><br><span class="line"></span><br><span class="line"><span class="comment">//以下方法发生在线程 A 中 初始化 Map</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">initMap</span><span class="params">()</span></span>{</span><br><span class="line"><span class="comment">//耗时操作</span></span><br><span class="line">value = getMapValue() ;<span class="comment">//1</span></span><br><span class="line">flag = <span class="keyword">true</span> ;<span class="comment">//2</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//发生在线程 B中 等到 Map 初始化成功进行其他操作</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doSomeThing</span><span class="params">()</span></span>{</span><br><span class="line"><span class="keyword">while</span>(!flag){</span><br><span class="line">sleep() ;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//dosomething</span></span><br><span class="line">doSomeThing(value);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里就能看出问题了,当 <code>flag</code> 没有被 <code>volatile</code> 修饰时,<code>JVM</code> 对 1 和 2 进行重排,导致 <code>value</code> 都还没有被初始化就有可能被线程 B 使用了。</p><p>所以加上 <code>volatile</code> 之后可以防止这样的重排优化,保证业务的正确性。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>volatile</code> 在 <code>Java</code> 并发中用的很多,比如像 <code>Atomic</code> 包中的 <code>value</code>、以及 <code>AbstractQueuedLongSynchronizer</code> 中的 <code>state</code> 都是被定义为 <code>volatile</code> 来用于保证内存可见性。</p>]]></content>
<summary type="html">
<p>不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能。</p>
<blockquote>
<p>内存可见性&amp;禁止指令重排</p>
</blockquote>
<h2 id="内存可见性"><a href="#内存可见性" class="headerlink" title="内存可见性"></a>内存可见性</h2><p>由于 <code>Java</code> 内存模型(<code>JMM</code>)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。</p>
<p>线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。</p>
<blockquote>
<p>这里所提到的主内存可以简单认为是<strong>堆内存</strong>,而工作内存则可以认为是<strong>栈内存</strong>。</p>
</blockquote>
</summary>
<category term="高并发" scheme="http://www.guowenbo.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/"/>
<category term="volatile" scheme="http://www.guowenbo.top/tags/volatile/"/>
</entry>
<entry>
<title>ConcurrentHashMap原理</title>
<link href="http://www.guowenbo.top/2018/07/05/ConcurrentHashMap%E5%8E%9F%E7%90%86/"/>
<id>http://www.guowenbo.top/2018/07/05/ConcurrentHashMap原理/</id>
<published>2018-07-05T02:47:19.000Z</published>
<updated>2019-01-03T09:43:41.557Z</updated>
<content type="html"><![CDATA[<p>由于 <code>HashMap</code> 是一个线程不安全的容器,主要体现并发情况下在size大于<code>容量*负载因子</code>发生扩容时,取一个不存在的 <code>key</code> 时,计算出的 <code>index</code> 正好是环形链表的下标会出现环形链表从而导致死循环。<br>因此需要支持线程安全的并发容器 <code>ConcurrentHashMap</code> 。</p><blockquote><p>所以 <code>HashMap</code> 只能在单线程中使用,并且尽量的预设容量,尽可能的减少扩容。<br>在 <code>JDK1.8</code> 中对 <code>HashMap</code> 进行了优化: 当 <code>hash</code> 碰撞之后写入链表的长度超过了阈值(默认为8),链表将会转换为红黑树。<br>假设 <code>hash</code> 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 <code>O(n)</code> 。<br>如果是红黑树,时间复杂度就是 <code>O(logn)</code> 。<br>大大提高了查询效率。</p></blockquote><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p><img src="/images/2018-07-05/concurrentHashMap.jpg" alt=""></p><a id="more"></a><p>如图所示,是由 <code>Segment</code> 数组、<code>HashEntry</code> 数组组成,和 <code>HashMap</code> 一样,仍然是数组加链表组成。</p><p><code>ConcurrentHashMap</code> 采用了分段锁技术,其中 <code>Segment</code> 继承于 <code>ReentrantLock</code>。不会像 <code>HashTable</code> 那样不管是 <code>put</code> 还是 <code>get</code> 操作都需要做同步处理,理论上 <code>ConcurrentHashMap</code> 支持 <code>CurrencyLevel (Segment 数组数量)</code>的线程并发。每当一个线程占用锁访问一个 <code>Segment</code> 时,不会影响到其他的 <code>Segment</code>。</p><h2 id="GET方法"><a href="#GET方法" class="headerlink" title="GET方法"></a>GET方法</h2><p><code>ConcurrentHashMap</code> 的 <code>get</code> 方法是非常高效的,因为整个过程都不需要加锁。</p><p>只需要将 <code>Key</code> 通过 <code>Hash</code> 之后定位到具体的 <code>Segment</code> ,再通过一次 <code>Hash</code> 定位到具体的元素上。由于 <code>HashEntry</code> 中的 <code>value</code> 属性是用 <code>volatile</code> 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。</p><h2 id="PUT方法"><a href="#PUT方法" class="headerlink" title="PUT方法"></a>PUT方法</h2><p>内部 <code>HashEntry</code> 类 :<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">HashEntry</span><<span class="title">K</span>,<span class="title">V</span>> </span>{</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> hash;</span><br><span class="line"> <span class="keyword">final</span> K key;</span><br><span class="line"> <span class="keyword">volatile</span> V value;</span><br><span class="line"> <span class="keyword">volatile</span> HashEntry<K,V> next;</span><br><span class="line"></span><br><span class="line"> HashEntry(<span class="keyword">int</span> hash, K key, V value, HashEntry<K,V> next) {</span><br><span class="line"> <span class="keyword">this</span>.hash = hash;</span><br><span class="line"> <span class="keyword">this</span>.key = key;</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> <span class="keyword">this</span>.next = next;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>虽然 <code>HashEntry</code> 中的 <code>value</code> 是用 <code>volatile</code> 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。</p><p>首先也是通过 Key 的 Hash 定位到具体的 Segment,在 put 之前会进行一次扩容校验。这里比 HashMap 要好的一点是:HashMap 是插入元素之后再看是否需要扩容,有可能扩容之后后续就没有插入就浪费了本次扩容(扩容非常消耗性能)。</p><p>而 ConcurrentHashMap 不一样,它是先将数据插入之后再检查是否需要扩容,之后再做插入。</p><h2 id="SIZE方法"><a href="#SIZE方法" class="headerlink" title="SIZE方法"></a>SIZE方法</h2><p>每个 <code>Segment</code> 都有一个 <code>volatile</code> 修饰的全局变量 <code>count</code> ,求整个 <code>ConcurrentHashMap</code> 的 <code>size</code> 时很明显就是将所有的 <code>count</code> 累加即可。但是 <code>volatile</code>修饰的变量却不能保证多线程的原子性,所有直接累加很容易出现并发问题。</p><p>但如果每次调用 size 方法将其余的修改操作加锁效率也很低。所以做法是先尝试两次将 <code>count</code> 累加,如果容器的 <code>count</code> 发生了变化再加锁来统计 <code>size</code>。</p><p>至于 <code>ConcurrentHashMap</code> 是如何知道在统计时大小发生了变化呢,每个 <code>Segment</code> 都有一个 <code>modCount</code> 变量,每当进行一次 <code>put remove</code> 等操作,<code>modCount</code> 将会 +1。只要 <code>modCount</code>发生了变化就认为容器的大小也在发生变化。</p>]]></content>
<summary type="html">
<p>由于 <code>HashMap</code> 是一个线程不安全的容器,主要体现并发情况下在size大于<code>容量*负载因子</code>发生扩容时,取一个不存在的 <code>key</code> 时,计算出的 <code>index</code> 正好是环形链表的下标会出现环形链表从而导致死循环。<br>因此需要支持线程安全的并发容器 <code>ConcurrentHashMap</code> 。</p>
<blockquote>
<p>所以 <code>HashMap</code> 只能在单线程中使用,并且尽量的预设容量,尽可能的减少扩容。<br>在 <code>JDK1.8</code> 中对 <code>HashMap</code> 进行了优化: 当 <code>hash</code> 碰撞之后写入链表的长度超过了阈值(默认为8),链表将会转换为红黑树。<br>假设 <code>hash</code> 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 <code>O(n)</code> 。<br>如果是红黑树,时间复杂度就是 <code>O(logn)</code> 。<br>大大提高了查询效率。</p>
</blockquote>
<h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p><img src="/images/2018-07-05/concurrentHashMap.jpg" alt=""></p>
</summary>
<category term="高并发" scheme="http://www.guowenbo.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/"/>
<category term="ConcurrentHashMap" scheme="http://www.guowenbo.top/tags/ConcurrentHashMap/"/>
<category term="HashMap" scheme="http://www.guowenbo.top/tags/HashMap/"/>
</entry>
<entry>
<title>jvm 5.jvm知识点总结</title>
<link href="http://www.guowenbo.top/2018/07/03/jvm%205.jvm%E7%9F%A5%E8%AF%86%E7%82%B9%E6%80%BB%E7%BB%93/"/>
<id>http://www.guowenbo.top/2018/07/03/jvm 5.jvm知识点总结/</id>
<published>2018-07-03T07:00:38.000Z</published>
<updated>2019-01-03T09:43:41.571Z</updated>
<content type="html"><![CDATA[<h1 id="总体梳理"><a href="#总体梳理" class="headerlink" title="总体梳理"></a>总体梳理</h1><p><strong>jvm体系总体分四大块:</strong></p><ul><li>类的加载机制</li><li>jvm内存结构</li><li>GC算法 垃圾回收</li><li>GC分析 命令调优</li></ul><a id="more"></a><h2 id="类的加载机制"><a href="#类的加载机制" class="headerlink" title="类的加载机制"></a>类的加载机制</h2><p>关注点</p><ul><li>什么是类的加载</li><li>类的生命周期</li><li>类加载器</li><li>双亲委派模型</li></ul><h3 id="什么是类的加载"><a href="#什么是类的加载" class="headerlink" title="什么是类的加载"></a>什么是类的加载</h3><p>类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。</p><h3 id="类的生命周期"><a href="#类的生命周期" class="headerlink" title="类的生命周期"></a>类的生命周期</h3><p>类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图;</p><p><img src="/images/2018-06-27/jvm_1_2.png" alt=""></p><ul><li>加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象</li><li>连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用</li><li>初始化,为类的静态变量赋予正确的初始值</li><li>使用,new出对象程序中使用</li><li>卸载,执行垃圾回收</li></ul><p><strong><em>1、JVM初始化步骤 ? 2、类初始化时机 ?3、哪几种情况下,Java虚拟机将结束生命周期?<br>答案:<a href="/2018/06/27/jvm%201.类的加载机制/">jvm 1.类的加载机制</a></em></strong></p><h3 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h3><p><img src="/images/2018-06-27/jvm_1_3.png" alt=""></p><ul><li>启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库</li><li>扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。</li><li>应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器</li></ul><h3 id="类加载机制"><a href="#类加载机制" class="headerlink" title="类加载机制"></a>类加载机制</h3><ul><li>全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入</li><li>父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类</li><li>缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效</li></ul><h2 id="jvm内存结构"><a href="#jvm内存结构" class="headerlink" title="jvm内存结构"></a>jvm内存结构</h2><p>关注点</p><ul><li>jvm内存结构都是什么</li><li>对象的创建与内存分配</li></ul><h3 id="jvm内存结构都是什么"><a href="#jvm内存结构都是什么" class="headerlink" title="jvm内存结构都是什么"></a>jvm内存结构都是什么</h3><p><img src="/images/2018-06-29/jvm_2_3.png" alt=""></p><blockquote><p>方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。</p></blockquote><ul><li>Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。<ol><li>年轻代(Eden,form survivor,to survivio 默认8:1:1)</li><li>老年代</li></ol></li><li>方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。</li><li>程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。</li><li>JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。</li><li>本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。</li></ul><h3 id="对象的创建与内存分配"><a href="#对象的创建与内存分配" class="headerlink" title="对象的创建与内存分配"></a>对象的创建与内存分配</h3><h4 id="创建对象"><a href="#创建对象" class="headerlink" title="创建对象"></a>创建对象</h4><p>当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载。</p><p>接着就是分配内存了,通常有两种方式:</p><ol><li><strong>指针碰撞</strong>:使用指针碰撞的前提是堆内存是<strong>完全工整</strong>的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。</li><li><strong>空闲列表</strong>:当堆中已经使用的内存和未使用的内存<strong>互相交错</strong>时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式。虚拟机会维护一个空闲的列表,用于记录哪些内存是可以进行分配的,分配时直接从可用内存中直接分配即可。</li></ol><p>堆中的内存是否工整是有<strong>垃圾收集器</strong>来决定的,如果带有压缩功能的垃圾收集器就是采用指针碰撞的方式来进行内存分配的。</p><h4 id="内存分配"><a href="#内存分配" class="headerlink" title="内存分配"></a>内存分配</h4><p><strong>Eden 区分配</strong></p><p>简单的来说对象都是在堆内存中分配的,往细一点看则是优先在 Eden 区分配。<br>这里就涉及到堆内存的划分了,为了方便垃圾回收,JVM 将对内存分为新生代和老年代。<br>而新生代中又会划分为 Eden 区,from Survivor、to Survivor 区。<br>其中 Eden 和 Survivor 区的比例默认是 8:1:1,当然也支持参数调整 -XX:SurvivorRatio=8。<br>当在 Eden 区分配内存不足时,则会发生 minorGC ,由于 Java 对象多数是朝生夕灭的特性,所以 minorGC 通常会比较频繁,效率也比较高。<br>当发生 minorGC 时,JVM 会根据复制算法将存活的对象拷贝到另一个未使用的 Survivor 区,如果 Survivor 区内存不足时,则会使用分配担保策略将对象移动到老年代中。<br>谈到 minorGC 时,就不得不提到 fullGC(majorGC) ,这是指发生在老年代的 GC ,不论是效率还是速度都比 minorGC 慢的多,回收时还会发生 stop the world 使程序发生停顿,所以应当尽量避免发生 fullGC 。</p><p><strong>老年代分配</strong></p><p>也有一些情况会导致对象直接在老年代分配,比如当分配一个大对象时(大的数组,很长的字符串),由于 Eden 区没有足够大的连续空间来分配时,会导致提前触发一次 GC,所以尽量别频繁的创建大对象。<br>因此 JVM 会根据一个阈值来判断大于该阈值对象直接分配到老年代,这样可以避免在新生代频繁的发生 GC。<br>对于一些在新生代的老对象 JVM 也会根据某种机制移动到老年代中。<br>JVM 是根据记录对象年龄的方式来判断该对象是否应该移动到老年代,根据新生代的复制算法,当一个对象被移动到 Survivor 区之后 JVM 就给该对象的年龄记为1,每当熬过一次 minorGC 后对象的年龄就 +1 ,直到达到阈值(默认为15)就移动到老年代中。</p><blockquote><p>可以使用 -XX:MaxTenuringThreshold=15 来配置这个阈值。</p></blockquote><p>精简一下:</p><ul><li>对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。</li><li>大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。</li><li>长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。</li><li>动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。</li><li>空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。 </li></ul><p><strong><em>详情:<a href="/2018/06/29/jvm%202.内存结构/">jvm 2.内存结构</a></em></strong></p><h2 id="GC算法-垃圾回收"><a href="#GC算法-垃圾回收" class="headerlink" title="GC算法 垃圾回收"></a>GC算法 垃圾回收</h2><p>关注点</p><ul><li>对象存活判断</li><li>GC算法</li><li>垃圾回收器</li></ul><h3 id="对象存活判断"><a href="#对象存活判断" class="headerlink" title="对象存活判断"></a>对象存活判断</h3><p>两种:</p><ol><li><strong>引用计数</strong>:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。</li><li><strong>可达性分析</strong>:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。<ul><li>GC ROOTS<ol><li>虚拟机栈引用对象</li><li>本地方法栈引用对象</li><li>方法区常量引用对象</li><li>方法区静态属性引用对象</li></ol></li></ul></li></ol><h3 id="GC算法"><a href="#GC算法" class="headerlink" title="GC算法"></a>GC算法</h3><p>GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。</p><ul><li>标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。</li><li>复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。</li><li>标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存</li><li>分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。<ol><li>新生代中存活对象较少,”朝生夕死”,所以采用复制算法,简单高效</li><li>老年代中对象较多,并且没有可以担保的内存区域,所以一般采用标记清除或者是标记整理算法。</li></ol></li></ul><h3 id="垃圾回收器"><a href="#垃圾回收器" class="headerlink" title="垃圾回收器"></a>垃圾回收器</h3><ul><li>Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。</li><li>ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。</li><li>Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。</li><li>Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法</li><li>CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。</li><li>G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征</li></ul><p><strong><em>详情:<a href="/2018/07/02/jvm%203.GC算法-垃圾收集器/">jvm 3.GC算法-垃圾收集器</a></em></strong></p><h2 id="GC分析-命令调优"><a href="#GC分析-命令调优" class="headerlink" title="GC分析 命令调优"></a>GC分析 命令调优</h2><p>关注点:</p><ul><li>GC日志分析</li><li>调优命令</li><li>调优工具</li></ul><h3 id="GC日志分析"><a href="#GC日志分析" class="headerlink" title="GC日志分析"></a>GC日志分析</h3><p>摘录GC日志一部分(前部分为年轻代gc回收;后部分为full gc回收):<br><figure class="highlight bash"><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">2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] </span><br><span class="line">2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]</span><br></pre></td></tr></table></figure></p><p>通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收前后年轻代的内存变化;ParOldGen表示gc回收前后老年代的内存变化;PSPermGen表示gc回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数</p><p>young gc 日志:<br><img src="/images/2018-07-03/jvm_5_1.jpg" alt=""></p><p>Full GC日志:<br><img src="/images/2018-07-03/jvm_5_2.jpg" alt=""></p><h3 id="调优命令"><a href="#调优命令" class="headerlink" title="调优命令"></a>调优命令</h3><p>Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo</p><ul><li><strong>jps</strong><br> jps命令用于查询正在运行的JVM进程,常用的参数为:<br> -q:只输出LVMID,省略主类的名称<br> -m:输出虚拟机进程启动时传给主类main()函数的参数<br> -l:输出主类的全类名,如果进程执行的是Jar包,输出Jar路径<br> -v:输出虚拟机进程启动时JVM参数<br> 命令格式:jps [option] [hostid] </li><li><strong>jstat</strong><br> jstat可以实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据(如果要显示远程JVM信息,需要远程主机开启RMI支持)。如果在服务启动时没有指定启动参数-verbose:gc,则可以用jstat实时查看gc情况。<br> jstat有如下选项:<br> -class:监视类装载、卸载数量、总空间及类装载所耗费的时间<br> -gc:监听Java堆状况,包括Eden区、两个Survivor区、老年代、永久代等的容量,以用空间、GC时间合计等信息<br> -gccapacity:监视内容与-gc基本相同,但输出主要关注java堆各个区域使用到的最大和最小空间<br> -gcutil:监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比<br> -gccause:与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因<br> -gcnew:监视新生代GC状况<br> -gcnewcapacity:监视内同与-gcnew基本相同,输出主要关注使用到的最大和最小空间<br> -gcold:监视老年代GC情况<br> -gcoldcapacity:监视内同与-gcold基本相同,输出主要关注使用到的最大和最小空间<br> -gcpermcapacity:输出永久代使用到最大和最小空间<br> -compiler:输出JIT编译器编译过的方法、耗时等信息<br> -printcompilation:输出已经被JIT编译的方法<br> 命令格式:jstat [option vmid [interval[s|ms] [count]]]</li><li><strong>jmap</strong><br> 用于显示当前Java堆和永久代的详细信息(如当前使用的收集器,当前的空间使用率等)<br> -dump:生成java堆转储快照<br> -heap:显示java堆详细信息(只在Linux/Solaris下有效)<br> -F:当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照(只在Linux/Solaris下有效)<br> -finalizerinfo:显示在F-Queue中等待Finalizer线程执行finalize方法的对象(只在Linux/Solaris下有效)<br> -histo:显示堆中对象统计信息<br> -permstat:以ClassLoader为统计口径显示永久代内存状态(只在Linux/Solaris下有效)<br> 命令格式:jmap [option] vmid<br> 其中前面3个参数最重要,如:<br> 查看对详细信息:sudo jmap -heap 309<br> 生成dump文件: sudo jmap -dump:file=./test.prof 309<br> 部分用户没有权限时,采用admin用户:sudo -u admin -H jmap -dump:format=b,file=文件名.hprof pid<br> 查看当前堆中对象统计信息:sudo jmap -histo 309:该命令显示3列,分别为对象数量,对象大小,对象名称,通过该命令可以查看是否内存中有大对象;<br> 有的用户可能没有jmap权限:sudo -u admin -H jmap -histo 309 | less</li><li><strong>jhat</strong><br> 用于分析使用jmap生成的dump文件,是JDK自带的工具,使用方法为: jhat -J -Xmx512m [file]<br> 不过jhat没有mat好用,推荐使用mat(Eclipse插件: <a href="http://www.eclipse.org/mat" target="_blank" rel="noopener">http://www.eclipse.org/mat</a> ),mat速度更快,而且是图形界面</li><li><strong>jstack</strong><br> 用于生成当前JVM的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因。<br> -F:当正常输出的请求不被响应时,强制输出线程堆栈<br> -l:除堆栈外,显示关于锁的附加信息<br> -m:如果调用到本地方法的话,可以显示C/C++的堆栈<br> 命令格式:jstack [option] vmid</li><li><strong>jinfo</strong><br> 用于查询当前运行这的JVM属性和参数的值。<br> jinfo可以使用如下选项:<br> -flag:显示未被显示指定的参数的系统默认值<br> -flag [+|-]name或-flag name=value: 修改部分参数<br> -sysprops:打印虚拟机进程的System.getProperties()<br> 命令格式:jinfo [option] pid </li></ul><h3 id="调优工具"><a href="#调优工具" class="headerlink" title="调优工具"></a>调优工具</h3><p>常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。</p><ul><li>jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控</li><li>jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。</li><li>MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗</li><li>GChisto,一款专业分析gc日志的工具<br>有兴趣的可以看看下面的文章,在此不再赘述:<br><a href="http://blog.csdn.net/java2000_wl/article/details/8049707" target="_blank" rel="noopener">http://blog.csdn.net/java2000_wl/article/details/8049707</a></li></ul><p><strong><em>详情:<a href="/2018/07/03/jvm%204.如何优化Java-GC「译」/">jvm 4.如何优化Java-GC「译」</a></em></strong></p>]]></content>
<summary type="html">
<h1 id="总体梳理"><a href="#总体梳理" class="headerlink" title="总体梳理"></a>总体梳理</h1><p><strong>jvm体系总体分四大块:</strong></p>
<ul>
<li>类的加载机制</li>
<li>jvm内存结构</li>
<li>GC算法 垃圾回收</li>
<li>GC分析 命令调优</li>
</ul>
</summary>
<category term="jvm" scheme="http://www.guowenbo.top/categories/jvm/"/>
<category term="jvm" scheme="http://www.guowenbo.top/tags/jvm/"/>
</entry>
<entry>
<title>jvm 4.如何优化Java GC「译」</title>
<link href="http://www.guowenbo.top/2018/07/03/jvm%204.%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96Java-GC%E3%80%8C%E8%AF%91%E3%80%8D/"/>
<id>http://www.guowenbo.top/2018/07/03/jvm 4.如何优化Java-GC「译」/</id>
<published>2018-07-03T02:57:28.000Z</published>
<updated>2019-01-03T09:43:41.571Z</updated>
<content type="html"><![CDATA[<blockquote><p>本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作。</p></blockquote><blockquote><p>Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章的第三篇《How to Tune Java Garbage Collection》,本文的作者是韩国人,写在JDK 1.8发布之前,虽然有些地方有些许过时,但整体内容还是非常有价值的。译者此前也看到有人翻译了本文,发现其中有许多错漏生硬和语焉不详之处,因此决定自己翻译一份,供大家分享。</p></blockquote><p>本文是“成为Java GC专家”系列文章的第三篇,在系列的第一篇文章《理解Java GC》中,我们了解到了不同GC算法的执行过程、GC的工作原理、新生代和老年代的概念、JDK 7中你需要了解的5种GC类型以及每一种GC对性能的影响。</p><p>在系列的第二篇文章《如何监控Java GC》中笔者已经解释了JVM进行实时GC的原理、监控GC的方法以及可以使这一过程更加迅速高效的工具。</p><p>在第三篇文章中,笔者将基于实际生产环境中的案例,介绍几个GC优化的最佳参数设置。在此我们假设你已经理解了本系列前两篇文章的内容,因此为了更深入的理解本文所讲内容,我建议你在阅读本篇文章之前先仔细阅读这两篇文章。</p><h2 id="GC优化是必要的吗?"><a href="#GC优化是必要的吗?" class="headerlink" title="GC优化是必要的吗?"></a>GC优化是必要的吗?</h2><p>或者更准确地说,GC优化对Java基础服务来说是必要的吗?答案是否定的,事实上GC优化对Java基础服务来说在有些场合是可以省去的,但前提是这些正在运行的Java系统,必须包含以下参数或行为:</p><ul><li>内存大小已经通过<strong>-Xms和-Xmx</strong>参数指定过</li><li>运行在server模式下(使用<strong>-server</strong>参数)</li><li>系统中没有残留超时日志之类的错误日志</li></ul><p>换句话说,如果你在运行时没有手动设置内存大小并且打印出了过多的超时日志,那你就需要对系统进行GC优化。</p><p>不过你需要时刻谨记一句话:<strong>GC tuning is the last task to be done.</strong></p><a id="more"></a><p>现在来想一想GC优化的最根本原因,垃圾收集器的工作就是清除Java创建的对象,垃圾收集器需要清理的对象数量以及要执行的GC数量均取决于已创建的对象数量。因此,为了使你的系统在GC上表现良好,首先需要减少创建对象的数量。</p><p>俗话说“冰冻三尺非一日之寒”,我们在编码时要首先要把下面这些小细节做好,否则一些琐碎的不良代码累积起来将让GC的工作变得繁重而难于管理:</p><ul><li>使用<code>StringBuilder</code>或<code>StringBuffer</code>来代替<code>String</code></li><li>尽量少输出日志</li></ul><p>尽管如此,仍然会有我们束手无策的情况。XML和JSON解析过程往往占用了最多的内存,即使我们已经尽可能地少用String、少输出日志,仍然会有大量的临时内存(大约10-100MB)被用来解析XML或JSON文件,但我们又很难弃用XML和JSON。在此,你只需要知道这一过程会占据大量内存即可。</p><p>如果在经过几次重复的优化后应用程序的内存用量情况有所改善,那么久可以启动GC优化了。</p><p>笔者总结了GC优化的两个目的:</p><ol><li><strong>将进入老年代的对象数量降到最低</strong></li><li><strong>减少Full GC的执行时间</strong></li></ol><h2 id="将进入老年代的对象数量降到最低"><a href="#将进入老年代的对象数量降到最低" class="headerlink" title="将进入老年代的对象数量降到最低"></a>将进入老年代的对象数量降到最低</h2><p>除了可以在JDK 7及更高版本中使用的G1收集器以外,其他分代GC都是由Oracle JVM提供的。关于分代GC,就是对象在Eden区被创建,随后被转移到Survivor区,在此之后剩余的对象会被转入老年代。也有一些对象由于占用内存过大,在Eden区被创建后会直接被传入老年代。老年代GC相对来说会比新生代GC更耗时,因此,减少进入老年代的对象数量可以显著降低Full GC的频率。你可能会以为减少进入老年代的对象数量意味着把它们留在新生代,事实正好相反,新生代内存的大小是可以调节的。</p><h2 id="降低Full-GC的时间"><a href="#降低Full-GC的时间" class="headerlink" title="降低Full GC的时间"></a>降低Full GC的时间</h2><p>Full GC的执行时间比Minor GC要长很多,因此,如果在Full GC上花费过多的时间(超过1s),将可能出现超时错误。</p><ul><li>如果<strong>通过减小老年代内存来减少Full GC时间</strong>,可能会引起<code>OutOfMemoryError</code>或者导致Full GC的频率升高。</li><li>另外,如果<strong>通过增加老年代内存来降低Full GC的频率</strong>,Full GC的时间可能因此增加。</li></ul><p>因此,<strong>你需要把老年代的大小设置成一个“合适”的值</strong>。</p><h2 id="影响GC性能的参数"><a href="#影响GC性能的参数" class="headerlink" title="影响GC性能的参数"></a>影响GC性能的参数</h2><p>正如我在系列的第一篇文章《理解Java GC》末尾提到的,不要幻想着“如果有人用他设置的GC参数获取了不错的性能,我们为什么不复制他的参数设置呢?”,因为对于不用的Web服务,它们创建的对象大小和生命周期都不相同。</p><p>举一个简单的例子,如果一个任务的执行条件是A,B,C,D和E,另一个完全相同的任务执行条件只有A和B,那么哪一个任务执行速度更快呢?作为常识来讲,答案很明显是后者。</p><p>Java GC参数的设置也是这个道理,设置好几个参数并不会提升GC执行的速度,反而会使它变得更慢。<strong>GC优化的基本原则</strong>是将不同的GC参数应用到两个及以上的服务器上然后比较它们的性能,然后将那些被证明可以提高性能或减少GC执行时间的参数应用于最终的工作服务器上。</p><p>下面这张表展示了与内存大小相关且会影响GC性能的GC参数</p><p><strong>表1:GC优化需要考虑的JVM参数</strong></p><table><thead><tr><th>类型</th><th style="text-align:center">参数</th><th>描述</th></tr></thead><tbody><tr><td>堆内存大小</td><td style="text-align:center"><code>-Xms</code></td><td>启动JVM时堆内存的大小</td></tr><tr><td></td><td style="text-align:center"><code>-Xmx</code></td><td>堆内存最大限制</td></tr><tr><td>新生代空间大小</td><td style="text-align:center"><code>-XX:NewRatio</code></td><td>新生代和老年代的内存比</td></tr><tr><td></td><td style="text-align:center"><code>-XX:NewSize</code></td><td>新生代内存大小</td></tr><tr><td></td><td style="text-align:center"><code>-XX:SurvivorRatio</code></td><td>Eden区和Survivor区的内存比</td></tr></tbody></table><p>笔者在进行GC优化时最常用的参数是<code>-Xms</code>,<code>-Xmx</code>和<code>-XX:NewRatio</code>。<code>-Xms</code>和<code>-Xmx</code>参数通常是必须的,所以<code>NewRatio</code>的值将对GC性能产生重要的影响。</p><p>有些人可能会问<strong>如何设置永久代内存大小</strong>,你可以用<code>-XX:PermSize</code>和<code>-XX:MaxPermSize</code>参数来进行设置,但是要记住,只有当出现<code>OutOfMemoryError</code>错误时你才需要去设置永久代内存。</p><p>还有一个会影响GC性能的因素是<a href="https://crowhawk.github.io/2017/08/15/jvm_3/" target="_blank" rel="noopener">垃圾收集器的类型</a>,下表展示了关于GC类型的可选参数(基于JDK 6.0):</p><p><strong>表2:GC类型可选参数</strong></p><table><thead><tr><th>GC类型</th><th style="text-align:center">参数</th><th>备注</th></tr></thead><tbody><tr><td>Serial GC</td><td style="text-align:center">-XX:+UseSerialGC</td><td></td></tr><tr><td>Parallel GC</td><td style="text-align:center">-XX:+UseParallelGC<br>-XX:ParallelGCThreads=value</td><td></td></tr><tr><td>Parallel Compacting GC</td><td style="text-align:center">-XX:+UseParallelOldGC</td><td></td></tr><tr><td>CMS GC</td><td style="text-align:center">-XX:+UseConcMarkSweepGC<br>-XX:+UseParNewGC<br>-XX:+CMSParallelRemarkEnabled<br>-XX:CMSInitiatingOccupancyFraction=value<br>-XX:+UseCMSInitiatingOccupancyOnly</td><td></td></tr><tr><td>G1</td><td style="text-align:center">-XX:+UnlockExperimentalVMOptions<br>-XX:+UseG1GC</td><td>在JDK 6中这两个参数必须配合使用</td></tr></tbody></table><p>除了G1收集器外,可以通过设置上表中每种类型第一行的参数来切换GC类型,最常见的非侵入式GC就是Serial GC,它针对客户端系统进行了特别的优化。</p><p>会影响GC性能的参数还有很多,但是上述的参数会带来最显著的效果,请切记,设置太多的参数并不一定会提升GC的性能。</p><h2 id="GC优化的过程"><a href="#GC优化的过程" class="headerlink" title="GC优化的过程"></a>GC优化的过程</h2><p>GC优化的过程和大多数常见的提升性能的过程相似,下面是笔者使用的流程:</p><h3 id="1-监控GC状态"><a href="#1-监控GC状态" class="headerlink" title="1.监控GC状态"></a>1.监控GC状态</h3><p>你需要监控GC从而检查系统中运行的GC的各种状态,具体方法请查看系列的第二篇文章<a href="http://www.cubrid.org/blog/how-to-monitor-java-garbage-collection" target="_blank" rel="noopener">《如何监控Java GC》</a></p><h3 id="2-分析监控结果后决定是否需要优化GC"><a href="#2-分析监控结果后决定是否需要优化GC" class="headerlink" title="2.分析监控结果后决定是否需要优化GC"></a>2.分析监控结果后决定是否需要优化GC</h3><p>在检查GC状态后,你需要分析监控结构并决定是否需要进行GC优化。如果分析结果显示运行GC的时间只有0.1-0.3秒,那么就不需要把时间浪费在GC优化上,但如果运行GC的时间达到1-3秒,甚至大于10秒,那么GC优化将是很有必要的。</p><p>但是,如果你已经分配了大约10GB内存给Java,并且这些内存无法省下,那么就无法进行GC优化了。在进行GC优化之前,你需要考虑为什么你需要分配这么大的内存空间,如果你分配了1GB或2GB大小的内存并且出现了<code>OutOfMemoryError</code>,那你就应该执行<strong>堆转储(heap dump)</strong>来消除导致异常的原因。</p><blockquote><p>注意:</p></blockquote><blockquote><p><strong>堆转储(heap dump)</strong>是一个用来检查Java内存中的对象和数据的内存文件。该文件可以通过执行JDK中的<code>jmap</code>命令来创建。在创建文件的过程中,所有Java程序都将暂停,因此,不要再系统执行过程中创建该文件。</p></blockquote><blockquote><p>你可以在互联网上搜索heap dump的详细说明。对于韩国读者,可以直接参考我去年发布的书:<a href="http://book.naver.com/bookdb/book_detail.nhn?bid=6654751" target="_blank" rel="noopener">《The story of troubleshooting for Java developers and system operators》</a> (Sangmin Lee, Hanbit Media, 2011, 416 pages)</p></blockquote><h3 id="3-设置GC类型-内存大小"><a href="#3-设置GC类型-内存大小" class="headerlink" title="3.设置GC类型/内存大小"></a>3.设置GC类型/内存大小</h3><p>如果你决定要进行GC优化,那么你需要选择一个GC类型并且为它设置内存大小。此时如果你有多个服务器,请如上文提到的那样,在每台机器上设置不同的GC参数并分析它们的区别。</p><h3 id="4-分析结果"><a href="#4-分析结果" class="headerlink" title="4.分析结果"></a>4.分析结果</h3><p>在设置完GC参数后就可以开始收集数据,请在收集至少24小时后再进行结果分析。如果你足够幸运,你可能会找到系统的最佳GC参数。如若不然,你还需要分析输出日志并检查分配的内存,然后需要通过不断调整GC类型/内存大小来找到系统的最佳参数。</p><h3 id="5-如果结果令人满意,将参数应用到所有服务器上并结束GC优化"><a href="#5-如果结果令人满意,将参数应用到所有服务器上并结束GC优化" class="headerlink" title="5.如果结果令人满意,将参数应用到所有服务器上并结束GC优化"></a>5.如果结果令人满意,将参数应用到所有服务器上并结束GC优化</h3><p>如果GC优化的结果令人满意,就可以将参数应用到所有服务器上,并停止GC优化。</p><p>在下面的章节中,你将会看到上述每一步所做的具体工作。</p><h2 id="监控GC状态并分析结果"><a href="#监控GC状态并分析结果" class="headerlink" title="监控GC状态并分析结果"></a>监控GC状态并分析结果</h2><p>在运行中的Web应用服务器(Web Application Server,WAS)上查看GC状态的最佳方式就是使用<code>jstat</code>命令。笔者在<a href="http://www.cubrid.org/blog/how-to-monitor-java-garbage-collection" target="_blank" rel="noopener">《如何监控Java GC》</a>中已经介绍过了<code>jstat</code>命令,所以在本篇文章中我将着重关注数据部分。</p><p>下面的例子展示了某个还没有执行GC优化的JVM的状态(虽然它并不是运行服务器)。</p><pre><code>$ jstat -gcutil 21719 1sS0 S1 E O P YGC YGCT FGC FGCT GCT48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.67348.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673</code></pre><p>我们先看一下YGC(从应用程序启动到采样时发生 Young GC 的次数)和YGCT(从应用程序启动到采样时 Young GC 所用的时间(秒)),计算YGCT/YGC会得出,平均每次新生代的GC耗时50ms,这是一个很小的数字,通过这个结果可以看出,我们大可不必关注新生代GC对GC性能的影响。</p><p>现在来看一下FGC( 从应用程序启动到采样时发生 Full GC 的次数)和FGCT(从应用程序启动到采样时 Full GC 所用的时间(秒)),计算FGCT/FGC会得出,平均每次老年代的GC耗时19.68s。有可能是执行了三次Full GC,每次耗时19.68s,也有可能是有两次只花了1s,另一次花了58s。不管是哪一种情况,GC优化都是很有必要的。</p><p>使用<code>jstat</code>命令可以很容易地查看GC状态,但是分析GC的最佳方式是加上<code>-verbosegc</code>参数来生成日志。在之前的文章中笔者已经解释了如何分析这些日志。<strong>HPJMeter</strong>是笔者最喜欢的用于分析<code>-verbosegc</code>生成的日志的工具,它简单易用,使用HPJmeter可以很容易地查看GC执行时间以及GC发生频率。</p><p>此外,如果GC执行时间满足下列所有条件,就没有必要进行GC优化了:</p><ul><li>Minor GC执行非常迅速(50ms以内)</li><li>Minor GC没有频繁执行(大约10s执行一次)</li><li>Full GC执行非常迅速(1s以内)</li><li>Full GC没有频繁执行(大约10min执行一次)</li></ul><p>括号中的数字并不是绝对的,它们也随着服务的状态而变化。有些服务可能要求一次Full GC在0.9s以内,而有些则会放得更宽一些。因此,对于不同的服务,需要按照不同的标准考虑是否需要执行GC优化。</p><p>当检查GC状态时,不能只查看Minor GC和Full GC的时间,还必须要<strong>关注GC执行的次数</strong>。如果新生代空间太小,Minor GC将会非常频繁地执行(有时每秒会执行一次,甚至更多)。此外,传入老年代的对象数目会上升,从而导致Full GC的频率升高。因此,在执行<code>jstat</code>命令时,请使用<code>-gccapacity</code>参数来查看具体占用了多少空间。</p><h2 id="设置GC类型-内存大小"><a href="#设置GC类型-内存大小" class="headerlink" title="设置GC类型/内存大小"></a>设置GC类型/内存大小</h2><h3 id="设置GC类型"><a href="#设置GC类型" class="headerlink" title="设置GC类型"></a>设置GC类型</h3><p>Oracle JVM有5种垃圾收集器,但是在JDK 7以前的版本中,你只能在Parallel GC, Parallel Compacting GC 和CMS GC之中选择,至于具体选择哪个,则没有具体的原则和规则。</p><p>既然这样的话,<strong>我们如何来选择GC呢?</strong>最好的方法是把三种都用上,但是有一点必须明确——CMS GC通常比其他并行(Parallel)GC都要快(这是因为CMS GC是并发的GC),如果确实如此,那只选择CMS GC就可以了,不过CMS GC也不总是更快,当出现<strong>concurrent mode failure</strong>时,CMS GC就会比并行GC更慢了。</p><p><strong>Concurrent mode failure</strong></p><p>现在让我们来深入地了解一下<strong>concurrent mode failure</strong>。</p><p>并行GC和CMS GC的最大区别是并行GC采用“标记-整理”(Mark-Compact)算法而CMS GC采用“标记-清除”(Mark-Sweep)算法(具体内容可参照译者的文章<a href="https://crowhawk.github.io/2017/08/10/jvm_2/" target="_blank" rel="noopener">《GC算法与内存分配策略》</a>),compact步骤就是通过移动内存来消除内存碎片,从而消除分配的内存之间的空白区域。</p><p>对于并行GC来说,无论何时执行Full GC,都会进行compact工作,这消耗了太多的时间。不过在执行完Full GC后,下次内存分配将会变得更快(因为直接顺序分配相邻的内存)。</p><p>相反,CMS GC没有compact的过程,因此CMS GC运行的速度更快。但是也是由于没有整理内存,在进行磁盘清理之前,内存中会有很多零碎的空白区域,这也导致没有足够的空间分配给大对象。例如,在老年代还有300MB可用空间,但是连一个10MB的对象都没有办法被顺序存储在老年代中,在这种情况下,会报出<strong>“concurrent mode failure”</strong>的warning,然后系统执行compact操作。但是CMS GC在这种情况下执行的compact操作耗时要比并行GC高很多,并且这还会导致另一个问题,关于<strong>“concurrent mode failure”</strong>的详细说明,可用参考Oracle工程师撰写的<a href="https://blogs.oracle.com/poonam/understanding-cms-gc-logs" target="_blank" rel="noopener">《Understanding CMS GC Logs》</a>。</p><p>综上所述,你需要根据你的系统情况为其选择一个最适合的GC类型。</p><p>每个系统都有最适合它的GC类型等着你去寻找,如果你有6台服务器,我建议你每两个服务器设置相同的参数,然后加上<code>-verbosegc</code>参数再分析结果。</p><h3 id="设置内存大小"><a href="#设置内存大小" class="headerlink" title="设置内存大小"></a>设置内存大小</h3><p>下面展示了内存大小、GC运行次数和GC运行时间之间的关系:</p><p><strong>大内存空间</strong></p><ul><li>减少了GC的次数</li><li>提高了GC的运行时间</li></ul><p><strong>小内存空间</strong></p><ul><li>增多了GC的次数</li><li>降低了GC的运行时间</li></ul><p>关于如何设置内存的大小,没有一个标准答案,如果服务器资源充足并且Full GC能在1s内完成,把内存设为10GB也是可以的,但是大部分服务器并不处在这种状态中,当内存设为10GB时,Full GC会耗时10-30s,具体的时间自然与对象的大小有关。</p><p>既然如此,<strong>我们该如何设置内存大小呢?</strong>通常我推荐设为500MB,这不是说你要通过<code>-Xms500m</code>和<code>-Xmx500m</code>参数来设置WAS内存。根据GC优化之前的状态,如果Full GC后还剩余300MB的空间,那么把内存设为1GB是一个不错的选择(300MB(默认程序占用)+ 500MB(老年代最小空间)+200MB(空闲内存))。这意味着你需要为老年代设置至少500MB空间,因此如果你有三个运行服务器,可以把它们的内存分别设置为1GB,1.5GB,2GB,然后检查结果。</p><p>理论上来说,GC执行速度应该遵循1GB> 1.5GB> 2GB,1GB内存时GC执行速度最快。然而,理论上的1GB内存Full GC消耗1s、2GB内存Full GC消耗2 s在现实里是无法保证的,实际的运行时间还依赖于服务器的性能和对象大小。因此,最好的方法是创建尽可能多的测量数据并监控它们。</p><p>在设置内存空间大小时,你还需要设置一个参数:<code>NewRatio</code>。<code>NewRatio</code>的值是新生代和老年代空间大小的比例。如果<code>XX:NewRatio=1</code>,则新生代空间:老年代空间=1:1,如果堆内存为1GB,则新生代:老年代=500MB:500MB。如果<code>NewRatio</code>等于2,则新生代:老年代=1:2,因此,<code>NewRatio</code>的值设置得越大,则老年代空间越大,新生代空间越小。</p><p>你可能会认为把<code>NewRatio</code>设为1会是最好的选择,然而事实并非如此,根据笔者的经验,当<code>NewRatio</code>设为2或3时,整个GC的状态表现得更好。</p><p><strong>完成GC优化最快地方法是什么?</strong>答案是比较性能测试的结果。为了给每台服务器设置不同的参数并监控它们,最好查看的是一或两天后的数据。当通过性能测试来进行GC优化时,你需要在不同的测试时保证它们有相同的负载和运行环境。然而,即使是专业的性能测试人员,想精确地控制负载也很困难,并且需要大量的时间准备。因此,更加方便容易的方式是直接设置参数来运行,然后等待运行的结果(即使这需要消耗更多的时间)。</p><h2 id="分析GC优化的结果"><a href="#分析GC优化的结果" class="headerlink" title="分析GC优化的结果"></a>分析GC优化的结果</h2><p>在设置了GC参数和<code>-verbosegc</code>参数后,可以使用tail命令确保日志被正确地生成。如果参数设置得不正确或日志未生成,那你的时间就被白白浪费了。如果日志收集没有问题的话,在收集一或两天数据后再检查结果。最简单的方法是把日志从服务器移到你的本地PC上,然后用<strong>HPJMeter</strong>分析数据。</p><p>在分析结果时,请关注下列几点(这个优先级是笔者根据自己的经验拟定的,我认为选取GC参数时应考虑的最重要的因素是Full GC的运行时间。):</p><ul><li>单次Full GC运行时间</li><li>单次Minor GC运行时间</li><li>Full GC运行间隔</li><li>Minor GC运行间隔</li><li>整个Full GC的时间</li><li>整个Minor GC的运行时间</li><li>整个GC的运行时间</li><li>Full GC的执行次数</li><li>Minor GC的执行次数</li></ul><p>找到最佳的GC参数是件非常幸运的,然而在大多数时候,我们并不会如此幸运,在进行GC优化时一定要小心谨慎,因为当你试图一次完成所有的优化工作时,可能会出现<code>OutOfMemoryError</code>错误。</p><h2 id="优化案例"><a href="#优化案例" class="headerlink" title="优化案例"></a>优化案例</h2><p>到目前为止,我们一直在从理论上介绍GC优化,现在是时候将这些理论付诸实践了,我们将通过几个例子来更深入地理解GC优化。</p><h3 id="示例1"><a href="#示例1" class="headerlink" title="示例1"></a>示例1</h3><p>下面这个例子是针对<strong>Service S</strong>的优化,对于最近刚开发出来的Service S,执行Full GC需要消耗过多的时间。</p><p>现在看一下执行<code>jstat -gcutil</code>的结果</p><pre><code>S0 S1 E O P YGC YGCT FGC FGCT GCT12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993</code></pre><p>左边的Perm区的值对于最初的GC优化并不重要,而YGC参数的值更加对于这次优化更为重要。</p><p>平均执行一次Minor GC和Full GC消耗的时间如下表所示:</p><p><strong>表3:Service S的Minor GC 和Full GC的平均执行时间</strong></p><table><thead><tr><th>GC类型</th><th>GC执行次数</th><th>GC执行时间</th><th>平均值</th></tr></thead><tbody><tr><td>Minor GC</td><td>54</td><td>2.047s</td><td>37ms</td></tr><tr><td>Full GC</td><td>5</td><td>6.946s</td><td>1.389s</td></tr></tbody></table><p><strong>37ms</strong>对于Minor GC来说还不赖,但1.389s对于Full GC来说意味着当GC发生在数据库Timeout设置为1s的系统中时,可能会频繁出现超时现象。</p><p>首先,你需要检查开始GC优化前内存的使用情况。使用<code>jstat -gccapacity</code>命令可以检查内存用量情况。在笔者的服务器上查看到的结果如下:</p><pre><code>NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5</code></pre><p>其中的关键值如下:</p><ul><li>新生代内存用量:212,992 KB</li><li>老年代内存用量:1,884,160 KB</li></ul><p>因此,除了永久代以外,被分配的内存空间加起来有2GB,并且新生代:老年代=1:9,为了得到比使用<code>jstat</code>更细致的结果,还需加上<code>-verbosegc</code>参数获取日志,并把三台服务器按照如下方式设置(除此以外没有使用任何其他参数):</p><ul><li>NewRatio=2</li><li>NewRatio=3</li><li>NewRatio=4</li></ul><p>一天后我得到了系统的GC log,幸运的是,在设置完NewRatio后系统没有发生任何Full GC。</p><p><strong>这是为什么呢?</strong>这是因为大部分对象在创建后很快就被回收了,所有这些对象没有被传入老年代,而是在新生代就被销毁回收了。</p><p>在这样的情况下,就没有必要去改变其他的参数值了,只要选择一个最合适的<code>NewRatio</code>值即可。那么,<strong>如何确定最佳的NewRatio值呢?</strong>为此,我们分析一下每种<code>NewRatio</code>值下Minor GC的平均响应时间。</p><p>在每种参数下Minor GC的平均响应时间如下:</p><ul><li>NewRatio=2:45ms</li><li>NewRatio=3:34ms</li><li>NewRatio=4:30ms</li></ul><p>我们可以根据GC时间的长短得出NewRatio=4是最佳的参数值(尽管NewRatio=4时新生代空间是最小的)。在设置完GC参数后,服务器没有发生Full GC。</p><p>为了说明这个问题,下面是服务执行一段时间后执行<code>jstat –gcutil</code>的结果:</p><pre><code>S0 S1 E O P YGC YGCT FGC FGCT GCT8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219</code></pre><p>你可能会认为是服务器接收的请求少才使得GC发生的频率较低,实际上,虽然Full GC没有执行过,但Minor GC被执行了2424次。</p><h3 id="示例2"><a href="#示例2" class="headerlink" title="示例2"></a>示例2</h3><p>这是一个Service A的例子。我们通过公司内部的应用性能管理系统(APM)发现JVM暂停了相当长的时间(超过8秒),因此我们进行了GC优化。我们努力寻找JVM暂停的原因,后来发现是因为Full GC执行时间过长,因此我们决定进行GC优化。</p><p>在GC优化的开始阶段,我们加上了<code>-verbosegc</code>参数,结果如下图所示:</p><p><img src="/images/2018-07-03/jvm_4_1.png" alt=""></p><p><strong>图1:进行GC优化之前STW的时间</strong></p><p>上图是由HPJMeter生成的图片之一。横坐标表示JVM执行的时间,纵坐标表示每次GC的时间。CMS为绿点,表示Full GC的结果,而Parallel Scavenge为蓝点,表示Minor GC的结果。</p><p>之前我说过CMS GC是最快的GC,但是上面的结果显示在一些时候CMS耗时达到了15s。<strong>是什么导致了这一结果?</strong>请记住我之前说的:CMS在执行compact(整理)操作时会显著变慢。此外,服务的内存通过<code>-Xms1g</code>和<code>=Xmx4g</code>设置了,而分配的内存只有4GB。</p><p>因此笔者将GC类型从CMS GC改为了Parallel GC,把内存大小设为2GB,并把<code>NewRatio</code>设为3。在执行<code>jstat -gcutil</code>几小时后的结果如下:</p><pre><code>S0 S1 E O P YGC YGCT FGC FGCT GCT0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890</code></pre><p>Full GC的时间缩短了,变成了每次3s,跟15s比有了显著提升。但是3s依然不够快,为此笔者创建了以下6种情况:</p><ul><li>Case 1: <code>-XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2</code></li><li>Case 2: <code>-XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3</code></li><li>Case 3: <code>-XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3</code></li><li>Case 4: <code>-XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2</code></li><li>Case 5: <code>-XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3</code></li><li>Case 6: <code>-XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3</code></li></ul><p><strong>上面哪一种情况最快?</strong>结果显示,内存空间越小,运行结果最少。下图展示了性能最好的Case 6的结果图,它的最慢响应时间只有1.7s,并且响应时间的平均值已经被控制到了1s以内。</p><p><img src="/images/2018-07-03/jvm_4_2.png" alt=""></p><p><strong>图2:Case 6的持续时间图</strong></p><p>基于上图的结果,按照Case 6调整了GC参数,但这却导致每晚都会发生<code>OutOfMemoryError</code>。很难解释发生异常的具体原因,简单地说,应该是批处理程序导致了内存泄漏,我们正在解决相关的问题。</p><p>如果只对GC日志做一些短时间的分析就将相关参数部署到所有服务器上来执行GC优化,这将是非常危险的。切记,只有当你同时仔细分析服务的执行情况和GC日志后,才能保证GC优化没有错误地执行。</p><p>在上文中,我们通过两个GC优化的例子来说明了GC优化是怎样执行的。正如上文中提到的,例子中设置的GC参数可以设置在相同的服务器之上,但前提是他们具有相同的CPU、操作系统、JDK版本并且运行着相同的服务。此外,不要把我使用的参数照搬到你的应用上,它们可能在你的机器上并不能起到同样良好的效果。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>笔者没有执行heap dump并分析内存的详细内容,而是通过自己的经验进行GC优化。精确地分析内存可以得到更好的优化效果,不过这种分析一般只适用于内存使用量相对固定的场景。如果服务严重过载并占有了大量的内存,则建议你根据之前的经验进行GC优化。</p><p>笔者已经在一些服务上设置了G1 GC参数并进行了性能测试,但还没有应用于正式的生产环境。G1 GC的速度快于任何其他的GC类型,但是你必须要升级到JDK 7。此外,暂时还无法保证它的稳定性,没有人知道运行时是否会出现致命的错误,因此G1<br>GC暂时还不适合投入应用。</p><p>等未来JDK 7真正稳定了(这并不是说它现在不稳定),并且WAS针对JDK 7进行优化后,G1 GC最终能按照预期的那样来工作,等到那一天我们可能就不再需要GC优化了。</p><p>想了解关于GC优化的更多细节,请前往<a href="https://www.slideshare.net/" target="_blank" rel="noopener">Slideshare.com</a> 查看相关资料。强烈推荐<a href="https://www.slideshare.net/aszegedi/everything-i-ever-learned-about-jvm-performance-tuning-twitter" target="_blank" rel="noopener">Everything I Ever Learned About JVM Performance Tuning @Twitter</a>,作者是Attila Szegedi, 一名Twitter工程师,请花些时间好好阅读它。</p>]]></content>
<summary type="html">
<blockquote>
<p>本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作。</p>
</blockquote>
<blockquote>
<p>Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章的第三篇《How to Tune Java Garbage Collection》,本文的作者是韩国人,写在JDK 1.8发布之前,虽然有些地方有些许过时,但整体内容还是非常有价值的。译者此前也看到有人翻译了本文,发现其中有许多错漏生硬和语焉不详之处,因此决定自己翻译一份,供大家分享。</p>
</blockquote>
<p>本文是“成为Java GC专家”系列文章的第三篇,在系列的第一篇文章《理解Java GC》中,我们了解到了不同GC算法的执行过程、GC的工作原理、新生代和老年代的概念、JDK 7中你需要了解的5种GC类型以及每一种GC对性能的影响。</p>
<p>在系列的第二篇文章《如何监控Java GC》中笔者已经解释了JVM进行实时GC的原理、监控GC的方法以及可以使这一过程更加迅速高效的工具。</p>
<p>在第三篇文章中,笔者将基于实际生产环境中的案例,介绍几个GC优化的最佳参数设置。在此我们假设你已经理解了本系列前两篇文章的内容,因此为了更深入的理解本文所讲内容,我建议你在阅读本篇文章之前先仔细阅读这两篇文章。</p>
<h2 id="GC优化是必要的吗?"><a href="#GC优化是必要的吗?" class="headerlink" title="GC优化是必要的吗?"></a>GC优化是必要的吗?</h2><p>或者更准确地说,GC优化对Java基础服务来说是必要的吗?答案是否定的,事实上GC优化对Java基础服务来说在有些场合是可以省去的,但前提是这些正在运行的Java系统,必须包含以下参数或行为:</p>
<ul>
<li>内存大小已经通过<strong>-Xms和-Xmx</strong>参数指定过</li>
<li>运行在server模式下(使用<strong>-server</strong>参数)</li>
<li>系统中没有残留超时日志之类的错误日志</li>
</ul>
<p>换句话说,如果你在运行时没有手动设置内存大小并且打印出了过多的超时日志,那你就需要对系统进行GC优化。</p>
<p>不过你需要时刻谨记一句话:<strong>GC tuning is the last task to be done.</strong></p>
</summary>
<category term="jvm" scheme="http://www.guowenbo.top/categories/jvm/"/>
<category term="jvm" scheme="http://www.guowenbo.top/tags/jvm/"/>
</entry>
<entry>
<title>jvm 3.GC算法 垃圾收集器</title>
<link href="http://www.guowenbo.top/2018/07/02/jvm%203.GC%E7%AE%97%E6%B3%95-%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8/"/>
<id>http://www.guowenbo.top/2018/07/02/jvm 3.GC算法-垃圾收集器/</id>
<published>2018-07-02T02:43:26.000Z</published>
<updated>2019-01-03T09:43:41.570Z</updated>
<content type="html"><![CDATA[<h1 id="GC算法-垃圾收集器"><a href="#GC算法-垃圾收集器" class="headerlink" title="GC算法-垃圾收集器"></a>GC算法-垃圾收集器</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。 jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.</p><a id="more"></a><h2 id="对象存活判断"><a href="#对象存活判断" class="headerlink" title="对象存活判断"></a>对象存活判断</h2><p>判断对象是否存活一般有两种方式:</p><p><strong>引用计数</strong>:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。<br><strong>可达性分析(Reachability Analysis)</strong>:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。</p><p>在Java语言中,GC Roots包括:</p><ul><li>虚拟机栈中引用的对象。</li><li>方法区中类静态属性实体引用的对象。</li><li>方法区中常量引用的对象。</li><li>本地方法栈中JNI引用的对象。</li></ul><h2 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h2><h3 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记 -清除算法"></a>标记 -清除算法</h3><p>“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。</p><p>它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。</p><p><img src="/images/2018-07-02/jvm_3_1.png" alt=""></p><h3 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h3><p>“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。</p><p>这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。</p><p><img src="/images/2018-07-02/jvm_3_2.png" alt=""></p><h3 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h3><p>复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。</p><p>根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存</p><p><img src="/images/2018-07-02/jvm_3_3.png" alt=""></p><h3 id="分代收集算法"><a href="#分代收集算法" class="headerlink" title="分代收集算法"></a>分代收集算法</h3><p>GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。</p><p>“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。</p><h2 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h2><blockquote><p>如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现</p></blockquote><h3 id="Serial收集器"><a href="#Serial收集器" class="headerlink" title="Serial收集器"></a>Serial收集器</h3><p>串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)</p><p>参数控制:</p><p><code>-XX:+UseSerialGC</code> 串行收集器</p><p><img src="/images/2018-07-02/jvm_3_4.png" alt=""></p><p>ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩</p><p>参数控制:</p><p><code>-XX:+UseParNewGC</code> ParNew收集器<br><code>-XX:ParallelGCThreads</code> 限制线程数量</p><p><img src="/images/2018-07-02/jvm_3_5.png" alt=""></p><p>Parallel收集器</p><p>Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩</p><p>参数控制:</p><p><code>-XX:+UseParallelGC</code> 使用Parallel收集器+ 老年代串行</p><p>Parallel Old 收集器</p><p>Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供</p><p>参数控制:</p><p><code>-XX:+UseParallelOldGC</code> 使用Parallel收集器+ 老年代并行</p><p>CMS收集器</p><p>CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。</p><p>从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:</p><ul><li>初始标记(CMS initial mark)</li><li>并发标记(CMS concurrent mark)</li><li>重新标记(CMS remark)</li><li>并发清除(CMS concurrent sweep)</li></ul><p>其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。</p><p>由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)</p><p><strong>优点</strong>: 并发收集、低停顿<br><strong>缺点</strong>: 产生大量空间碎片、并发阶段会降低吞吐量</p><p>参数控制:</p><p><code>-XX:+UseConcMarkSweepGC</code> 使用CMS收集器<br><code>-XX:+ UseCMSCompactAtFullCollection</code> Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长<br><code>-XX:+CMSFullGCsBeforeCompaction</code> 设置进行几次Full GC后,进行一次碎片整理<br><code>-XX:ParallelCMSThreads</code> 设定CMS的线程数量(一般情况约等于可用CPU数量)</p><p><img src="/images/2018-07-02/jvm_3_6.png" alt=""></p><p>G1收集器</p><p>G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:</p><ol><li><strong>空间整合</strong>,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。</li><li><strong>可预测停顿</strong>,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。</li></ol><p>上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。</p><p><img src="/images/2018-07-02/jvm_3_7.png" alt=""></p><p>G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。</p><p>收集步骤:</p><ol><li>标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)</li><li>Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。</li><li>Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。<br> <img src="/images/2018-07-02/jvm_3_8.png" alt=""></li><li>Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。</li><li>Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。<br> <img src="/images/2018-07-02/jvm_3_9.png" alt=""></li><li>复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。<br> <img src="/images/2018-07-02/jvm_3_10.png" alt=""></li></ol><h2 id="常用的收集器组合"><a href="#常用的收集器组合" class="headerlink" title="常用的收集器组合"></a>常用的收集器组合</h2><table><thead><tr><th>服务器31</th><th style="text-align:center">新生代GC策略</th><th style="text-align:center">老年老代GC策略</th><th>说明</th></tr></thead><tbody><tr><td>组合1</td><td style="text-align:center">Serial</td><td style="text-align:center">Serial Old</td><td>Serial和Serial Old都是单线程进行GC,特点就是GC时暂停所有应用线程。</td></tr><tr><td>组合2</td><td style="text-align:center">Serial</td><td style="text-align:center">CMS+Serial</td><td>Old CMS(Concurrent Mark Sweep)是并发GC,实现GC线程和应用线程并发工作,不需要暂停所有应用线程。另外,当CMS进行GC失败时,会自动使用Serial Old策略进行GC。</td></tr><tr><td>组合3</td><td style="text-align:center">ParNew</td><td style="text-align:center">CMS</td><td>使用<code>-XX:+UseParNewGC</code>选项来开启。ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量。可以使用-XX:ParallelGCThreads选项指定GC的线程数。如果指定了选项<code>-XX:+UseConcMarkSweepGC</code>选项,则新生代默认使用ParNew GC策略。</td></tr><tr><td>组合4</td><td style="text-align:center">ParNew</td><td style="text-align:center">Serial Old</td><td>使用<code>-XX:+UseParNewGC</code>选项来开启。新生代使用ParNew GC策略,年老代默认使用Serial Old GC策略。</td></tr><tr><td>组合5</td><td style="text-align:center">Parallel Scavenge</td><td style="text-align:center">Serial Old</td><td>Parallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间),可见这会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。</td></tr><tr><td>组合6</td><td style="text-align:center">Parallel Scavenge</td><td style="text-align:center">Parallel Old</td><td>Parallel Old是Serial Old的并行版本</td></tr><tr><td>组合7</td><td style="text-align:center">G1GC</td><td style="text-align:center">G1GC</td><td><code>-XX:+UnlockExperimentalVMOptions</code> <code>-XX:+UseG1GC</code> #开启;<code>-XX:MaxGCPauseMillis =50</code> #暂停时间目标;<code>-XX:GCPauseIntervalMillis =200</code> #暂停间隔目标;<code>-XX:+G1YoungGenSize=512m</code> #年轻代大小;<code>-XX:SurvivorRatio=6</code> #幸存区比例</td></tr></tbody></table>]]></content>
<summary type="html">
<h1 id="GC算法-垃圾收集器"><a href="#GC算法-垃圾收集器" class="headerlink" title="GC算法-垃圾收集器"></a>GC算法-垃圾收集器</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。 jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.</p>
</summary>
<category term="jvm" scheme="http://www.guowenbo.top/categories/jvm/"/>
<category term="jvm" scheme="http://www.guowenbo.top/tags/jvm/"/>
</entry>
<entry>
<title>jvm 2.内存结构</title>
<link href="http://www.guowenbo.top/2018/06/29/jvm%202.%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84/"/>
<id>http://www.guowenbo.top/2018/06/29/jvm 2.内存结构/</id>
<published>2018-06-29T10:07:59.000Z</published>
<updated>2019-01-03T09:43:41.570Z</updated>
<content type="html"><![CDATA[<h1 id="jvm内存结构"><a href="#jvm内存结构" class="headerlink" title="jvm内存结构"></a>jvm内存结构</h1><p><img src="/images/2018-06-29/jvm_2_1.png" alt=""></p><p>JVM内存结构主要有三大块:<strong>堆内存、方法区和栈</strong>。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,<strong>Eden空间、From Survivor空间、To Survivor空间</strong>,默认情况下年轻代按照<strong>8:1:1</strong>的比例来分配;</p><p>方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。</p><p>在通过一张图来了解如何通过参数来控制各区域的内存大小</p><a id="more"></a><p><img src="/images/2018-06-29/jvm_2_2.png" alt=""></p><p>控制参数</p><ul><li>-Xms设置堆的最小空间大小。</li><li>-Xmx设置堆的最大空间大小。</li><li>-XX:NewSize设置新生代最小空间大小。</li><li>-XX:MaxNewSize设置新生代最大空间大小。</li><li>-XX:PermSize设置永久代最小空间大小。</li><li>-XX:MaxPermSize设置永久代最大空间大小。</li><li>-Xss设置每个线程的堆栈大小。</li></ul><p>没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。</p><blockquote><p>老年代空间大小=堆空间大小-年轻代大空间大小</p></blockquote><p>从更高的一个维度再次来看JVM和系统调用之间的关系</p><p><img src="/images/2018-06-29/jvm_2_3.png" alt=""></p><h2 id="Java堆(Heap)"><a href="#Java堆(Heap)" class="headerlink" title="Java堆(Heap)"></a>Java堆(Heap)</h2><p>对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中<strong>最大</strong>的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,<strong>几乎所有的对象实例都在这里分配内存</strong>。</p><p>Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做<strong>“GC堆”</strong>。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:<strong>新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等</strong>。</p><p>根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。</p><p>如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。</p><h2 id="方法区(Method-Area)"><a href="#方法区(Method-Area)" class="headerlink" title="方法区(Method Area)"></a>方法区(Method Area)</h2><p>方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,<strong>它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据</strong>。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。</p><p>对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。</p><p>Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。</p><p>根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。</p><p>方法区有时被称为持久代(PermGen)。</p><p><img src="/images/2018-06-29/jvm_2_4.png" alt=""></p><p>所有的对象在实例化后的整个运行周期内,都被存放在堆内存中。堆内存又被划分成不同的部分:伊甸区(Eden),幸存者区域(Survivor Sapce),老年代(Old Generation Space)。</p><p>方法的执行都是伴随着线程的。原始类型的本地变量以及引用都存放在线程栈中。而引用关联的对象比如String,都存在在堆中。为了更好的理解上面这段话,我们可以看一个例子:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.text.SimpleDateFormat;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"><span class="keyword">import</span> org.apache.log4j.Logger;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloWorld</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Logger LOGGER = Logger.getLogger(HelloWorld.class.getName());</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sayHello</span><span class="params">(String message)</span> </span>{</span><br><span class="line"> SimpleDateFormat formatter = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"dd.MM.YYYY"</span>);</span><br><span class="line"> String today = formatter.format(<span class="keyword">new</span> Date());</span><br><span class="line"> LOGGER.info(today + <span class="string">": "</span> + message);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段程序的数据在内存中的存放如下:</p><p><img src="/images/2018-06-29/jvm_2_5.png" alt=""></p><p>通过JConsole工具可以查看运行中的Java程序(比如Eclipse)的一些信息:堆内存的分配,线程的数量以及加载的类的个数;</p><p><img src="/images/2018-06-29/jvm_2_6.png" alt=""></p><h2 id="程序计数器(Program-Counter-Register)"><a href="#程序计数器(Program-Counter-Register)" class="headerlink" title="程序计数器(Program Counter Register)"></a>程序计数器(Program Counter Register)</h2><p>一块较小的内存空间,字节码的行号指示器,为线程的切换提供保障</p><h2 id="Java虚拟机栈(Java-Virtual-Machine-Stacks)"><a href="#Java虚拟机栈(Java-Virtual-Machine-Stacks)" class="headerlink" title="Java虚拟机栈(Java Virtual Machine Stacks)"></a>Java虚拟机栈(Java Virtual Machine Stacks)</h2><p>与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,<strong>它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型</strong>:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。<strong>每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程</strong>。</p><p>局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。</p><p>其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。</p><p>在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。</p><h2 id="本地方法栈(Native-Method-Stacks)"><a href="#本地方法栈(Native-Method-Stacks)" class="headerlink" title="本地方法栈(Native Method Stacks)"></a>本地方法栈(Native Method Stacks)</h2><p>本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而<strong>本地方法栈则是为虚拟机使用到的Native方法服务</strong>。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。</p><h2 id="哪儿的OutOfMemoryError"><a href="#哪儿的OutOfMemoryError" class="headerlink" title="哪儿的OutOfMemoryError"></a>哪儿的OutOfMemoryError</h2><p>对内存结构清晰的认识同样可以帮助理解不同OutOfMemoryErrors:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread <span class="string">"main"</span>: java.lang.OutOfMemoryError: Java heap space</span><br></pre></td></tr></table></figure></p><p>原因:堆溢出<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread <span class="string">"main"</span>: java.lang.OutOfMemoryError: PermGen space</span><br></pre></td></tr></table></figure></p><p>原因:类或者方法不能被加载到持久代。它可能出现在一个程序加载很多类的时候,比如引用了很多第三方的库;<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread <span class="string">"main"</span>: java.lang.OutOfMemoryError: Requested array size exceeds VM limit</span><br></pre></td></tr></table></figure></p><p>原因:创建的数组大于堆内存的空间<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread <span class="string">"main"</span>: java.lang.OutOfMemoryError: request <size> bytes <span class="keyword">for</span> <reason>. Out of swap space?</span><br></pre></td></tr></table></figure></p><p>原因:分配本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread <span class="string">"main"</span>: java.lang.OutOfMemoryError: <reason> <stack trace>(Native method)</span><br></pre></td></tr></table></figure></p><p>原因:同样是本地方法内存分配失败,只不过是JNI或者本地方法或者Java虚拟机发现</p>]]></content>
<summary type="html">
<h1 id="jvm内存结构"><a href="#jvm内存结构" class="headerlink" title="jvm内存结构"></a>jvm内存结构</h1><p><img src="/images/2018-06-29/jvm_2_1.png" alt=""></p>
<p>JVM内存结构主要有三大块:<strong>堆内存、方法区和栈</strong>。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,<strong>Eden空间、From Survivor空间、To Survivor空间</strong>,默认情况下年轻代按照<strong>8:1:1</strong>的比例来分配;</p>
<p>方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。</p>
<p>在通过一张图来了解如何通过参数来控制各区域的内存大小</p>
</summary>
<category term="jvm" scheme="http://www.guowenbo.top/categories/jvm/"/>
<category term="jvm" scheme="http://www.guowenbo.top/tags/jvm/"/>
</entry>
<entry>
<title>jvm 1.类的加载机制</title>
<link href="http://www.guowenbo.top/2018/06/27/jvm%201.%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/"/>
<id>http://www.guowenbo.top/2018/06/27/jvm 1.类的加载机制/</id>
<published>2018-06-27T07:30:37.000Z</published>
<updated>2019-01-03T09:43:41.570Z</updated>
<content type="html"><![CDATA[<h1 id="类加载机制"><a href="#类加载机制" class="headerlink" title="类加载机制"></a>类加载机制</h1><h2 id="什么是类的加载"><a href="#什么是类的加载" class="headerlink" title="什么是类的加载"></a>什么是类的加载</h2><p>类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。</p><p><img src="/images/2018-06-27/jvm_1_1.png" alt=""></p><p>类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误</p><a id="more"></a><blockquote><p>加载.class文件的方式:</p></blockquote><ul><li>从本地系统中直接加载</li><li>通过网络下载.class文件</li><li>从zip,jar等归档文件中加载.class文件</li><li>从专有数据库中提取.class文件</li><li>将Java源文件动态编译为.class文件</li></ul><h2 id="类的生命周期"><a href="#类的生命周期" class="headerlink" title="类的生命周期"></a>类的生命周期</h2><p><img src="/images/2018-06-27/jvm_1_2.png" alt=""></p><p>其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。</p><h3 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h3><p>查找并加载类的二进制数据加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:</p><ul><li>通过一个类的全限定名来获取其定义的二进制字节流。</li><li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。</li><li>在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。</li></ul><p>相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。</p><p>加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。</p><h3 id="验证:确保被加载的类的正确性"><a href="#验证:确保被加载的类的正确性" class="headerlink" title="验证:确保被加载的类的正确性"></a>验证:确保被加载的类的正确性</h3><p>验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:</p><ul><li><strong>文件格式验证</strong>:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。</li><li><strong>元数据验证</strong>:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。</li><li><strong>字节码验证</strong>:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。</li><li><strong>符号引用验证</strong>:确保解析动作能正确执行。</li></ul><p>验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。</p><h3 id="准备:为类的静态变量分配内存,并将其初始化为默认值"><a href="#准备:为类的静态变量分配内存,并将其初始化为默认值" class="headerlink" title="准备:为类的静态变量分配内存,并将其初始化为默认值"></a>准备:为类的静态变量分配内存,并将其初始化为默认值</h3><p>准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:</p><ol><li>这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。</li><li><p>这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。</p><p> 假设一个类变量的定义为:public static int value = 3;</p><p> 那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的public static指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。</clinit></p><blockquote><p>这里还需要注意如下几点:</p><ul><li>对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。</li><li>对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。</li><li>对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。</li><li>如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。</li></ul></blockquote></li><li><p>如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。</p><p> 假设上面的类变量value被定义为: public static final int value = 3;</p><p> 编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中</p></li></ol><h3 id="解析:把类中的符号引用转换为直接引用"><a href="#解析:把类中的符号引用转换为直接引用" class="headerlink" title="解析:把类中的符号引用转换为直接引用"></a>解析:把类中的符号引用转换为直接引用</h3><p>解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。</p><p>直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。</p><p>初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:</p><ol><li>声明类变量是指定初始值</li><li>使用静态代码块为类变量指定初始值</li></ol><p>JVM初始化步骤</p><ol><li>假如这个类还没有被加载和连接,则程序先加载并连接该类</li><li>假如该类的直接父类还没有被初始化,则先初始化其直接父类</li><li>假如类中有初始化语句,则系统依次执行这些初始化语句</li></ol><p>类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:</p><ul><li>创建类的实例,也就是new的方式</li><li>访问某个类或接口的静态变量,或者对该静态变量赋值</li><li>调用类的静态方法</li><li>反射(如Class.forName(“com.shengsiyuan.Test”))</li><li>初始化某个类的子类,则其父类也会被初始化</li><li>Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类</li></ul><h3 id="结束生命周期"><a href="#结束生命周期" class="headerlink" title="结束生命周期"></a>结束生命周期</h3><p>在如下几种情况下,Java虚拟机将结束生命周期</p><ul><li>执行了System.exit()方法</li><li>程序正常执行结束</li><li>程序在执行过程中遇到了异常或错误而异常终止</li><li>由于操作系统出现错误而导致Java虚拟机进程终止</li></ul><h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><p>寻找类加载器,先来一个小例子<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ClassLoaderTest</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> ClassLoader loader = Thread.currentThread().getContextClassLoader();</span><br><span class="line"> System.out.println(loader);</span><br><span class="line"> System.out.println(loader.getParent());</span><br><span class="line"> System.out.println(loader.getParent().getParent());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>运行后,输出结果:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sun.misc.Launcher$AppClassLoader@<span class="number">64f</span>ef26a</span><br><span class="line">sun.misc.Launcher$ExtClassLoader@<span class="number">1</span>ddd40f3</span><br><span class="line"><span class="keyword">null</span></span><br></pre></td></tr></table></figure></p><p>从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(引导类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null。</p><p>这几种类加载器的层次关系如下图所示:</p><p><img src="/images/2018-06-27/jvm_1_3.png" alt=""></p><blockquote><p>注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。</p></blockquote><p>站在Java虚拟机的角度来讲,只存在两种不同的类加载器:启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分;所有其它的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。</p><p>站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:</p><p><strong>启动类加载器</strong>:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。<br><strong>扩展类加载器</strong>:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。<br><strong>应用程序类加载器</strong>:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。</p><p>应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:</p><ol><li>在执行非置信代码之前,自动验证数字签名。</li><li>动态地创建符合用户特定需要的定制化构建类。</li><li>从特定的场所取得java class,例如数据库中和网络中。</li></ol><p><strong>JVM类加载机制</strong></p><ul><li><strong>全盘负责</strong>,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入</li><li><strong>父类委托</strong>,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类</li><li><strong>缓存机制</strong>,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效</li></ul><h2 id="类的加载"><a href="#类的加载" class="headerlink" title="类的加载"></a>类的加载</h2><p>类加载有三种方式:</p><ol><li>命令行启动应用时候由JVM初始化加载</li><li>通过Class.forName()方法动态加载</li><li>通过ClassLoader.loadClass()方法动态加载</li></ol><p>例子:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">loaderTest</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ClassNotFoundException </span>{ </span><br><span class="line"> ClassLoader loader = HelloWorld.class.getClassLoader(); </span><br><span class="line"> System.out.println(loader); </span><br><span class="line"> <span class="comment">//使用ClassLoader.loadClass()来加载类,不会执行初始化块 </span></span><br><span class="line"> loader.loadClass(<span class="string">"Test2"</span>); </span><br><span class="line"> <span class="comment">//使用Class.forName()来加载类,默认会执行初始化块 </span></span><br><span class="line"> <span class="comment">//Class.forName("Test2"); </span></span><br><span class="line"> <span class="comment">//使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块 </span></span><br><span class="line"> <span class="comment">//Class.forName("Test2", false, loader); </span></span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>demo类:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test2</span> </span>{ </span><br><span class="line"> <span class="keyword">static</span> { </span><br><span class="line"> System.out.println(<span class="string">"静态初始化块执行了!"</span>); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>分别切换加载方式,会有不同的输出结果。</p><p><strong>Class.forName()和ClassLoader.loadClass()区别</strong></p><ul><li>Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;</li><li>ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。</li><li>Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。</li></ul><h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><p>双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。</p><p>双亲委派机制:</p><ol><li>当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。</li><li>当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。</li><li>如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;</li><li>若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。</li></ol><p>ClassLoader源码分析:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Class<?> loadClass(String name)<span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"> <span class="keyword">return</span> loadClass(name, <span class="keyword">false</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">synchronized</span> Class<?> loadClass(String name, <span class="keyword">boolean</span> resolve)<span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"> <span class="comment">// 首先判断该类型是否已经被加载</span></span><br><span class="line"> Class c = findLoadedClass(name);</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (parent != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//如果存在父类加载器,就委派给父类加载器加载</span></span><br><span class="line"> c = parent.loadClass(name, <span class="keyword">false</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)</span></span><br><span class="line"> c = findBootstrapClass0(name);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="comment">// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能</span></span><br><span class="line"> c = findClass(name);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (resolve) {</span><br><span class="line"> resolveClass(c);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> c;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>双亲委派模型意义:</p><ul><li>系统类防止内存中出现多份同样的字节码</li><li>保证Java程序安全稳定运行</li></ul><h2 id="自定义类加载器"><a href="#自定义类加载器" class="headerlink" title="自定义类加载器"></a>自定义类加载器</h2><p>通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自ClassLoader类,从上面对loadClass方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClassLoader</span> <span class="keyword">extends</span> <span class="title">ClassLoader</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String root;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> Class<?> findClass(String name) <span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"> <span class="keyword">byte</span>[] classData = loadClassData(name);</span><br><span class="line"> <span class="keyword">if</span> (classData == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ClassNotFoundException();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> defineClass(name, classData, <span class="number">0</span>, classData.length);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">byte</span>[] loadClassData(String className) {</span><br><span class="line"> String fileName = root + File.separatorChar</span><br><span class="line"> + className.replace(<span class="string">'.'</span>, File.separatorChar) + <span class="string">".class"</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> InputStream ins = <span class="keyword">new</span> FileInputStream(fileName);</span><br><span class="line"> ByteArrayOutputStream baos = <span class="keyword">new</span> ByteArrayOutputStream();</span><br><span class="line"> <span class="keyword">int</span> bufferSize = <span class="number">1024</span>;</span><br><span class="line"> <span class="keyword">byte</span>[] buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[bufferSize];</span><br><span class="line"> <span class="keyword">int</span> length = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> ((length = ins.read(buffer)) != -<span class="number">1</span>) {</span><br><span class="line"> baos.write(buffer, <span class="number">0</span>, length);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> baos.toByteArray();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getRoot</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> root;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setRoot</span><span class="params">(String root)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.root = root;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"></span><br><span class="line"> MyClassLoader classLoader = <span class="keyword">new</span> MyClassLoader();</span><br><span class="line"> classLoader.setRoot(<span class="string">"E:\\temp"</span>);</span><br><span class="line"></span><br><span class="line"> Class<?> testClass = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> testClass = classLoader.loadClass(<span class="string">"com.neo.classloader.Test2"</span>);</span><br><span class="line"> Object object = testClass.newInstance();</span><br><span class="line"> System.out.println(object.getClass().getClassLoader());</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (InstantiationException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (IllegalAccessException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:</p><ol><li>这里传递的文件名需要是类的全限定性名称,即com.paddx.test.classloading.Test格式的,因为 defineClass 方法是按这种格式进行处理的。</li><li>最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。</li><li>这类Test 类本身可以被 AppClassLoader类加载,因此我们不能把com/paddx/test/classloading/Test.class放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由AppClassLoader加载,而不会通过我们自定义类加载器来加载。</li></ol><blockquote><p>参考: <a href="http://www.cnblogs.com/ityouknow/p/5603287.html" target="_blank" rel="noopener">http://www.cnblogs.com/ityouknow/p/5603287.html</a></p></blockquote>]]></content>
<summary type="html">
<h1 id="类加载机制"><a href="#类加载机制" class="headerlink" title="类加载机制"></a>类加载机制</h1><h2 id="什么是类的加载"><a href="#什么是类的加载" class="headerlink" title="什么是类的加载"></a>什么是类的加载</h2><p>类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。</p>
<p><img src="/images/2018-06-27/jvm_1_1.png" alt=""></p>
<p>类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误</p>
</summary>
<category term="jvm" scheme="http://www.guowenbo.top/categories/jvm/"/>
<category term="jvm" scheme="http://www.guowenbo.top/tags/jvm/"/>
</entry>
<entry>
<title>java之ladp轻量目录访问协议操作</title>
<link href="http://www.guowenbo.top/2018/06/24/java%E4%B9%8Bladp%E8%BD%BB%E9%87%8F%E7%9B%AE%E5%BD%95%E8%AE%BF%E9%97%AE%E5%8D%8F%E8%AE%AE%E6%93%8D%E4%BD%9C/"/>
<id>http://www.guowenbo.top/2018/06/24/java之ladp轻量目录访问协议操作/</id>
<published>2018-06-24T05:15:02.000Z</published>
<updated>2019-01-03T09:43:41.569Z</updated>
<content type="html"><![CDATA[<p>定义:LDAP是基于TCP/IP协议的目录访问协议,是Internet上目录服务的通用访问协议。LDAP的出现简化了X.500目录的复杂度,降低了开发成本,是X.500标准的目录访问协议DAP的子集,同时也作为IETF的一个正式标准。LDAP的核心规范在RFC中都有定义,所有与LDAP相关的RFC都可以在LDAPman RFC网页中找到。</p><a id="more"></a><h1 id="ldap介绍"><a href="#ldap介绍" class="headerlink" title="ldap介绍"></a>ldap介绍</h1><h2 id="ldap有什么用"><a href="#ldap有什么用" class="headerlink" title="ldap有什么用"></a>ldap有什么用</h2><p>在当今的信息世界,网络为人们提供了丰富的资源。随着网络资源的日益丰富,迫切需要一种能有效管理资源信息并利于检索查询的服务技术。目录服务技术随之产生。<br>1.LDAP目录服务可以有效地解决众多网络服务的用户账户问题。<br>2.LDAP目录服务规定了统一的身份信息数据库、身份认证机制和接口,实现了资源和信息的统一管理,保证了数据的一致性和完整性。<br>3.LDAP目录服务是以树状的层次结构来描述数据信息的,此种模型适应了众多行业应用的业务组织结构。</p><h2 id="ldap的好处"><a href="#ldap的好处" class="headerlink" title="ldap的好处"></a>ldap的好处</h2><p>LDAP服务器也是用来处理查询和更新LDAP目录的。换句话来说LDAP目录也是一种类型的数据库,但是不是关系型数据库。不象被设计成每分钟需要处理成百上千条数据变化的数据库,例如:在电子商务中经常用到的在线交易处理(OLTP)系统,LDAP主要是优化数据读取的性能。<br>LDAP最大的优势是:可以在任何计算机平台上,用很容易获得的而且数目不断增加的LDAP的客户端程序访问LDAP目录。而且也很容易定制应用程序为它加上LDAP的支持。</p><h1 id="Softerra-LDAP-Browser-4-5"><a href="#Softerra-LDAP-Browser-4-5" class="headerlink" title="Softerra LDAP Browser 4.5"></a>Softerra LDAP Browser 4.5</h1><p>Softerra LDAP Browser 4.5在我看来就是一个可视化工具,可以清晰的看到目录结构和数据,下面说明一下如何简单的使用:</p><h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p><a href="http://download.csdn.net/detail/fddqfddq/4656393" target="_blank" rel="noopener">http://download.csdn.net/detail/fddqfddq/4656393</a></p><p>这里就不详细了解此软件的使用,感兴趣的朋友可以自行了解一下。</p><h1 id="JAVA实现ldap的操作"><a href="#JAVA实现ldap的操作" class="headerlink" title="JAVA实现ldap的操作"></a>JAVA实现ldap的操作</h1><p>通过eclpse创建maven项目,引入需要的包和spring的常用包,主要jar包如下 (文档编写匆忙可能不全)</p><pre><code>spring-ldap-1.3.1.RELEASE-all.jarcommons-lang-2.6.jarcommons-logging-2.6.jarspring-beans-3.05.jarspring-core-3.05.jarspring-tx-3.05.jar</code></pre><p>创建基础类baseldap</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.meixin.ldap.base;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Hashtable;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.naming.Context;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.DirContext;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.InitialDirContext;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> ldap基础类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午3:53:57</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BaseLdap</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">//上下文,可由此进行简单操作</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> DirContext dc = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> {</span><br><span class="line">Hashtable<String, String> env = <span class="keyword">new</span> Hashtable<String, String>();</span><br><span class="line">String LDAP_URL = <span class="string">"********"</span>;</span><br><span class="line">String adminName = <span class="string">"*******"</span>;</span><br><span class="line">String adminPassword = <span class="string">"******"</span>;</span><br><span class="line">env.put(Context.INITIAL_CONTEXT_FACTORY,<span class="string">"com.sun.jndi.ldap.LdapCtxFactory"</span>);</span><br><span class="line">env.put(Context.PROVIDER_URL, LDAP_URL);</span><br><span class="line">env.put(Context.SECURITY_AUTHENTICATION, <span class="string">"simple"</span>);</span><br><span class="line">env.put(Context.SECURITY_PRINCIPAL, adminName);</span><br><span class="line">env.put(Context.SECURITY_CREDENTIALS, adminPassword);</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">dc = <span class="keyword">new</span> InitialDirContext(env);</span><br><span class="line">System.out.println(<span class="string">"账户认证成功!"</span>);</span><br><span class="line">} <span class="keyword">catch</span> (javax.naming.AuthenticationException e) {</span><br><span class="line">System.out.println(<span class="string">"账户认证失败,账号或密码不正确!"</span>);</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">System.out.println(<span class="string">"账户认证出错:"</span> + e);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后大概了解一下,写一个简单的工具类继承baseldap:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.meixin.ldap.utils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.naming.NamingEnumeration;</span><br><span class="line"><span class="keyword">import</span> javax.naming.NamingException;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.Attribute;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.Attributes;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.BasicAttribute;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.BasicAttributes;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.DirContext;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.ModificationItem;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.SearchControls;</span><br><span class="line"><span class="keyword">import</span> javax.naming.directory.SearchResult;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.meixin.ldap.base.BaseLdap;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 对ldap操作的简易工具类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午4:19:12</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LdapUtils</span> <span class="keyword">extends</span> <span class="title">BaseLdap</span></span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 创建新用户</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guoewenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午5:13:46</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> rootElement 某节点路径(将在此节点下创建)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> newUserName 名称</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(String rootElement,String newUserName)</span> </span>{</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">BasicAttributes attrs = <span class="keyword">new</span> BasicAttributes();</span><br><span class="line"><span class="comment">//创建子节点值</span></span><br><span class="line">BasicAttribute objclassSet = <span class="keyword">new</span> BasicAttribute(<span class="string">"objectClass"</span>);</span><br><span class="line">objclassSet.add(<span class="string">"memberOf"</span>);</span><br><span class="line">objclassSet.add(<span class="string">"msExchVersion"</span>);</span><br><span class="line">attrs.put(objclassSet);</span><br><span class="line">attrs.put(<span class="string">"ou"</span>, newUserName);</span><br><span class="line">dc.createSubcontext(<span class="string">"ou="</span> + newUserName + <span class="string">","</span> + rootElement, attrs);</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">System.out.println(<span class="string">"新增失败:"</span> + e);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 删除用户</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午5:16:25</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> dn 账户所在的域全路径</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">delete</span><span class="params">(String dn)</span> </span>{</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">dc.destroySubcontext(dn);</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">System.out.println(<span class="string">"删除失败:"</span> + e);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 更改节点名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午5:17:27</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> oldDN 旧节点名称全路径</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> newDN 新节点名称全路径</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">renameEntry</span><span class="params">(String oldDN, String newDN)</span> </span>{</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">dc.rename(oldDN, newDN);</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">} <span class="keyword">catch</span> (NamingException e) {</span><br><span class="line">System.out.println(<span class="string">"更新失败: "</span> + e);</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 更新节点下的属性与值 (节点属性的增删改,可扩展为批量操作)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午5:24:06</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> dn 需要更新的节点全路径</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> type 节点更新类型(默认0:新增,1:修改,2:删除)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> attribute 更改属性</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value 属性值</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">modifyInformation</span><span class="params">(String dn,<span class="keyword">int</span> type,String attribute, String value)</span> </span>{</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">ModificationItem[] mods = <span class="keyword">new</span> ModificationItem[<span class="number">1</span>];</span><br><span class="line">Attribute attr0 = <span class="keyword">new</span> BasicAttribute(attribute, value);</span><br><span class="line"><span class="keyword">if</span>(type==<span class="number">0</span>){</span><br><span class="line">mods[<span class="number">0</span>] = <span class="keyword">new</span> ModificationItem(DirContext.ADD_ATTRIBUTE, attr0);</span><br><span class="line">}<span class="keyword">else</span> <span class="keyword">if</span>(type==<span class="number">1</span>){</span><br><span class="line">mods[<span class="number">0</span>] = <span class="keyword">new</span> ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr0);</span><br><span class="line">}<span class="keyword">else</span> <span class="keyword">if</span>(type==<span class="number">2</span>){</span><br><span class="line">mods[<span class="number">0</span>] = <span class="keyword">new</span> ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr0);</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line">System.out.println(<span class="string">"type数据有误!"</span>);</span><br><span class="line">}</span><br><span class="line">dc.modifyAttributes(dn, mods);</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">} <span class="keyword">catch</span> (NamingException e) {</span><br><span class="line">System.out.println(<span class="string">"更新失败: "</span> + e);</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 关闭ldap连接</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午5:18:41</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">if</span> (dc != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">dc.close();</span><br><span class="line">System.out.println(<span class="string">"关闭ldap连接成功。。。"</span>);</span><br><span class="line">} <span class="keyword">catch</span> (NamingException e) {</span><br><span class="line">System.out.println(<span class="string">"关闭失败:"</span> + e);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 简单的条件查询</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> guowenbo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年3月15日 下午4:19:27</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> base 基础节点 (dc=meixin,dc=com)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> scope 查询范围(默认0:遍历,1:单层,2:本节点)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> resultAttrutes[] 指定查询属性(不需要指定则传递null:查询符合条件的所有属性;)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> searchFilter 查询条件(属性名=值,如:"sAMAccountName=guowenbo" 查询所有sAMAccountName属性值为guowenbo的数据;"objectclass=*"默认查询全部。)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Ldapbyuserinfo</span><span class="params">(String base,<span class="keyword">int</span> scope,String resultAttrutes[],String searchFilter)</span> </span>{</span><br><span class="line">SearchControls searchCtls = <span class="keyword">new</span> SearchControls();</span><br><span class="line"><span class="keyword">if</span>(scope==<span class="number">0</span>){</span><br><span class="line"><span class="comment">//默认</span></span><br><span class="line">searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);</span><br><span class="line">}<span class="keyword">else</span> <span class="keyword">if</span>(scope==<span class="number">1</span>){</span><br><span class="line">searchCtls.setSearchScope(SearchControls.ONELEVEL_SCOPE);</span><br><span class="line">}<span class="keyword">else</span> <span class="keyword">if</span>(scope==<span class="number">2</span>){</span><br><span class="line">searchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line">System.out.println(<span class="string">"查询范围数据不正确!"</span>);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">int</span> totalResults = <span class="number">0</span>;</span><br><span class="line">searchCtls.setReturningAttributes(resultAttrutes);</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">NamingEnumeration answer = dc.search(base, searchFilter,</span><br><span class="line">searchCtls);</span><br><span class="line"><span class="keyword">if</span> (answer == <span class="keyword">null</span> || answer.equals(<span class="keyword">null</span>)) {</span><br><span class="line">System.out.println(<span class="string">"返回结果为空或查询数据不存在!"</span>);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">}</span><br><span class="line">System.out.println(<span class="string">"查询成功!继续解析节点数据。。。"</span>);</span><br><span class="line"><span class="keyword">while</span> (answer.hasMoreElements()) {</span><br><span class="line">SearchResult sr = (SearchResult) answer.next();</span><br><span class="line">System.out.println(<span class="string">"已获取到信息:"</span> + sr.getName());</span><br><span class="line">System.out.println(<span class="string">"*****************进行遍历********************"</span>);</span><br><span class="line">Attributes Attrs = sr.getAttributes();</span><br><span class="line"><span class="keyword">if</span> (Attrs != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="keyword">for</span> (NamingEnumeration ne = Attrs.getAll(); ne.hasMore();) {</span><br><span class="line">Attribute Attr = (Attribute) ne.next();</span><br><span class="line">System.out.println(<span class="string">"属性名称(↓为值):"</span>+ Attr.getID().toString());</span><br><span class="line"><span class="keyword">for</span> (NamingEnumeration e = Attr.getAll(); e.hasMore(); totalResults++) {</span><br><span class="line">System.out.println(e.next().toString());</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">catch</span> (NamingException e) {</span><br><span class="line">System.err.println(<span class="string">"执行出错: "</span> + e);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">System.out.println(<span class="string">"*****************遍历结束********************"</span>);</span><br><span class="line">System.out.println(<span class="string">"属性总数: "</span> + totalResults);</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">System.err.println(<span class="string">"执行出错: "</span> + e);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试一下看看:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.meixin.ldap.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.meixin.ldap.utils.LdapUtils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LdapTest</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> String base = <span class="string">"dc=meixin,dc=com"</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> String filter = <span class="string">"sAMAccountName=guowenbo"</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> String resultElements[] = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> scope = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testQuery</span><span class="params">()</span></span>{</span><br><span class="line">LdapUtils test = <span class="keyword">new</span> LdapUtils();</span><br><span class="line">resultElements = <span class="keyword">new</span> String[]{<span class="string">"sAMAccountName"</span>,<span class="string">"msExchVersion"</span>,<span class="string">"memberOf"</span>};</span><br><span class="line">test.Ldapbyuserinfo(base,scope,resultElements,filter);</span><br><span class="line">test.close();</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果:</p><p><img src="/images/2018-06-24/ldap_1.png" alt="ldap" title="result"></p>]]></content>
<summary type="html">
<p>定义:LDAP是基于TCP/IP协议的目录访问协议,是Internet上目录服务的通用访问协议。LDAP的出现简化了X.500目录的复杂度,降低了开发成本,是X.500标准的目录访问协议DAP的子集,同时也作为IETF的一个正式标准。LDAP的核心规范在RFC中都有定义,所有与LDAP相关的RFC都可以在LDAPman RFC网页中找到。</p>
</summary>
<category term="java" scheme="http://www.guowenbo.top/tags/java/"/>
<category term="ladp" scheme="http://www.guowenbo.top/tags/ladp/"/>
</entry>
<entry>
<title>SpringBoot 5.邮件服务</title>
<link href="http://www.guowenbo.top/2018/06/22/SpringBoot%205.%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1/"/>
<id>http://www.guowenbo.top/2018/06/22/SpringBoot 5.邮件服务/</id>
<published>2018-06-22T08:17:38.000Z</published>
<updated>2019-01-03T09:43:41.568Z</updated>
<content type="html"><![CDATA[<h1 id="SpringBoot-邮件发送"><a href="#SpringBoot-邮件发送" class="headerlink" title="SpringBoot 邮件发送"></a>SpringBoot 邮件发送</h1><blockquote><p>目的为了实现忘记密码发送邮件,点击邮件链接验证重置密码(申请邮箱需要开启POP3/IMAP/SMTP服务,如用QQ邮箱密码为授权码不是登录密码),下面以163邮箱为例。</p></blockquote><a id="more"></a><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><ol><li><p>pom</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><dependency></span><br><span class="line"><groupId>org.springframework.boot</groupId></span><br><span class="line"><artifactId>spring-boot-starter-mail</artifactId></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">spring.mail.host=smtp.163.com //邮箱服务器地址</span><br><span class="line">spring.mail.username=xxx@163.com //用户名</span><br><span class="line">spring.mail.password=xxx //密码</span><br><span class="line">spring.mail.default-encoding=UTF-8</span><br><span class="line">spring.mail.port=587</span><br><span class="line"></span><br><span class="line">spring.mail.properties.mail.smtp.auth=true</span><br><span class="line">spring.mail.properties.mail.smtp.starttls.enable=true</span><br><span class="line">spring.mail.properties.mail.smtp.starttls.required=true</span><br><span class="line">spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory</span><br><span class="line">spring.mail.properties.mail.smtp.socketFactory.port=465</span><br></pre></td></tr></table></figure></li><li><p>实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> JavaMailSender javaMailSender;</span><br><span class="line"></span><br><span class="line"><span class="comment">//发送人</span></span><br><span class="line"><span class="meta">@Value</span>(<span class="string">"${spring.mail.username}"</span>)</span><br><span class="line"><span class="keyword">private</span> String fromEmail;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* 发送普通邮件(收件人,主题,内容)</span></span><br><span class="line"><span class="comment">* */</span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">sendEmail</span><span class="params">(String toEmail,String subject,String content)</span></span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> SimpleMailMessage smm = <span class="keyword">new</span> SimpleMailMessage();</span><br><span class="line"> smm.setFrom(fromEmail);</span><br><span class="line"> smm.setTo(toEmail);</span><br><span class="line"> <span class="comment">/* String[] adds = {"xxx@qq.com","yyy@qq.com"}; //同时发送给多人</span></span><br><span class="line"><span class="comment"> smm.setTo(adds);*/</span></span><br><span class="line"> smm.setSubject(subject);<span class="comment">//设置主题</span></span><br><span class="line"> smm.setText(content);<span class="comment">//设置内容</span></span><br><span class="line"> javaMailSender.send(smm);<span class="comment">//执行发送邮件</span></span><br><span class="line"> logger.info(<span class="string">"html email send success! toEmail:[{}]"</span>,toEmail);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> logger.error(<span class="string">"sendEmail error,toEmail:[{}]"</span>,toEmail,e);</span><br><span class="line"> <span class="keyword">return</span> Boolean.FALSE;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> Boolean.TRUE;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* 发送html邮件(收件人,主题,内容)</span></span><br><span class="line"><span class="comment">* */</span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">sendHtmlEmail</span><span class="params">(String toEmail,String subject,String content)</span></span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> MimeMessage mimeMessage = javaMailSender.createMimeMessage();</span><br><span class="line"> <span class="comment">//true表示需要创建一个multipart message</span></span><br><span class="line"> MimeMessageHelper helper = <span class="keyword">new</span> MimeMessageHelper(mimeMessage, <span class="keyword">true</span>);</span><br><span class="line"> helper.setFrom(fromEmail);</span><br><span class="line"> helper.setTo(toEmail);</span><br><span class="line"> helper.setSubject(subject);</span><br><span class="line"> helper.setText(content, <span class="keyword">true</span>);</span><br><span class="line"> javaMailSender.send(mimeMessage);</span><br><span class="line"> logger.info(<span class="string">"html email send success! toEmail:[{}]"</span>,toEmail);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> logger.error(<span class="string">"sendEmail error,toEmail:[{}]"</span>,toEmail,e);</span><br><span class="line"> <span class="keyword">return</span> Boolean.FALSE;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> Boolean.TRUE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/forgotPassword"</span>,method = RequestMethod.POST)</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">forgotPassword</span><span class="params">(String email,HttpServletRequest request)</span></span>{</span><br><span class="line"> User user = userService.findByEmail(email);</span><br><span class="line"> String key = MD5Util.encrypt(user.getId()+<span class="string">"_"</span>+UUID.randomUUID().toString()+<span class="string">"_"</span>+user.getEmail()); <span class="comment">// 密钥</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//设置秘钥10分钟有效期</span></span><br><span class="line"> redisUtil.set(String.valueOf(user.getId()),key,<span class="number">10L</span>,MINUTES);</span><br><span class="line"></span><br><span class="line"> String url = <span class="string">"http://"</span> + request.getServerName() + <span class="string">":"</span>+ request.getServerPort()+ request.getContextPath() + <span class="string">"/user/newPassword"</span>+ <span class="string">"?email="</span>+user.getEmail()+<span class="string">"&key="</span>+key;</span><br><span class="line"> String content =<span class="string">"<br/>请点击下方链接重置您的账号密码↓↓↓↓↓ <br/>"</span>+<span class="string">"<a href='"</span>+url+<span class="string">"' ><pre>"</span>+url+<span class="string">"</pre></a><br/>"</span>;</span><br><span class="line"></span><br><span class="line"> sendHtmlEmail(email,<span class="string">"[重置密码]"</span>,content);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>除了发送普通邮件html邮件,还能发送带图片,附件,或是通过模板来发送邮件,不一一列举。</p>]]></content>
<summary type="html">
<h1 id="SpringBoot-邮件发送"><a href="#SpringBoot-邮件发送" class="headerlink" title="SpringBoot 邮件发送"></a>SpringBoot 邮件发送</h1><blockquote>
<p>目的为了实现忘记密码发送邮件,点击邮件链接验证重置密码(申请邮箱需要开启POP3/IMAP/SMTP服务,如用QQ邮箱密码为授权码不是登录密码),下面以163邮箱为例。</p>
</blockquote>
</summary>
<category term="springboot" scheme="http://www.guowenbo.top/categories/springboot/"/>
<category term="springboot" scheme="http://www.guowenbo.top/tags/springboot/"/>
</entry>
<entry>
<title>SpringBoot 4.集成redis</title>
<link href="http://www.guowenbo.top/2018/06/22/SpringBoot%204.%E9%9B%86%E6%88%90redis/"/>
<id>http://www.guowenbo.top/2018/06/22/SpringBoot 4.集成redis/</id>
<published>2018-06-22T07:57:59.000Z</published>
<updated>2019-01-03T09:43:41.567Z</updated>
<content type="html"><![CDATA[<h1 id="redis"><a href="#redis" class="headerlink" title="redis"></a>redis</h1><h2 id="redis介绍"><a href="#redis介绍" class="headerlink" title="redis介绍"></a>redis介绍</h2><blockquote><p>Redis是目前业界使用最广泛的内存数据存储。相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化。除此之外,Redis还提供一些类数据库的特性,比如事务,HA,主从库。可以说Redis兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。本文介绍Redis在Spring Boot中两个典型的应用场景。<br><a id="more"></a></p></blockquote><h2 id="redis配置"><a href="#redis配置" class="headerlink" title="redis配置"></a>redis配置</h2><ol><li><p>引入pom</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><dependency> </span><br><span class="line"> <groupId>org.springframework.boot</groupId> </span><br><span class="line"> <artifactId>spring-boot-starter-redis</artifactId> </span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure></li><li><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"># REDIS (RedisProperties)</span><br><span class="line"># Redis数据库索引(默认为0)</span><br><span class="line">spring.redis.database=0 </span><br><span class="line"># Redis服务器地址</span><br><span class="line">spring.redis.host=192.168.0.58</span><br><span class="line"># Redis服务器连接端口</span><br><span class="line">spring.redis.port=6379 </span><br><span class="line"># Redis服务器连接密码(默认为空)</span><br><span class="line">spring.redis.password= </span><br><span class="line"># 连接池最大连接数(使用负值表示没有限制)</span><br><span class="line">spring.redis.pool.max-active=8 </span><br><span class="line"># 连接池最大阻塞等待时间(使用负值表示没有限制)</span><br><span class="line">spring.redis.pool.max-wait=-1 </span><br><span class="line"># 连接池中的最大空闲连接</span><br><span class="line">spring.redis.pool.max-idle=8 </span><br><span class="line"># 连接池中的最小空闲连接</span><br><span class="line">spring.redis.pool.min-idle=0 </span><br><span class="line"># 连接超时时间(毫秒)</span><br><span class="line">spring.redis.timeout=0</span><br></pre></td></tr></table></figure></li><li><p>添加cache的配置类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisConfig</span> <span class="keyword">extends</span> <span class="title">CachingConfigurerSupport</span></span>{</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${spring.redis.host}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String host;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${spring.redis.port}"</span>)</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> port;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${spring.redis.timeout}"</span>)</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> timeout;</span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> KeyGenerator <span class="title">wiselyKeyGenerator</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> KeyGenerator() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">generate</span><span class="params">(Object target, Method method, Object... params)</span> </span>{</span><br><span class="line"> StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> sb.append(target.getClass().getName());</span><br><span class="line"> sb.append(method.getName());</span><br><span class="line"> <span class="keyword">for</span> (Object obj : params) {</span><br><span class="line"> sb.append(obj.toString());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sb.toString();</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> JedisConnectionFactory <span class="title">redisConnectionFactory</span><span class="params">()</span> </span>{</span><br><span class="line"> JedisConnectionFactory factory = <span class="keyword">new</span> JedisConnectionFactory();</span><br><span class="line"> factory.setHostName(host);</span><br><span class="line"> factory.setPort(port);</span><br><span class="line"> factory.setTimeout(timeout); <span class="comment">//设置连接超时时间</span></span><br><span class="line"> <span class="keyword">return</span> factory;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> RedisTemplate<String, String> <span class="title">redisTemplate</span><span class="params">(RedisConnectionFactory factory)</span> </span>{</span><br><span class="line"> StringRedisTemplate template = <span class="keyword">new</span> StringRedisTemplate(factory);</span><br><span class="line"> setSerializer(template); <span class="comment">//设置序列化工具,这样ReportBean不需要实现Serializable接口</span></span><br><span class="line"> template.afterPropertiesSet();</span><br><span class="line"> <span class="keyword">return</span> template;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setSerializer</span><span class="params">(StringRedisTemplate template)</span> </span>{</span><br><span class="line"> Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = <span class="keyword">new</span> Jackson2JsonRedisSerializer(Object.class);</span><br><span class="line"> ObjectMapper om = <span class="keyword">new</span> ObjectMapper();</span><br><span class="line"> om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);</span><br><span class="line"> om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);</span><br><span class="line"> jackson2JsonRedisSerializer.setObjectMapper(om);</span><br><span class="line"> template.setValueSerializer(jackson2JsonRedisSerializer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>utils</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisUtil</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 写入缓存</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">set</span><span class="params">(<span class="keyword">final</span> String key, Object value)</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> result = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();</span><br><span class="line"> operations.set(key, value);</span><br><span class="line"> result = <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 写入缓存设置时效时间</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">set</span><span class="params">(<span class="keyword">final</span> String key, Object value, Long expireTime ,TimeUnit timeUnit)</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> result = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();</span><br><span class="line"> operations.set(key, value);</span><br><span class="line"> redisTemplate.expire(key, expireTime, timeUnit);</span><br><span class="line"> result = <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 批量删除对应的value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> keys</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> String... keys)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (String key : keys) {</span><br><span class="line"> remove(key);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 批量删除key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> pattern</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removePattern</span><span class="params">(<span class="keyword">final</span> String pattern)</span> </span>{</span><br><span class="line"> Set<Serializable> keys = redisTemplate.keys(pattern);</span><br><span class="line"> <span class="keyword">if</span> (keys.size() > <span class="number">0</span>){</span><br><span class="line"> redisTemplate.delete(keys);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 删除对应的value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> String key)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (exists(key)) {</span><br><span class="line"> redisTemplate.delete(key);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 判断缓存中是否有对应的value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">exists</span><span class="params">(<span class="keyword">final</span> String key)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> redisTemplate.hasKey(key);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 读取缓存</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">get</span><span class="params">(<span class="keyword">final</span> String key)</span> </span>{</span><br><span class="line"> Object result = <span class="keyword">null</span>;</span><br><span class="line"> ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();</span><br><span class="line"> result = operations.get(key);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 哈希 添加</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> hashKey</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">hmSet</span><span class="params">(String key, Object hashKey, Object value)</span></span>{</span><br><span class="line"> HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();</span><br><span class="line"> hash.put(key,hashKey,value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 哈希获取数据</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> hashKey</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">hmGet</span><span class="params">(String key, Object hashKey)</span></span>{</span><br><span class="line"> HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();</span><br><span class="line"> <span class="keyword">return</span> hash.get(key,hashKey);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 列表添加</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> k</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> v</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lPush</span><span class="params">(String k,Object v)</span></span>{</span><br><span class="line"> ListOperations<String, Object> list = redisTemplate.opsForList();</span><br><span class="line"> list.rightPush(k,v);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 列表获取</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> k</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> l</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> l1</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> List<Object> <span class="title">lRange</span><span class="params">(String k, <span class="keyword">long</span> l, <span class="keyword">long</span> l1)</span></span>{</span><br><span class="line"> ListOperations<String, Object> list = redisTemplate.opsForList();</span><br><span class="line"> <span class="keyword">return</span> list.range(k,l,l1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 集合添加</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(String key,Object value)</span></span>{</span><br><span class="line"> SetOperations<String, Object> set = redisTemplate.opsForSet();</span><br><span class="line"> set.add(key,value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 集合获取</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Set<Object> <span class="title">setMembers</span><span class="params">(String key)</span></span>{</span><br><span class="line"> SetOperations<String, Object> set = redisTemplate.opsForSet();</span><br><span class="line"> <span class="keyword">return</span> set.members(key);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 有序集合添加</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> scoure</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">zAdd</span><span class="params">(String key,Object value,<span class="keyword">double</span> scoure)</span></span>{</span><br><span class="line"> ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();</span><br><span class="line"> zset.add(key,value,scoure);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 有序集合获取</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> scoure</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> scoure1</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Set<Object> <span class="title">rangeByScore</span><span class="params">(String key, <span class="keyword">double</span> scoure, <span class="keyword">double</span> scoure1)</span></span>{</span><br><span class="line"> ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();</span><br><span class="line"> <span class="keyword">return</span> zset.rangeByScore(key, scoure, scoure1);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><h2 id="redis使用"><a href="#redis使用" class="headerlink" title="redis使用"></a>redis使用</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisUtil redisUtil;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRedis</span><span class="params">()</span></span>{</span><br><span class="line">redisUtil.set(<span class="string">"123"</span>,<span class="string">"345"</span>,<span class="number">10L</span>,MINUTES);</span><br><span class="line">redisUtil.get(<span class="string">"123"</span>);</span><br><span class="line">redisUtil.remove(<span class="string">"123"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="redis"><a href="#redis" class="headerlink" title="redis"></a>redis</h1><h2 id="redis介绍"><a href="#redis介绍" class="headerlink" title="redis介绍"></a>redis介绍</h2><blockquote>
<p>Redis是目前业界使用最广泛的内存数据存储。相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化。除此之外,Redis还提供一些类数据库的特性,比如事务,HA,主从库。可以说Redis兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。本文介绍Redis在Spring Boot中两个典型的应用场景。<br>
</summary>
<category term="springboot" scheme="http://www.guowenbo.top/categories/springboot/"/>
<category term="springboot" scheme="http://www.guowenbo.top/tags/springboot/"/>
<category term="redis" scheme="http://www.guowenbo.top/tags/redis/"/>
</entry>
<entry>
<title>SpringBoot 3.spring-data-jpa框架</title>
<link href="http://www.guowenbo.top/2018/06/05/SpringBoot%203.spring-data-jpa%E6%A1%86%E6%9E%B6/"/>
<id>http://www.guowenbo.top/2018/06/05/SpringBoot 3.spring-data-jpa框架/</id>
<published>2018-06-05T02:40:58.000Z</published>
<updated>2019-01-03T09:43:41.567Z</updated>
<content type="html"><![CDATA[<h1 id="spring-data-jpa的使用"><a href="#spring-data-jpa的使用" class="headerlink" title="spring-data-jpa的使用"></a>spring-data-jpa的使用</h1><h2 id="spring-data-jpa介绍"><a href="#spring-data-jpa介绍" class="headerlink" title="spring-data-jpa介绍"></a>spring-data-jpa介绍</h2><h3 id="JPA是什么?"><a href="#JPA是什么?" class="headerlink" title="JPA是什么?"></a>JPA是什么?</h3><p>JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。<br>Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。</p><p>PA的总体思想和现有Hibernate、TopLink、JDO等ORM框架大体一致。总的来说,JPA包括以下3方面的技术:</p><ol><li>ORM映射元数据<br> JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;</li><li>API<br> 用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。</li><li>查询语言<br> 这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。</li></ol><a id="more"></a><h3 id="spring-data-jpa"><a href="#spring-data-jpa" class="headerlink" title="spring-data-jpa"></a>spring-data-jpa</h3><p>Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!</p><h2 id="spring-data-jpa使用"><a href="#spring-data-jpa使用" class="headerlink" title="spring-data-jpa使用"></a>spring-data-jpa使用</h2><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>添加pom依赖<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-data-jpa</artifactId></span><br><span class="line"></dependency></span><br><span class="line"><dependency></span><br><span class="line"> <groupId>mysql</groupId></span><br><span class="line"> <artifactId>mysql-connector-java</artifactId></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure></p><p>application.properties添加配置<br><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></pre></td><td class="code"><pre><span class="line">#数据库链接</span><br><span class="line">spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true</span><br><span class="line">spring.datasource.username=root</span><br><span class="line">spring.datasource.password=root</span><br><span class="line">spring.datasource.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line"></span><br><span class="line">#spring data jpa 配置</span><br><span class="line">spring.jpa.properties.hibernate.hbm2ddl.auto=update</span><br><span class="line">spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect</span><br><span class="line">spring.jpa.show-sql= true</span><br></pre></td></tr></table></figure></p><p>hbm2ddl.auto参数的作用主要用于:自动创建|更新|验证数据库表结构,有四个值:</p><blockquote><ol><li>create: 每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。</li><li>create-drop :每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。</li><li>update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 应用第一次运行起来后才会。</li><li>validate :每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。</li></ol></blockquote><p>dialect 主要是指定生成表名的存储引擎为InneoDB</p><p>show-sql 控制台打印sql,方便调试</p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><h4 id="数据库层代码"><a href="#数据库层代码" class="headerlink" title="数据库层代码"></a>数据库层代码</h4><p>BaseEntity.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> javax.persistence.*;</span><br><span class="line"><span class="keyword">import</span> java.io.Serializable;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"></span><br><span class="line"><span class="comment">//@MappedSuperclass注解(或mapped-superclass XML描述符元素)来指定映射超类。映射超类不会生成单独的表,它的映射信息作用于继承自它的实体类。</span></span><br><span class="line"><span class="meta">@MappedSuperclass</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BaseEntity</span> <span class="keyword">implements</span> <span class="title">Serializable</span></span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">6845215436041679253L</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Id</span></span><br><span class="line"> <span class="meta">@GeneratedValue</span>(strategy= GenerationType.IDENTITY)</span><br><span class="line"> <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(name= <span class="string">"is_delete"</span>, nullable = <span class="keyword">false</span>)</span><br><span class="line"> <span class="keyword">private</span> Integer isDelete = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <!-- 省略getset方法 --></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>User.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> javax.persistence.Column;</span><br><span class="line"><span class="keyword">import</span> javax.persistence.Entity;</span><br><span class="line"><span class="keyword">import</span> javax.persistence.Table;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="meta">@Table</span>(name=<span class="string">"mars_user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">extends</span> <span class="title">BaseEntity</span></span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//账户</span></span><br><span class="line"> <span class="meta">@Column</span>(name=<span class="string">"name"</span>, nullable = <span class="keyword">false</span>, unique = <span class="keyword">true</span>)</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//邮箱</span></span><br><span class="line"> <span class="meta">@Column</span>(name=<span class="string">"email"</span>, nullable = <span class="keyword">false</span>, unique = <span class="keyword">true</span>)</span><br><span class="line"> <span class="keyword">private</span> String email;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//加密密码</span></span><br><span class="line"> <span class="meta">@Column</span>(name=<span class="string">"password"</span>, nullable = <span class="keyword">false</span>)</span><br><span class="line"> <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line"> <!-- 省略getset方法 --></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h4><p>1.继承JpaRepository</p><figure class="highlight java"><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"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserRepository</span> <span class="keyword">extends</span> <span class="title">JpaRepository</span><<span class="title">User</span>, <span class="title">Long</span>></span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>2.使用JpaRepository默认方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testUser</span><span class="params">()</span></span>{</span><br><span class="line">User user = <span class="keyword">new</span> User();</span><br><span class="line">user.setName(<span class="string">"abc"</span>);</span><br><span class="line">user.setEmail(<span class="string">"abc@qq.com"</span>);</span><br><span class="line">user.setPassword(MD5Util.encrypt(<span class="string">"mimahenjiandan"</span>));</span><br><span class="line"></span><br><span class="line">userRepository.save(user);</span><br><span class="line"><span class="comment">//userRepository.findAll();</span></span><br><span class="line"><span class="comment">//userRepository.delete(user);</span></span><br><span class="line"><span class="comment">//userRepository.count();</span></span><br><span class="line"><span class="comment">//...等等</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>3.启动成功创建表与数据</p><p><img src="/images/2018-06-05/springboot_jpa_1.png" alt="spring data jpa"></p><p><img src="/images/2018-06-05/springboot_jpa_2.png" alt="spring data jpa"></p><h3 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h3><h4 id="自定义查询"><a href="#自定义查询" class="headerlink" title="自定义查询"></a>自定义查询</h4><p>自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟属性名称:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">User <span class="title">findByName</span><span class="params">(String name)</span></span>;</span><br></pre></td></tr></table></figure><p>也使用一些加一些关键字And、 Or</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">User <span class="title">findByNameOrEmail</span><span class="params">(String name, String email)</span></span>;</span><br></pre></td></tr></table></figure><p>修改、删除、统计也是类似语法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Long <span class="title">deleteById</span><span class="params">(Long id)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">Long <span class="title">countByName</span><span class="params">(String name)</span></span></span><br></pre></td></tr></table></figure><p>基本上SQL体系中的关键词都可以使用,例如:LIKE、 IgnoreCase、 OrderBy。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">List<User> <span class="title">findByEmailLike</span><span class="params">(String email)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">User <span class="title">findByNameIgnoreCase</span><span class="params">(String name)</span></span>;</span><br><span class="line"> </span><br><span class="line"><span class="function">List<User> <span class="title">findByNameOrderByEmailDesc</span><span class="params">(String email)</span></span>;</span><br></pre></td></tr></table></figure><p><strong> 具体的关键字,使用方法和生产成SQL如下表所示 </strong></p><table><thead><tr><th>Keyword</th><th style="text-align:center">Sample</th><th>JPQL snippet</th></tr></thead><tbody><tr><td>And</td><td style="text-align:center">findByLastnameAndFirstname</td><td>… where x.lastname = ?1 and x.firstname = ?2</td></tr><tr><td>Or</td><td style="text-align:center">findByLastnameOrFirstname</td><td>… where x.lastname = ?1 or x.firstname = ?2</td></tr><tr><td>Is,Equals</td><td style="text-align:center">findByFirstnameIs,findByFirstnameEquals</td><td>… where x.firstname = ?1</td></tr><tr><td>Between</td><td style="text-align:center">findByStartDateBetween</td><td>… where x.startDate between ?1 and ?2</td></tr><tr><td>LessThan</td><td style="text-align:center">findByAgeLessThan</td><td>… where x.age < ?1</td></tr><tr><td>LessThanEqual</td><td style="text-align:center">findByAgeLessThanEqual</td><td>… where x.age ⇐ ?1</td></tr><tr><td>GreaterThan</td><td style="text-align:center">findByAgeGreaterThan</td><td>… where x.age > ?1</td></tr><tr><td>GreaterThanEqual</td><td style="text-align:center">findByAgeGreaterThanEqual</td><td>… where x.age >= ?1</td></tr><tr><td>After</td><td style="text-align:center">findByStartDateAfter</td><td>… where x.startDate > ?1</td></tr><tr><td>Before</td><td style="text-align:center">findByStartDateBefore</td><td>… where x.startDate < ?1</td></tr><tr><td>IsNull</td><td style="text-align:center">findByAgeIsNull</td><td>… where x.age is null</td></tr><tr><td>IsNotNull,NotNull</td><td style="text-align:center">findByAge(Is)NotNull</td><td>… where x.age not null</td></tr><tr><td>Like</td><td style="text-align:center">findByFirstnameLike</td><td>… where x.firstname like ?1</td></tr><tr><td>NotLike</td><td style="text-align:center">findByFirstnameNotLike</td><td>… where x.firstname not like ?1</td></tr><tr><td>StartingWith</td><td style="text-align:center">findByFirstnameStartingWith</td><td>… where x.firstname like ?1 (parameter bound with appended %)</td></tr><tr><td>EndingWith</td><td style="text-align:center">findByFirstnameEndingWith</td><td>… where x.firstname like ?1 (parameter bound with prepended %)</td></tr><tr><td>Containing</td><td style="text-align:center">findByFirstnameContaining</td><td>… where x.firstname like ?1 (parameter bound wrapped in %)</td></tr><tr><td>OrderBy</td><td style="text-align:center">findByAgeOrderByLastnameDesc</td><td>… where x.age = ?1 order by x.lastname desc</td></tr><tr><td>Not</td><td style="text-align:center">findByLastnameNot</td><td>… where x.lastname <> ?1</td></tr><tr><td>In</td><td style="text-align:center">findByAgeIn(Collection ages)</td><td>… where x.age in ?1</td></tr><tr><td>NotIn</td><td style="text-align:center">findByAgeNotIn(Collection age)</td><td>… where x.age not in ?1</td></tr><tr><td>TRUE</td><td style="text-align:center">findByActiveTrue()</td><td>… where x.active = true</td></tr><tr><td>FALSE</td><td style="text-align:center">findByActiveFalse()</td><td>… where x.active = false</td></tr><tr><td>IgnoreCase</td><td style="text-align:center">findByFirstnameIgnoreCase</td><td>… where UPPER(x.firstame) = UPPER(?1)</td></tr></tbody></table><h4 id="分页查询"><a href="#分页查询" class="headerlink" title="分页查询"></a>分页查询</h4><p>分页查询在实际使用中非常普遍了,spring data jpa已经帮我们实现了分页的功能,在查询的方法中,需要传入参数Pageable ,当查询中有多个参数的时候Pageable建议做为最后一个参数传入</p><blockquote><p>Pageable 是spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> page=<span class="number">1</span>,size=<span class="number">10</span>;</span><br><span class="line">Sort sort = <span class="keyword">new</span> Sort(Direction.DESC, <span class="string">"id"</span>);</span><br><span class="line">Pageable pageable = <span class="keyword">new</span> PageRequest(page, size, sort);</span><br><span class="line"></span><br><span class="line"><span class="function">Page<User> <span class="title">findALL</span><span class="params">(Pageable pageable)</span></span>;</span><br><span class="line"> </span><br><span class="line"><span class="function">Page<User> <span class="title">findByName</span><span class="params">(String name,Pageable pageable)</span></span>;</span><br></pre></td></tr></table></figure><h4 id="限制查询"><a href="#限制查询" class="headerlink" title="限制查询"></a>限制查询</h4><p>有时候我们只需要查询前N个元素,或者只取前一个实体。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">ser <span class="title">findFirstByOrderByLastnameAsc</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">User <span class="title">findTopByOrderByAgeDesc</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">Page<User> <span class="title">queryFirst10ByLastname</span><span class="params">(String lastname, Pageable pageable)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">List<User> <span class="title">findFirst10ByLastname</span><span class="params">(String lastname, Sort sort)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">List<User> <span class="title">findTop10ByLastname</span><span class="params">(String lastname, Pageable pageable)</span></span>;</span><br></pre></td></tr></table></figure><h4 id="自定义SQL查询"><a href="#自定义SQL查询" class="headerlink" title="自定义SQL查询"></a>自定义SQL查询</h4><p>其实Spring data 觉大部分的SQL都可以根据方法名定义的方式来实现,但是由于某些原因我们想使用自定义的SQL来查询,spring data也是完美支持的;在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying.也可以根据需要添加 @Transactional 对事物的支持,查询超时的设置等</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Modifying</span></span><br><span class="line"><span class="meta">@Query</span>(<span class="string">"update User u set u.name = ? where u.id = ?"</span>)</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">modifyByIdAndUserId</span><span class="params">(String name, Long id)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="meta">@Modifying</span></span><br><span class="line"><span class="meta">@Query</span>(<span class="string">"delete from User where id = ?"</span>)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">deleteByUserId</span><span class="params">(Long id)</span></span>;</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Transactional</span>(timeout = <span class="number">10</span>)</span><br><span class="line"><span class="meta">@Query</span>(<span class="string">"select u from User u where u.email = ?"</span>)</span><br><span class="line"><span class="function">User <span class="title">findByEmail</span><span class="params">(String email)</span></span>;</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="spring-data-jpa的使用"><a href="#spring-data-jpa的使用" class="headerlink" title="spring-data-jpa的使用"></a>spring-data-jpa的使用</h1><h2 id="spring-data-jpa介绍"><a href="#spring-data-jpa介绍" class="headerlink" title="spring-data-jpa介绍"></a>spring-data-jpa介绍</h2><h3 id="JPA是什么?"><a href="#JPA是什么?" class="headerlink" title="JPA是什么?"></a>JPA是什么?</h3><p>JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。<br>Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。</p>
<p>PA的总体思想和现有Hibernate、TopLink、JDO等ORM框架大体一致。总的来说,JPA包括以下3方面的技术:</p>
<ol>
<li>ORM映射元数据<br> JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;</li>
<li>API<br> 用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。</li>
<li>查询语言<br> 这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。</li>
</ol>
</summary>
<category term="springboot" scheme="http://www.guowenbo.top/categories/springboot/"/>
<category term="springboot" scheme="http://www.guowenbo.top/tags/springboot/"/>
<category term="jpa" scheme="http://www.guowenbo.top/tags/jpa/"/>
</entry>
<entry>
<title>SpringBoot 2.thymeleaf模板</title>
<link href="http://www.guowenbo.top/2018/06/04/SpringBoot%202.thymeleaf%E6%A8%A1%E6%9D%BF/"/>
<id>http://www.guowenbo.top/2018/06/04/SpringBoot 2.thymeleaf模板/</id>
<published>2018-06-04T06:43:44.000Z</published>
<updated>2019-01-03T09:43:41.567Z</updated>
<content type="html"><![CDATA[<h1 id="thymeleaf的使用"><a href="#thymeleaf的使用" class="headerlink" title="thymeleaf的使用"></a>thymeleaf的使用</h1><h2 id="thymeleaf介绍"><a href="#thymeleaf介绍" class="headerlink" title="thymeleaf介绍"></a>thymeleaf介绍</h2><p>Springboot默认是不支持JSP的,默认使用thymeleaf模板引擎。</p><blockquote><p>Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等,它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。</p></blockquote><a id="more"></a><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><ol><li><p>在application.properties文件中增加Thymeleaf模板的配置。</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></pre></td><td class="code"><pre><span class="line">#thymelea模板配置</span><br><span class="line">spring.thymeleaf.prefix=classpath:/templates/</span><br><span class="line">spring.thymeleaf.suffix=.html</span><br><span class="line">spring.thymeleaf.mode=HTML5</span><br><span class="line">spring.thymeleaf.encoding=UTF-8</span><br><span class="line">spring.thymeleaf.content-type=text/html</span><br><span class="line">spring.thymeleaf.cache=false //开发时关闭缓存,不然没法看到实时页面</span><br><span class="line">spring.resources.chain.strategy.content.enabled=true</span><br><span class="line">spring.resources.chain.strategy.content.paths=/**</span><br></pre></td></tr></table></figure><p> 这些配置不是必须的,如果配置了会覆盖默认的。</p></li><li><p>pom.xml添加依赖</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-thymeleaf</artifactId></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure></li><li><p>目录结构</p><ul><li>spring-boot项目静态文件目录:/src/java/resources/static </li><li>spring-boot项目模板文件目录:/src/java/resources/templates </li><li>spring-boot静态首页的支持,即index.html放在以下目录结构会直接映射到应用的根目录下</li></ul></li></ol><h2 id="测试使用"><a href="#测试使用" class="headerlink" title="测试使用"></a>测试使用</h2><p>编写一个demo Controller</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/"</span>,method = RequestMethod.GET)</span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">index</span><span class="params">(Model model)</span></span>{</span><br><span class="line"> model.addAttribute(<span class="string">"title"</span>,<span class="string">"thymeleaf测试"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"index"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>写index.html页面</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE html></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">xmlns:th</span>=<span class="string">"http://www.thymeleaf.org"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Index!<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">p</span> <span class="attr">th:text</span>=<span class="string">"${title}"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>启动springboot 访问 <a href="http://localhost:8080" target="_blank" rel="noopener">http://localhost:8080</a> 如下图</p><p><img src="/images/2018-06-04/springboot_2_1.png" alt="thymeleaf"></p><h2 id="thymeleaf常用基础知识点"><a href="#thymeleaf常用基础知识点" class="headerlink" title="thymeleaf常用基础知识点"></a>thymeleaf常用基础知识点</h2><ol><li><p>在html页面中引入thymeleaf命名空间,即<html xmlns:th=<a href="http://www.thymeleaf.org>" target="_blank" rel="noopener">http://www.thymeleaf.org></a>,此时在html模板文件中动态的属性使用th:命名空间修饰 </p></li><li><p>引用静态资源文件,比如CSS和JS文件,语法格式为“@{}”,如@{/js/blog/blog.js}会引入/static目录下的/js/blog/blog.js文件</p></li><li><p>访问spring-mvc中model的属性,语法格式为“${}”,如${user.id}可以获取model里的user对象的id属性 </p></li><li><p>循环,在html的标签中,加入th:each=“value:${list}”形式的属性,如<span th:each="”user:${users}”"></span>可以迭代users的数据 </p></li><li><p>判断,在html标签中,加入th:if=”表达式”可以根据条件显示html元素 </p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">span</span> <span class="attr">th:if</span>=<span class="string">"${not #lists.isEmpty(blog.publishTime)}"</span>></span> </span><br><span class="line"><span class="tag"><<span class="name">span</span> <span class="attr">id</span>=<span class="string">"publishtime"</span> <span class="attr">th:text</span>=<span class="string">"${#dates.format(blog.publishTime, 'yyyy-MM-dd HH:mm:ss')}"</span>></span><span class="tag"></<span class="name">span</span>></span> </span><br><span class="line"><span class="tag"></<span class="name">span</span>></span></span><br></pre></td></tr></table></figure><p> 以上代码表示若blog.publishTime时间不为空,则显示时间</p></li><li><p>时间的格式化 </p><pre><code>${#dates.format(blog.publishTime,'yyyy-MM-dd HH:mm:ss')}</code></pre></li><li>符串拼接 th:href=”‘/blog/delete/‘ + ${blog.id }”</li></ol><blockquote><p>参考:<a href="https://blog.csdn.net/u014695188/article/details/52347318" target="_blank" rel="noopener">https://blog.csdn.net/u014695188/article/details/52347318</a></p></blockquote>]]></content>
<summary type="html">
<h1 id="thymeleaf的使用"><a href="#thymeleaf的使用" class="headerlink" title="thymeleaf的使用"></a>thymeleaf的使用</h1><h2 id="thymeleaf介绍"><a href="#thymeleaf介绍" class="headerlink" title="thymeleaf介绍"></a>thymeleaf介绍</h2><p>Springboot默认是不支持JSP的,默认使用thymeleaf模板引擎。</p>
<blockquote>
<p>Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等,它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。</p>
</blockquote>
</summary>
<category term="springboot" scheme="http://www.guowenbo.top/categories/springboot/"/>
<category term="springboot" scheme="http://www.guowenbo.top/tags/springboot/"/>
<category term="thymeleaf" scheme="http://www.guowenbo.top/tags/thymeleaf/"/>
</entry>
<entry>
<title>GITHUB:协同开发</title>
<link href="http://www.guowenbo.top/2018/06/04/GITHUB%EF%BC%9A%E5%8D%8F%E5%90%8C%E5%BC%80%E5%8F%91/"/>
<id>http://www.guowenbo.top/2018/06/04/GITHUB:协同开发/</id>
<published>2018-06-04T05:36:43.000Z</published>
<updated>2019-01-03T09:43:41.558Z</updated>
<content type="html"><![CDATA[<h1 id="如何将项目代码上传到github管理"><a href="#如何将项目代码上传到github管理" class="headerlink" title="如何将项目代码上传到github管理"></a>如何将项目代码上传到github管理</h1><h2 id="下载git"><a href="#下载git" class="headerlink" title="下载git"></a>下载git</h2><p>官网 <a href="https://git-scm.com/downloads" target="_blank" rel="noopener">链接</a></p><p>下载完成一直next即可。</p><p>任何文件夹下,点鼠标右键会多出一些菜单<br>如 Git Init Hear、Git Bash、Git Gui , 说明安装成功。</p><h2 id="注册github账户"><a href="#注册github账户" class="headerlink" title="注册github账户"></a>注册github账户</h2><p>官网注册 <a href="https://github.com/" target="_blank" rel="noopener">官网</a></p><a id="more"></a><h2 id="创建github仓库"><a href="#创建github仓库" class="headerlink" title="创建github仓库"></a>创建github仓库</h2><p>页面上方用户菜单上选择 “+”->New repository 创建一个新的仓库来协同开发</p><p><img src="/images/2018-06-04/github_1.png" alt="github"></p><h2 id="创建本地仓库"><a href="#创建本地仓库" class="headerlink" title="创建本地仓库"></a>创建本地仓库</h2><ol><li><p>在本地需要上传项目的目录下鼠标右键,选择:Git Bash Here,执行命令 git init</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br></pre></td></tr></table></figure><blockquote><p>Git 使用 git init 命令来初始化一个 Git 仓库,Git 的很多命令都需要在 Git 的仓库中运行,所以 git init 是使用 Git 的第一个命令。在执行完成 git init 命令后,Git 仓库会生成一个 .git 目录,该目录包含了资源的所有元数据,其他的项目目录保持不变(不像 SVN 会在每个子目录生成 .svn 目录,Git 只在仓库的根目录生成 .git 目录)。</p></blockquote><p> 输入指令之后,回车,你就会看到的项目路径下会多出了一个.git文件夹</p></li><li><p>创建 SSH key</p><p> 众所周知ssh是加密传输。加密传输的算法有好多,git可使用rsa,rsa要解决的一个核心问题是,如何使用一对特定的数字,使其中一个数字可以用来加密,而另外一个数字可以用来解密。这两个数字就是你在使用git和github的时候所遇到的public key也就是公钥以及privatekey私钥。其中,公钥就是那个用来加密的数字,这也就是为什么你在本机生成了公钥之后,要上传到github的原因。从github发回来的,用那公钥加密过的数据,可以用你本地的私钥来还原。如果你的key丢失了,不管是公钥还是私钥,丢失一个都不能用了,解决方法也很简单,重新再生成一次,然后在github.com里再设置一次就行。</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C <span class="string">"youremail@example.com"</span> //youremail@example.com是在github注册时候使用的邮箱</span><br></pre></td></tr></table></figure><p> 出现的步骤一直回车即可(如下图:)<br> <img src="/images/2018-05-31/hexo_github_ssh.png" alt="github-ssh" title="SSH"></p><p> 这样在 /c/Users/用户名/.ssh/id_rsa文件中就生成了公钥</p></li><li><p>配置github账户的ssh key</p><p> 拷贝 /c/Users/用户名/.ssh/id_rsa 的公钥内容,进入你的github设置Settings<br> <img src="/images/2018-05-31/hexo_github_ssh_1.png" alt="github-ssh" title="SSH"></p><p> 将公钥贴到Key中,title可以为空<br> <img src="/images/2018-05-31/hexo_github_ssh_2.png" alt="github-ssh" title="SSH"></p><p> 添加好之后验证</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh -T git@github.com</span><br></pre></td></tr></table></figure><p> 提示信息为下面语句说明key已经配置好了</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hi github_user_name! You<span class="string">'ve successfully authenticated, but GitHub does not provide shell access.</span></span><br></pre></td></tr></table></figure></li></ol><h2 id="项目上传github"><a href="#项目上传github" class="headerlink" title="项目上传github"></a>项目上传github</h2><ol><li><p>把本地仓库传到github上去,在此之前还需要设置username和email,因为github每次commit都会记录他们</p> <figure class="highlight bash"><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">$ git config --global user.name <span class="string">"姓名"</span></span><br><span class="line">$ git config --global user.email <span class="string">"邮箱"</span></span><br></pre></td></tr></table></figure></li><li><p>进入要上传的仓库,右键git bash,添加远程地址</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote add origin git@github.com:yourName/yourRepository.git</span><br></pre></td></tr></table></figure><p> 后面的yourName和yourRepo表示你再github的用户名和刚才新建的仓库,加完之后进入.git,打开config,这里会多出一个remote “origin”内容,这就是刚才添加的远程地址,也可以直接修改config来配置远程地址。</p></li><li><p>github上传,更新项目</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git add -A // 也可以单独提交文件, -A改成文件即可</span><br><span class="line">git commit -m <span class="string">"test commit"</span> // -m 添加提交注释</span><br><span class="line">git push origin master</span><br></pre></td></tr></table></figure></li></ol><h2 id="gitignore文件"><a href="#gitignore文件" class="headerlink" title="gitignore文件"></a>gitignore文件</h2><p>.gitignore顾名思义就是告诉git需要忽略的文件,这是一个很重要并且很实用的文件。一般我们写完代码后会执行编译、调试等操作,这期间会产生很多中间文件和可执行文件,这些都不是代码文件,是不需要git来管理的。我们在git status的时候会看到很多这样的文件,如果用git add -A来添加的话会把他们都加进去,而手动一个个添加的话也太麻烦了。这时我们就需要.gitignore了。</p><h1 id="github上协作开发"><a href="#github上协作开发" class="headerlink" title="github上协作开发"></a>github上协作开发</h1><h2 id="添加github账户"><a href="#添加github账户" class="headerlink" title="添加github账户"></a>添加github账户</h2><p>进入项目的仓库,Settings —》 Collaborators 添加github账户,如下图</p><p><img src="/images/2018-06-04/github_2.png" alt="github" title="SSH"></p><p>会向上图填写的github账户绑定的邮箱发送邀请的邮件,点击链接跳转网页,点击accept invitation就可以了。</p><p>同样配置SSH Key(配置方法见上面)</p><p>下载项目<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> 项目路径</span><br></pre></td></tr></table></figure></p><p>更改文件提交先更新最新代码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git pull</span><br><span class="line">git add 更改的文件</span><br><span class="line">git commit -m <span class="string">"update test"</span></span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>回到Leader的账号,可以看到刚才提交的东西已经同步到了该远程仓库 </p><blockquote><p>参考:<a href="http://blog.csdn.net/gpwner/article/details/52829187" target="_blank" rel="noopener">http://blog.csdn.net/gpwner/article/details/52829187</a></p></blockquote>]]></content>
<summary type="html">
<h1 id="如何将项目代码上传到github管理"><a href="#如何将项目代码上传到github管理" class="headerlink" title="如何将项目代码上传到github管理"></a>如何将项目代码上传到github管理</h1><h2 id="下载git"><a href="#下载git" class="headerlink" title="下载git"></a>下载git</h2><p>官网 <a href="https://git-scm.com/downloads" target="_blank" rel="noopener">链接</a></p>
<p>下载完成一直next即可。</p>
<p>任何文件夹下,点鼠标右键会多出一些菜单<br>如 Git Init Hear、Git Bash、Git Gui , 说明安装成功。</p>
<h2 id="注册github账户"><a href="#注册github账户" class="headerlink" title="注册github账户"></a>注册github账户</h2><p>官网注册 <a href="https://github.com/" target="_blank" rel="noopener">官网</a></p>
</summary>
<category term="github" scheme="http://www.guowenbo.top/categories/github/"/>
<category term="github" scheme="http://www.guowenbo.top/tags/github/"/>
<category term="git" scheme="http://www.guowenbo.top/tags/git/"/>
</entry>
</feed>