forked from itwanger/toBeBetterJavaer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThreadLocal.html
More file actions
306 lines (287 loc) · 117 KB
/
ThreadLocal.html
File metadata and controls
306 lines (287 loc) · 117 KB
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
<!DOCTYPE html>
<html lang="zh-CN" data-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="generator" content="VuePress 2.0.0-beta.60" />
<meta name="theme" content="VuePress Theme Hope" />
<meta name="keywords" content="Java,并发编程,多线程,Thread,ThreadLocal"><meta property="og:url" content="https://tobebetterjavaer.com/thread/ThreadLocal.html"><meta property="og:site_name" content="Java程序员进阶之路"><meta property="og:title" content="吊打Java并发面试官之ThreadLocal"><meta property="og:description" content="吊打Java并发面试官之ThreadLocal"><meta property="og:type" content="article"><meta property="og:locale" content="zh-CN"><meta property="og:updated_time" content="2023-04-10T14:14:27.000Z"><meta property="article:tag" content="Java并发编程"><meta property="article:modified_time" content="2023-04-10T14:14:27.000Z"><script type="application/ld+json">{"@context":"https://schema.org","@type":"Article","headline":"吊打Java并发面试官之ThreadLocal","image":[""],"dateModified":"2023-04-10T14:14:27.000Z","author":[]}</script><meta name="robots" content="all"><meta name="author" content="沉默王二"><meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"><meta http-equiv="Pragma" content="no-cache"><meta http-equiv="Expires" content="0"><meta name="apple-mobile-web-app-capable" content="yes"><script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?5230ac143650bf5eb3c14f3fb9b1d3ec";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script><link rel="stylesheet" href="//at.alicdn.com/t/font_3180624_7cy10l7jqqh.css"><link rel="icon" href="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/favicon.ico"><link rel="icon" href="/assets/icon/chrome-mask-512.png" type="image/png" sizes="512x512"><link rel="icon" href="/assets/icon/chrome-mask-192.png" type="image/png" sizes="192x192"><link rel="icon" href="/assets/icon/chrome-512.png" type="image/png" sizes="512x512"><link rel="icon" href="/assets/icon/chrome-192.png" type="image/png" sizes="192x192"><link rel="manifest" href="/manifest.webmanifest" crossorigin="use-credentials"><meta name="theme-color" content="#096dd9"><link rel="apple-touch-icon" href="/assets/icon/apple-icon-152.png"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="msapplication-TileImage" content="/assets/icon/ms-icon-144.png"><meta name="msapplication-TileColor" content="#ffffff"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><title>吊打Java并发面试官之ThreadLocal | Java程序员进阶之路</title><meta name="description" content="吊打Java并发面试官之ThreadLocal">
<style>
:root {
--bg-color: #fff;
}
html[data-theme="dark"] {
--bg-color: #1d1e1f;
}
html,
body {
background: var(--bg-color);
}
</style>
<script>
const userMode = localStorage.getItem("vuepress-theme-hope-scheme");
const systemDarkMode =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
if (userMode === "dark" || (userMode !== "light" && systemDarkMode)) {
document.documentElement.setAttribute("data-theme", "dark");
}
</script>
<link rel="preload" href="/assets/style-a53bc671.css" as="style"><link rel="stylesheet" href="/assets/style-a53bc671.css">
<link rel="modulepreload" href="/assets/app-736b5df9.js"><link rel="modulepreload" href="/assets/framework-bb7be5cb.js"><link rel="modulepreload" href="/assets/ThreadLocal.html-c5c2c651.js"><link rel="modulepreload" href="/assets/ThreadLocal.html-b1f22fc6.js">
</head>
<body>
<div id="app"><!--[--><!--[--><!--[--><span tabindex="-1"></span><a href="#main-content" class="skip-link sr-only">跳至主要內容</a><!--]--><div class="theme-container has-toc"><!--[--><header class="navbar" id="navbar"><div class="navbar-start"><button class="toggle-sidebar-button" title="Toggle Sidebar"><span class="icon"></span></button><!--[--><!----><!--]--><a href="/" class="brand"><img class="logo" src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/logo-02.png" alt="Java程序员进阶之路"><!----><span class="site-name hide-in-pad">Java程序员进阶之路</span></a><!--[--><!----><!--]--></div><div class="navbar-center"><!--[--><!----><!--]--><nav class="nav-links"><div class="nav-item hide-in-mobile"><a href="/blog.html" class="nav-link" aria-label="博客"><span class="font-icon icon iconfont icon-gaishu" style=""></span>博客<!----></a></div><div class="nav-item hide-in-mobile"><a href="/home.html" class="nav-link" aria-label="进阶之路"><span class="font-icon icon iconfont icon-lujing" style=""></span>进阶之路<!----></a></div><div class="nav-item hide-in-mobile"><a href="/zhishixingqiu/" class="nav-link" aria-label="知识星球"><span class="font-icon icon iconfont icon-Artboard" style=""></span>知识星球<!----></a></div><div class="nav-item hide-in-mobile"><a href="/xuexiluxian/" class="nav-link" aria-label="学习路线"><span class="font-icon icon iconfont icon-luxian" style=""></span>学习路线<!----></a></div><div class="nav-item hide-in-mobile"><div class="dropdown-wrapper"><button class="dropdown-title" type="button" aria-label="珍藏资源"><span class="title"><span class="font-icon icon iconfont icon-youzhi" style=""></span>珍藏资源</span><span class="arrow"></span><ul class="nav-dropdown"><li class="dropdown-item"><a href="/pdf/" class="nav-link" aria-label="PDF下载"><span class="font-icon icon iconfont icon-java" style=""></span>PDF下载<!----></a></li><li class="dropdown-item"><a href="/sidebar/sanfene/nixi.html" class="nav-link" aria-label="面渣逆袭"><span class="font-icon icon iconfont icon-zhunbei" style=""></span>面渣逆袭<!----></a></li><li class="dropdown-item"><a href="/nice-article/itmind/" class="nav-link" aria-label="破解合集"><span class="font-icon icon iconfont icon-zhongyaotishi" style=""></span>破解合集<!----></a></li></ul></button></div></div><div class="nav-item hide-in-mobile"><a href="https://space.bilibili.com/513340480" rel="noopener noreferrer" target="_blank" aria-label="B站视频" class="nav-link"><span class="font-icon icon iconfont icon-bzhan" style=""></span>B站视频<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span><!----></a></div></nav><!--[--><!----><!--]--></div><div class="navbar-end"><!--[--><!----><!--]--><!----><div class="nav-item"><a class="repo-link" href="https://github.com/itwanger/toBeBetterJavaer" target="_blank" rel="noopener noreferrer" aria-label="GitHub"><svg xmlns="http://www.w3.org/2000/svg" class="icon github-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="github icon" style="width:1.25rem;height:1.25rem;vertical-align:middle;"><path d="M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"></path></svg></a></div><div class="nav-item hide-in-mobile"><button class="outlook-button" tabindex="-1" ariahidden="true"><svg xmlns="http://www.w3.org/2000/svg" class="icon outlook-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="outlook icon"><path d="M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4 38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32 51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0 102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2 6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4 0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2 9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224 419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4 470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0 22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6 12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128 505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2 16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8 86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4 80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6 6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"></path></svg><div class="outlook-dropdown"><!----></div></button></div><div id="docsearch-container"></div><!--[--><!----><!--]--><button class="toggle-navbar-button" aria-label="Toggle Navbar" aria-expanded="false" aria-controls="nav-screen"><span class="button-container"><span class="button-top"></span><span class="button-middle"></span><span class="button-bottom"></span></span></button></div></header><!----><!--]--><!----><div class="toggle-sidebar-wrapper"><span class="arrow start"></span></div><aside class="sidebar" id="sidebar"><!--[--><!----><!--]--><ul class="sidebar-links"><li><!--[--><a href="/home.html" class="nav-link sidebar-link sidebar-page" aria-label="一、前言"><!---->一、前言<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><section class="sidebar-group"><button class="sidebar-heading clickable active"><!----><span class="title">二、Java基础</span><span class="arrow down"></span></button><ul class="sidebar-links"><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.1 Java概述及环境配置</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.2 Java语法基础</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.3 数组&字符串</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.4 面向对象编程</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.5 集合框架(容器)</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.6 Java IO</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.7 异常处理</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.8 常用工具类</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.9 Java新特性</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.10 网络编程</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.11 NIO</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.12 Java重要知识点</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable active"><!----><span class="title">2.13 并发编程</span><span class="arrow down"></span></button><ul class="sidebar-links"><li><!--[--><a href="/thread/wangzhe-thread.html" class="nav-link sidebar-link sidebar-page" aria-label="初步掌握 Java 多线程"><!---->初步掌握 Java 多线程<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/callable-future-futuretask.html" class="nav-link sidebar-link sidebar-page" aria-label="获取线程执行结果"><!---->获取线程执行结果<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/thread-state-and-method.html" class="nav-link sidebar-link sidebar-page" aria-label="Java线程的6种状态"><!---->Java线程的6种状态<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/thread-group-and-thread-priority.html" class="nav-link sidebar-link sidebar-page" aria-label="线程组和线程优先级"><!---->线程组和线程优先级<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/why-need-thread.html" class="nav-link sidebar-link sidebar-page" aria-label="进程与线程的区别是什么?"><!---->进程与线程的区别是什么?<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/thread-bring-some-problem.html" class="nav-link sidebar-link sidebar-page" aria-label="并发编程带来了哪些问题?"><!---->并发编程带来了哪些问题?<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/jmm.html" class="nav-link sidebar-link sidebar-page" aria-label="全面理解Java的内存模型(JMM)"><!---->全面理解Java的内存模型(JMM)<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/volatile.html" class="nav-link sidebar-link sidebar-page" aria-label="volatile关键字解析"><!---->volatile关键字解析<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/synchronized-1.html" class="nav-link sidebar-link sidebar-page" aria-label="synchronized关键字"><!---->synchronized关键字<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/synchronized.html" class="nav-link sidebar-link sidebar-page" aria-label="synchronized锁的到底是什么?"><!---->synchronized锁的到底是什么?<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/cas.html" class="nav-link sidebar-link sidebar-page" aria-label="Java实现CAS的原理"><!---->Java实现CAS的原理<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/aqs.html" class="nav-link sidebar-link sidebar-page" aria-label="Java并发AQS详解"><!---->Java并发AQS详解<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/lock.html" class="nav-link sidebar-link sidebar-page" aria-label="大致了解下Java的锁接口和锁"><!---->大致了解下Java的锁接口和锁<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/suo.html" class="nav-link sidebar-link sidebar-page" aria-label="不可不说的Java“锁”事"><!---->不可不说的Java“锁”事<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/pianxiangsuo.html" class="nav-link sidebar-link sidebar-page" aria-label="Java15终于把难搞的偏向锁移除了"><!---->Java15终于把难搞的偏向锁移除了<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/reentrantLock.html" class="nav-link sidebar-link sidebar-page" aria-label="重入锁ReentrantLock"><!---->重入锁ReentrantLock<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/ReentrantReadWriteLock.html" class="nav-link sidebar-link sidebar-page" aria-label="读写锁ReentrantReadWriteLock"><!---->读写锁ReentrantReadWriteLock<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/condition.html" class="nav-link sidebar-link sidebar-page" aria-label="线程协作类Condition"><!---->线程协作类Condition<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/LockSupport.html" class="nav-link sidebar-link sidebar-page" aria-label="线程阻塞唤醒类LockSupport"><!---->线程阻塞唤醒类LockSupport<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/map.html" class="nav-link sidebar-link sidebar-page" aria-label="简单聊聊Java的并发集合容器"><!---->简单聊聊Java的并发集合容器<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/ConcurrentHashMap.html" class="nav-link sidebar-link sidebar-page" aria-label="ConcurrentHashMap"><!---->ConcurrentHashMap<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/ConcurrentLinkedQueue.html" class="nav-link sidebar-link sidebar-page" aria-label="ConcurrentLinkedQueue"><!---->ConcurrentLinkedQueue<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/CopyOnWriteArrayList.html" class="nav-link sidebar-link sidebar-page" aria-label="CopyOnWriteArrayList"><!---->CopyOnWriteArrayList<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a aria-current="page" href="/thread/ThreadLocal.html" class="router-link-active router-link-exact-active nav-link active sidebar-link sidebar-page active" aria-label="ThreadLocal"><!---->ThreadLocal<!----></a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocal-的简介" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="ThreadLocal 的简介"><!---->ThreadLocal 的简介<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocal-的实现原理" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="ThreadLocal 的实现原理"><!---->ThreadLocal 的实现原理<!----></a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#set-方法" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="set() 方法"><!---->set() 方法<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#get-方法" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="get() 方法"><!---->get() 方法<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#remove-方法" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="remove() 方法"><!---->remove() 方法<!----></a><ul class="sidebar-sub-headers"></ul></li></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocalmap-详解" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="ThreadLocalMap 详解"><!---->ThreadLocalMap 详解<!----></a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#entry-数据结构" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="Entry 数据结构"><!---->Entry 数据结构<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#set-方法-1" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="set 方法"><!---->set 方法<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#getentry-方法" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="getEntry 方法"><!---->getEntry 方法<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#remove-方法-1" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="remove 方法"><!---->remove 方法<!----></a><ul class="sidebar-sub-headers"></ul></li></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocal-的使用场景" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="ThreadLocal 的使用场景"><!---->ThreadLocal 的使用场景<!----></a><ul class="sidebar-sub-headers"></ul></li></ul><!--]--></li><li><!--[--><a href="/thread/BlockingQueue.html" class="nav-link sidebar-link sidebar-page" aria-label="BlockingQueue"><!---->BlockingQueue<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/pool.html" class="nav-link sidebar-link sidebar-page" aria-label="面试必问:Java 线程池"><!---->面试必问:Java 线程池<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/ScheduledThreadPoolExecutor.html" class="nav-link sidebar-link sidebar-page" aria-label="ScheduledThreadPoolExecutor"><!---->ScheduledThreadPoolExecutor<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/atomic.html" class="nav-link sidebar-link sidebar-page" aria-label="原子操作类总结"><!---->原子操作类总结<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/CountDownLatch.html" class="nav-link sidebar-link sidebar-page" aria-label="CountDownLatch"><!---->CountDownLatch<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/fork-join.html" class="nav-link sidebar-link sidebar-page" aria-label="Fork/Join框架"><!---->Fork/Join框架<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/thread/shengchanzhe-xiaofeizhe.html" class="nav-link sidebar-link sidebar-page" aria-label="从根上理解生产者-消费者模式"><!---->从根上理解生产者-消费者模式<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li></ul></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">2.14 JVM</span><span class="arrow end"></span></button><!----></section></li></ul></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">三、Java进阶</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">四、数据库</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">五、计算机基础</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">六、求职面试</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">七、学习建议</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">八、知识库搭建</span><span class="arrow end"></span></button><!----></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">九、联系作者</span><span class="arrow end"></span></button><!----></section></li></ul><!--[--><!----><!--]--></aside><!--[--><main class="page" id="main-content"><!--[--><!----><nav class="breadcrumb disable"></nav><div class="page-title"><h1><!---->吊打Java并发面试官之ThreadLocal</h1><div class="page-info"><span class="page-author-info" aria-label="作者🖊" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon author-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="author icon"><path d="M649.6 633.6c86.4-48 147.2-144 147.2-249.6 0-160-128-288-288-288s-288 128-288 288c0 108.8 57.6 201.6 147.2 249.6-121.6 48-214.4 153.6-240 288-3.2 9.6 0 19.2 6.4 25.6 3.2 9.6 12.8 12.8 22.4 12.8h704c9.6 0 19.2-3.2 25.6-12.8 6.4-6.4 9.6-16 6.4-25.6-25.6-134.4-121.6-240-243.2-288z"></path></svg><span><a class="page-author-item" href="/about-the-author/" target="_blank" rel="noopener noreferrer">沉默王二</a></span><span property="author" content="沉默王二"></span></span><!----><span class="page-date-info" aria-label="写作日期📅" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon calendar-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="calendar icon"><path d="M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"></path></svg><span><!----></span><meta property="datePublished" content="2022-03-23T12:08:58.000Z"></span><span class="page-category-info" aria-label="分类🌈" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon category-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="category icon"><path d="M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"></path></svg><span class="page-category-item category0 clickable" role="navigation">Java核心</span><meta property="articleSection" content="Java核心"></span><span class="page-tag-info" aria-label="标签🏷" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon tag-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="tag icon"><path d="M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"></path></svg><span class="page-tag-item tag6 clickable" role="navigation">Java并发编程</span><meta property="keywords" content="Java并发编程"></span><span class="page-word-info" aria-label="字数🔠" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon word-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="word icon"><path d="M518.217 432.64V73.143A73.143 73.143 0 01603.43 1.097a512 512 0 01419.474 419.474 73.143 73.143 0 01-72.046 85.212H591.36a73.143 73.143 0 01-73.143-73.143z"></path><path d="M493.714 566.857h340.297a73.143 73.143 0 0173.143 85.577A457.143 457.143 0 11371.566 117.76a73.143 73.143 0 0185.577 73.143v339.383a36.571 36.571 0 0036.571 36.571z"></path></svg><span>约 5386 字</span><meta property="wordCount" content="5386"></span><span class="page-reading-time-info" aria-label="阅读时间⌛" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon timer-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="timer icon"><path d="M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"></path></svg><span>大约 18 分钟</span><meta property="timeRequired" content="PT18M"></span></div><hr></div><div class="toc-place-holder"><aside id="toc"><!--[--><!----><!--]--><div class="toc-header">此页内容<button class="print-button" title="print"><svg xmlns="http://www.w3.org/2000/svg" class="icon print-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="print icon"><path d="M819.2 364.8h-44.8V128c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v236.8h-44.8C145.067 364.8 96 413.867 96 473.6v192c0 59.733 49.067 108.8 108.8 108.8h44.8V896c0 17.067 14.933 32 32 32h460.8c17.067 0 32-14.933 32-32V774.4h44.8c59.733 0 108.8-49.067 108.8-108.8v-192c0-59.733-49.067-108.8-108.8-108.8zM313.6 160h396.8v204.8H313.6V160zm396.8 704H313.6V620.8h396.8V864zM864 665.6c0 25.6-19.2 44.8-44.8 44.8h-44.8V588.8c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v121.6h-44.8c-25.6 0-44.8-19.2-44.8-44.8v-192c0-25.6 19.2-44.8 44.8-44.8h614.4c25.6 0 44.8 19.2 44.8 44.8v192z"></path></svg></button></div><div class="toc-wrapper"><ul class="toc-list"><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocal-的简介" class="router-link-active router-link-exact-active toc-link level2">ThreadLocal 的简介</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocal-的实现原理" class="router-link-active router-link-exact-active toc-link level2">ThreadLocal 的实现原理</a></li><ul class="toc-list"><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#set-方法" class="router-link-active router-link-exact-active toc-link level3">set() 方法</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#get-方法" class="router-link-active router-link-exact-active toc-link level3">get() 方法</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#remove-方法" class="router-link-active router-link-exact-active toc-link level3">remove() 方法</a></li><!----><!--]--></ul><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocalmap-详解" class="router-link-active router-link-exact-active toc-link level2">ThreadLocalMap 详解</a></li><ul class="toc-list"><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#entry-数据结构" class="router-link-active router-link-exact-active toc-link level3">Entry 数据结构</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#set-方法-1" class="router-link-active router-link-exact-active toc-link level3">set 方法</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#getentry-方法" class="router-link-active router-link-exact-active toc-link level3">getEntry 方法</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#remove-方法-1" class="router-link-active router-link-exact-active toc-link level3">remove 方法</a></li><!----><!--]--></ul><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/thread/ThreadLocal.html#threadlocal-的使用场景" class="router-link-active router-link-exact-active toc-link level2">ThreadLocal 的使用场景</a></li><!----><!--]--></ul></div><!--[--><!----><!--]--></aside></div><!----><div class="theme-hope-content"><h2 id="threadlocal-的简介" tabindex="-1"><a class="header-anchor" href="#threadlocal-的简介" aria-hidden="true">#</a> ThreadLocal 的简介</h2><p>在多线程编程中通常解决线程安全的问题时,我们会利用 synchronzed 或者 lock 控制线程对临界区资源的同步顺序,但是这种加锁的方式会让未获取到锁的线程进行阻塞等待,很显然这种方式的时间效率并不是特别好。</p><p><strong>线程安全问题的核心在于多个线程会对同一个临界区共享资源进行操作</strong>,那么,如果每个线程都使用自己的“共享资源”,各自使用各自的,互相不影响到彼此,即多个线程间达到隔离的状态,这样就不会出现线程安全的问题。</p><p>事实上,这就是一种“<strong>空间换时间</strong>”的方案,每个线程都拥有自己的“共享资源”无疑会让内存占用大很多,但是由于不需要同步也就减少了线程可能存在的阻塞等待,从而提高时间效率。</p><p>虽然 ThreadLocal 并不在 java.util.concurrent 包中而在 java.lang 包中,但我更倾向于把它当作是一种并发容器(虽然真正存放数据的是 ThreadLocalMap)进行归类。</p><p>顾名思义,<strong>ThreadLocal 表示线程的“本地变量”,即每个线程都拥有该变量副本,达到人手一份的效果,各用各的,这样就可以避免共享资源的竞争</strong>。</p><h2 id="threadlocal-的实现原理" tabindex="-1"><a class="header-anchor" href="#threadlocal-的实现原理" aria-hidden="true">#</a> ThreadLocal 的实现原理</h2><p>要想学习 ThreadLocal 的实现原理,就必须了解它的几个核心方法,包括怎样存怎样取等等,下面我们一个个来看。</p><h3 id="set-方法" tabindex="-1"><a class="header-anchor" href="#set-方法" aria-hidden="true">#</a> set() 方法</h3><p><strong>set 方法设置当前线程中 ThreadLocal 变量的值</strong>,该方法的源码为:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token class-name">T</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//1. 获取当前线程实例对象</span>
<span class="token class-name">Thread</span> t <span class="token operator">=</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//2. 通过当前线程实例获取到ThreadLocalMap对象</span>
<span class="token class-name">ThreadLocalMap</span> map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token comment">//3. 如果Map不为null,则以当前ThreadLocal实例为key,值为value进行存入</span>
map<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">else</span>
<span class="token comment">//4.map为null,则新建ThreadLocalMap并存入value</span>
<span class="token function">createMap</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>方法的逻辑很清晰,具体请看上面的注释。通过源码我们知道 value 是存放在 ThreadLocalMap 里的,当前先把它理解为一个普普通通的 map 即可,也就是说,<strong>数据 value 是存放在 ThreadLocalMap 这个容器中的,并且是以当前 ThreadLocal 实例为 key 的</strong>。</p><p>简单看下 ThreadLocalMap 是什么,有个简单的认识就好,后面会具体说的。</p><p><strong>首先 ThreadLocalMap 是怎样来的</strong>?源码很清楚,是通过<code>getMap(t)</code>进行获取:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token class-name">ThreadLocalMap</span> <span class="token function">getMap</span><span class="token punctuation">(</span><span class="token class-name">Thread</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name"><span class="token namespace">t<span class="token punctuation">.</span></span>ThreadLocals</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>该方法直接返回当前线程对象 t 的一个成员变量 ThreadLocals:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token comment">/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */</span>
<span class="token class-name">ThreadLocal<span class="token punctuation">.</span>ThreadLocalMap</span> <span class="token class-name">ThreadLocals</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>也就是说<strong>ThreadLocalMap 的引用是作为 Thread 的一个成员变量的,被 Thread 进行维护的</strong>。回过头再来看 set 方法,当 map 为 Null 的时候会通过<code>createMap(t,value)</code>方法 new 出来一个:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">void</span> <span class="token function">createMap</span><span class="token punctuation">(</span><span class="token class-name">Thread</span> t<span class="token punctuation">,</span> <span class="token class-name">T</span> firstValue<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name"><span class="token namespace">t<span class="token punctuation">.</span></span>ThreadLocals</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocalMap</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> firstValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>该方法就是new 一个 ThreadLocalMap 实例对象,然后同样以当前 ThreadLocal 实例作为 key,值为 value 存放到 ThreadLocalMap 中的,然后将当前线程对象的 ThreadLocals 赋值为 ThreadLocalMap 对象。</p><p>现在来对 set 方法进行总结一下:</p><p>通过当前线程对象 thread 获取该 thread 所维护的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则以 ThreadLocal 实例为 key,值为 value 的键值对存入 ThreadLocalMap,若 ThreadLocalMap 为 null 的话,就新建 ThreadLocalMap,然后再以 ThreadLocal 为键,值为 value 的键值对存入即可。</p><h3 id="get-方法" tabindex="-1"><a class="header-anchor" href="#get-方法" aria-hidden="true">#</a> get() 方法</h3><p><strong>get 方法是获取当前线程中 ThreadLocal 变量的值</strong>,同样的还是来看看源码:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token class-name">T</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//1. 获取当前线程的实例对象</span>
<span class="token class-name">Thread</span> t <span class="token operator">=</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//2. 获取当前线程的ThreadLocalMap</span>
<span class="token class-name">ThreadLocalMap</span> map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//3. 获取map中当前ThreadLocal实例为key的值的entry</span>
<span class="token class-name">ThreadLocalMap<span class="token punctuation">.</span>Entry</span> e <span class="token operator">=</span> map<span class="token punctuation">.</span><span class="token function">getEntry</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">"unchecked"</span><span class="token punctuation">)</span>
<span class="token comment">//4. 当前entitiy不为null的话,就返回相应的值value</span>
<span class="token class-name">T</span> result <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">T</span><span class="token punctuation">)</span>e<span class="token punctuation">.</span>value<span class="token punctuation">;</span>
<span class="token keyword">return</span> result<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">//5. 若map为null或者entry为null的话通过该方法初始化,并返回该方法返回的value</span>
<span class="token keyword">return</span> <span class="token function">setInitialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>弄懂了 set 方法的逻辑,看 get 方法只需要带着逆向思维去看就好,如果是那样存的,反过来去拿就好。代码逻辑请看注释,另外,看下 setInitialValue 主要做了些什么事情?</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token class-name">T</span> <span class="token function">setInitialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">T</span> value <span class="token operator">=</span> <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Thread</span> t <span class="token operator">=</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">ThreadLocalMap</span> map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
map<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">else</span>
<span class="token function">createMap</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> value<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这段方法的逻辑和 set 方法几乎一致,另外值得关注的是 initialValue 方法:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">protected</span> <span class="token class-name">T</span> <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个<strong>方法是 protected 修饰的,也就是说继承 ThreadLocal 的子类可重写该方法,实现赋值为其他的初始值</strong>。关于 get 方法来总结一下:</p><p>通过当前线程 thread 实例获取到它所维护的 ThreadLocalMap,然后以当前 ThreadLocal 实例为 key 获取该 map 中的键值对(Entry),如果 Entry 不为 null 则返回 Entry 的 value。如果获取 ThreadLocalMap 为 null 或者 Entry 为 null 的话,就以当前 ThreadLocal 为 Key,value 为 null 存入 map 后,并返回 null。</p><h3 id="remove-方法" tabindex="-1"><a class="header-anchor" href="#remove-方法" aria-hidden="true">#</a> remove() 方法</h3><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//1. 获取当前线程的ThreadLocalMap</span>
<span class="token class-name">ThreadLocalMap</span> m <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>m <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token comment">//2. 从map中删除以当前ThreadLocal实例为key的键值对</span>
m<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>get、set 方法实现了存数据和读数据的操作,remove 方法实现了如何删数据的操作。删除数据当然是从 map 中删除数据,先获取与当前线程相关联的 ThreadLocalMap,然后从 map 中删除该 ThreadLocal 实例为 key 的键值对即可。</p><h2 id="threadlocalmap-详解" tabindex="-1"><a class="header-anchor" href="#threadlocalmap-详解" aria-hidden="true">#</a> ThreadLocalMap 详解</h2><p>从上面的分析我们已经知道,数据其实都放在了 ThreadLocalMap 中,ThreadLocal 的 get、set 和 remove 方法实际上都是通过 ThreadLocalMap 的 getEntry、set 和 remove 方法实现的。如果想真正全方位的弄懂 ThreadLocal,势必得再对 ThreadLocalMap 做一番理解。</p><h3 id="entry-数据结构" tabindex="-1"><a class="header-anchor" href="#entry-数据结构" aria-hidden="true">#</a> Entry 数据结构</h3><p>ThreadLocalMap 是 ThreadLocal 一个静态内部类,和大多数容器一样,内部维护了一个数组(Entry 类型的 table 数组)。</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token doc-comment comment">/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/</span>
<span class="token keyword">private</span> <span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token punctuation">]</span> table<span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过注释可以看出,table 数组的长度为 2 的幂次方。接下来看下 Entry 是什么:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Entry</span> <span class="token keyword">extends</span> <span class="token class-name">WeakReference</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">ThreadLocal</span><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token punctuation">{</span>
<span class="token doc-comment comment">/** The value associated with this ThreadLocal. */</span>
<span class="token class-name">Object</span> value<span class="token punctuation">;</span>
<span class="token class-name">Entry</span><span class="token punctuation">(</span><span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> k<span class="token punctuation">,</span> <span class="token class-name">Object</span> v<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">super</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span>
value <span class="token operator">=</span> v<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Entry 是一个以 ThreadLocal 为 key,Object 为 value 的键值对,另外需要注意的是这里的<strong>ThreadLocal 是弱引用,因为 Entry 继承了 WeakReference,在 Entry 的构造方法中,调用了 super(k)方法,会将 ThreadLocal 实例包装成一个 WeakReferenece。</strong></p><p>到这里我们可以用一个图来理解下 Thread、ThreadLocal、ThreadLocalMap、Entry 之间的关系:</p><figure><img src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/thread/ThreadLocal-01.png" alt="ThreadLocal各引用间的关系" tabindex="0" loading="lazy"><figcaption>ThreadLocal各引用间的关系</figcaption></figure><p>注意上图中的实线表示强引用,虚线表示弱引用。如图所示,每个线程实例中都可以通过 ThreadLocals 获取到 ThreadLocalMap,而 ThreadLocalMap 实际上就是一个以 ThreadLocal 实例为 key,任意对象为 value 的 Entry 数组。</p><p>当我们为 ThreadLocal 变量赋值时,实际上就是以当前 ThreadLocal 实例为 key,值为 value 的 Entry 往这个 ThreadLocalMap 中存放。</p><p>需要注意的是,Entry 中的 key 是弱引用,当 ThreadLocal 外部强引用被置为 null(<code>ThreadLocalInstance=null</code>)时,那么系统 GC 的时候,根据可达性分析,这个 ThreadLocal 实例就没有任何一条链路能够引用到它,此时 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄漏。</p><p>当然,如果当前 thread 运行结束,ThreadLocal、ThreadLocalMap、Entry 没有引用链可达,在垃圾回收的时候都会被系统回收。在实际开发中,会使用线程池去维护线程的创建和复用,比如固定大小的线程池,线程为了复用是不会主动结束的。</p><p>ThreadLocal 的内存泄漏问题,值得我们思考和注意,关于这个问题可以看这篇文章:</p><blockquote><p>----<a href="http://www.jianshu.com/p/dde92ec37bd1" target="_blank" rel="noopener noreferrer">详解 ThreadLocal 内存泄漏问题<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span></a></p></blockquote><h3 id="set-方法-1" tabindex="-1"><a class="header-anchor" href="#set-方法-1" aria-hidden="true">#</a> set 方法</h3><p>与 ConcurrentHashMap、HashMap 等容器一样,ThreadLocalMap 也是采用散列表进行实现的。在了解 set 方法前,我们先来回顾下关于散列表相关的知识。</p><h4 id="散列表" tabindex="-1"><a class="header-anchor" href="#散列表" aria-hidden="true">#</a> 散列表</h4><p>理想状态下,散列表就是一个包含关键字的固定大小的数组,通过使用散列函数,将关键字映射到数组的不同位置。下面是</p><figure><img src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/thread/ThreadLocal-02.png" alt="理想散列表的一个示意图" tabindex="0" loading="lazy"><figcaption>理想散列表的一个示意图</figcaption></figure><p>在理想状态下,哈希函数可以将关键字均匀的分散到数组的不同位置,不会出现两个关键字散列值相同(假设关键字数量小于数组的大小)的情况。</p><p>但是在实际使用中,经常会出现多个关键字散列值相同的情况(被映射到数组的同一个位置),我们将这种情况称为散列冲突。</p><p>为了解决散列冲突,主要采用下面两种方式: <strong>分离链表法</strong>(separate chaining)和<strong>开放地址法</strong>(open addressing)</p><h5 id="分离链表法" tabindex="-1"><a class="header-anchor" href="#分离链表法" aria-hidden="true">#</a> 分离链表法</h5><p>分离链表法使用链表解决冲突,将散列值相同的元素都保存到一个链表中。当查询的时候,首先找到元素所在的链表,然后遍历链表查找对应的元素,典型实现为 HashMap、ConcurrentHashMap 的拉链法。下面是一个示意图:</p><figure><img src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/thread/ThreadLocal-02.gif" alt="分离链表法示意图" tabindex="0" loading="lazy"><figcaption>分离链表法示意图</figcaption></figure><h5 id="开放地址法" tabindex="-1"><a class="header-anchor" href="#开放地址法" aria-hidden="true">#</a> 开放地址法</h5><p>开放地址法不会创建链表,当关键字散列到的数组单元已经被另外一个关键字占用的时候,就会尝试在数组中寻找其他的单元,直到找到一个空的单元。</p><p>探测数组空单元的方式有很多,这里介绍一种最简单的 -- 线性探测法。线性探测法就是从冲突的数组单元开始,依次往后搜索空单元,如果到数组尾部,再从头开始搜索(环形查找)。如下图所示:</p><figure><img src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/thread/ThreadLocal-03.jpg" alt="开放定址法示意图" tabindex="0" loading="lazy"><figcaption>开放定址法示意图</figcaption></figure><p>关于两种方式的比较,可以参考 <a href="http://www.nowamagic.net/academy/detail/3008060" target="_blank" rel="noopener noreferrer">这篇文章<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span></a>。</p><p><strong>ThreadLocalMap 中使用开放地址法来处理散列冲突</strong>,而 HashMap 中使用的分离链表法。之所以采用不同的方式主要是因为:</p><blockquote><p>在 ThreadLocalMap 中的散列值分散的十分均匀,很少会出现冲突。并且 ThreadLocalMap 经常需要清除无用的对象,使用纯数组更加方便。</p></blockquote><p>在了解这些相关知识后我们再回过头来看一下 set 方法。set 方法的源码为:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> key<span class="token punctuation">,</span> <span class="token class-name">Object</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// We don't use a fast path as with get() because it is at</span>
<span class="token comment">// least as common to use set() to create new entries as</span>
<span class="token comment">// it is to replace existing ones, in which case, a fast</span>
<span class="token comment">// path would fail more often than not.</span>
<span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>
<span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
<span class="token comment">//根据ThreadLocal的hashCode确定Entry应该存放的位置</span>
<span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">key<span class="token punctuation">.</span></span>ThreadLocalHashCode</span> <span class="token operator">&</span> <span class="token punctuation">(</span>len<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//采用开放地址法,hash冲突的时候使用线性探测</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">Entry</span> e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
e <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//覆盖旧Entry</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
e<span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span>
<span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">//当key为null时,说明ThreadLocal强引用已经被释放掉,那么就无法</span>
<span class="token comment">//再通过这个key获取ThreadLocalMap中对应的entry,这里就存在内存泄漏的可能性</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//用当前插入的值替换掉这个key为null的“脏”entry</span>
<span class="token function">replaceStaleEntry</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">//新建entry并插入table中i处</span>
tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> sz <span class="token operator">=</span> <span class="token operator">++</span>size<span class="token punctuation">;</span>
<span class="token comment">//插入后再次清除一些key为null的“脏”entry,如果大于阈值就需要扩容</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">cleanSomeSlots</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> sz<span class="token punctuation">)</span> <span class="token operator">&&</span> sz <span class="token operator">>=</span> threshold<span class="token punctuation">)</span>
<span class="token function">rehash</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>set 方法的关键部分<strong>请看上面的注释</strong>,主要有这样几点需要注意:</p><h4 id="threadlocal-的-hashcode" tabindex="-1"><a class="header-anchor" href="#threadlocal-的-hashcode" aria-hidden="true">#</a> ThreadLocal 的 hashcode</h4><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token class-name">ThreadLocalHashCode</span> <span class="token operator">=</span> <span class="token function">nextHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">HASH_INCREMENT</span> <span class="token operator">=</span> <span class="token number">0x61c88647</span><span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">AtomicInteger</span> nextHashCode <span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token doc-comment comment">/**
* Returns the next hash code.
*/</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">nextHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> nextHashCode<span class="token punctuation">.</span><span class="token function">getAndAdd</span><span class="token punctuation">(</span><span class="token constant">HASH_INCREMENT</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>从源码中我们可以清楚的看到 ThreadLocal 实例的 hashCode 是通过 nextHashCode() 方法实现的,该方法实际上是用一个 AtomicInteger 加上 0x61c88647 来实现的。</p><p>0x61c88647 这个数是有特殊意义的,它能够保证 hash 表的每个散列桶能够均匀的分布,这是<code>Fibonacci Hashing</code>,关于更多介绍可以看<a href="https://www.cnblogs.com/zhangjk1993/archive/2017/03/29/6641745.html" target="_blank" rel="noopener noreferrer">这篇文章的 ThreadLocal 散列值部分<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span></a>。</p><p>也正是能够均匀分布,所以 ThreadLocal 选择使用开放地址法来解决 hash 冲突的问题。</p><h4 id="怎样确定新值插入到哈希表中的位置" tabindex="-1"><a class="header-anchor" href="#怎样确定新值插入到哈希表中的位置" aria-hidden="true">#</a> 怎样确定新值插入到哈希表中的位置?</h4><p>该操作源码为:<code>key.ThreadLocalHashCode & (len-1)</code>,同 HashMap 和 ConcurrentHashMap 等容器的方式一样,利用当前 key(即 ThreadLocal 实例)的 hashcode 与哈希表大小相与,因为哈希表大小总是为 2 的幂次方,所以相与等同于一个取模的过程,这样就可以通过 Key 分配到具体的哈希桶中去。至于为什么取模要通过位与运算的原因,很简单,位运算的执行效率远远高于取模运算。</p><h4 id="怎样解决-hash-冲突" tabindex="-1"><a class="header-anchor" href="#怎样解决-hash-冲突" aria-hidden="true">#</a> 怎样解决 hash 冲突?</h4><p>源码中通过<code>nextIndex(i, len)</code>方法解决 hash 冲突的问题,该方法中的<code>((i + 1 < len) ? i + 1 : 0);</code>,也就是不断往后线性探测,当到哈希表末尾的时候再从 0 开始,成环形。</p><h4 id="怎样解决-脏-entry" tabindex="-1"><a class="header-anchor" href="#怎样解决-脏-entry" aria-hidden="true">#</a> 怎样解决“脏”Entry?</h4><p>在分析 ThreadLocal、ThreadLocalMap 以及 Entry 的关系的时候,我们已经知道使用 ThreadLocal 有可能存在内存泄漏(对象创建出来后,在之后一直没有使用该对象,但是垃圾回收器无法回收这个部分的内存),在源码中针对这种 key 为 null 的 Entry 称之为“stale entry”,直译为不新鲜的 entry,我把它理解为“脏 entry”,自然而然,Josh Bloch 和 Doug Lea 大师考虑到了这种情况,在 set 方法的 for 循环中寻找和当前 Key 相同的可覆盖 entry 的过程中通过<strong>replaceStaleEntry</strong>方法解决脏 entry 的问题。</p><p>如果当前<code>table[i]</code>为 null 的话,直接插入新 entry 后也会通过<strong>cleanSomeSlots</strong>来解决脏 entry 的问题,关于 cleanSomeSlots 和 replaceStaleEntry 方法,会在详解 ThreadLocal 内存泄漏中讲到,具体可看<a href="http://www.jianshu.com/p/dde92ec37bd1" target="_blank" rel="noopener noreferrer">这篇文章<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span></a>。</p><h4 id="如何进行扩容" tabindex="-1"><a class="header-anchor" href="#如何进行扩容" aria-hidden="true">#</a> 如何进行扩容?</h4><blockquote><p>threshold 的确定</p></blockquote><p>也几乎和大多数容器一样,ThreadLocalMap 会有扩容机制,那么它的 threshold 又是怎样确定的呢?</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token keyword">int</span> threshold<span class="token punctuation">;</span> <span class="token comment">// Default to 0</span>
<span class="token doc-comment comment">/**
* The initial capacity -- MUST be a power of two.
*/</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">INITIAL_CAPACITY</span> <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span>
<span class="token class-name">ThreadLocalMap</span><span class="token punctuation">(</span><span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> firstKey<span class="token punctuation">,</span> <span class="token class-name">Object</span> firstValue<span class="token punctuation">)</span> <span class="token punctuation">{</span>
table <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token constant">INITIAL_CAPACITY</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">firstKey<span class="token punctuation">.</span></span>ThreadLocalHashCode</span> <span class="token operator">&</span> <span class="token punctuation">(</span><span class="token constant">INITIAL_CAPACITY</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
table<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">(</span>firstKey<span class="token punctuation">,</span> firstValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
size <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token function">setThreshold</span><span class="token punctuation">(</span><span class="token constant">INITIAL_CAPACITY</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token doc-comment comment">/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">setThreshold</span><span class="token punctuation">(</span><span class="token keyword">int</span> len<span class="token punctuation">)</span> <span class="token punctuation">{</span>
threshold <span class="token operator">=</span> len <span class="token operator">*</span> <span class="token number">2</span> <span class="token operator">/</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>根据源码可知,在第一次为 ThreadLocal 进行赋值的时候会创建初始大小为 16 的 ThreadLocalMap,并且通过 setThreshold 方法设置 threshold,其值为当前哈希数组长度乘以(2/3),也就是说加载因子为 2/3。</p><p>(加载因子是衡量哈希表密集程度的一个参数,如果加载因子越大的话,说明哈希表被装载的越多,出现 hash 冲突的可能性越大,反之,则被装载的越少,出现 hash 冲突的可能性越小。同时如果过小,很显然内存使用率不高,该值的取值应该考虑到内存使用率和 hash 冲突概率的一个平衡,如 HashMap、ConcurrentHashMap 的加载因子都为 0.75)。</p><p>这里<strong>ThreadLocalMap 初始大小为 16</strong>,<strong>加载因子为 2/3</strong>,所以哈希表可用大小为:16*2/3=10,即哈希表可用容量为 10。</p><blockquote><p>扩容 resize</p></blockquote><p>从 set 方法中可以看出当 hash 表的 size 大于 threshold 的时候,会通过 resize 方法进行扩容。</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token doc-comment comment">/**
* Double the capacity of the table.
*/</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token punctuation">]</span> oldTab <span class="token operator">=</span> table<span class="token punctuation">;</span>
<span class="token keyword">int</span> oldLen <span class="token operator">=</span> oldTab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
<span class="token comment">//新数组为原数组的2倍</span>
<span class="token keyword">int</span> newLen <span class="token operator">=</span> oldLen <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token punctuation">]</span> newTab <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">[</span>newLen<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator"><</span> oldLen<span class="token punctuation">;</span> <span class="token operator">++</span>j<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">Entry</span> e <span class="token operator">=</span> oldTab<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//遍历过程中如果遇到脏entry的话直接另value为null,有助于value能够被回收</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
e<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// Help the GC</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token comment">//重新确定entry在新数组的位置,然后进行插入</span>
<span class="token keyword">int</span> h <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">k<span class="token punctuation">.</span></span>ThreadLocalHashCode</span> <span class="token operator">&</span> <span class="token punctuation">(</span>newLen <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>newTab<span class="token punctuation">[</span>h<span class="token punctuation">]</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
h <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> newLen<span class="token punctuation">)</span><span class="token punctuation">;</span>
newTab<span class="token punctuation">[</span>h<span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span>
count<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">//设置新哈希表的threshHold和size属性</span>
<span class="token function">setThreshold</span><span class="token punctuation">(</span>newLen<span class="token punctuation">)</span><span class="token punctuation">;</span>
size <span class="token operator">=</span> count<span class="token punctuation">;</span>
table <span class="token operator">=</span> newTab<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>方法逻辑<strong>请看注释</strong>,新建一个大小为原来数组长度的两倍的数组,然后遍历旧数组中的 entry 并将其插入到新的 hash 数组中,需要注意的是,<strong>在扩容的过程中针对脏 entry 的话会把 value 设为 null,以便能够被垃圾回收器回收,解决隐藏的内存泄漏的问题</strong>。</p><h3 id="getentry-方法" tabindex="-1"><a class="header-anchor" href="#getentry-方法" aria-hidden="true">#</a> getEntry 方法</h3><p>getEntry 方法源码为:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token class-name">Entry</span> <span class="token function">getEntry</span><span class="token punctuation">(</span><span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//1. 确定在散列数组中的位置</span>
<span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">key<span class="token punctuation">.</span></span>ThreadLocalHashCode</span> <span class="token operator">&</span> <span class="token punctuation">(</span>table<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//2. 根据索引i获取entry</span>
<span class="token class-name">Entry</span> e <span class="token operator">=</span> table<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token comment">//3. 满足条件则返回该entry</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&&</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> key<span class="token punctuation">)</span>
<span class="token keyword">return</span> e<span class="token punctuation">;</span>
<span class="token keyword">else</span>
<span class="token comment">//4. 未查找到满足条件的entry,额外在做的处理</span>
<span class="token keyword">return</span> <span class="token function">getEntryAfterMiss</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> i<span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>方法逻辑很简单,如果当前定位的 entry 的 key 和查找的 key 相同的话就直接返回这个 entry,否则的话就是在 set 的时候判断是否存在 hash 冲突,需要通过 getEntryAfterMiss 做进一步处理。getEntryAfterMiss 方法为:</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token class-name">Entry</span> <span class="token function">getEntryAfterMiss</span><span class="token punctuation">(</span><span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> key<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token class-name">Entry</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>
<span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> key<span class="token punctuation">)</span>
<span class="token comment">//找到和查询的key相同的entry则返回</span>
<span class="token keyword">return</span> e<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token comment">//解决脏entry的问题</span>
<span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">else</span>
<span class="token comment">//继续向后环形查找</span>
i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span>
e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个方法同样很好理解,通过 nextIndex 往后环形查找,如果找到和查询的 key 相同的 entry 的话就直接返回,如果在查找过程中遇到脏 entry 的话就使用 expungeStaleEntry 方法进行处理。到目前为止,为了解决潜在的内存泄漏的问题,在 set、resize、getEntry 这些地方都会对这些脏 entry 进行处理,可见为了尽可能解决这个问题几乎无时无刻都在做出努力。</p><h3 id="remove-方法-1" tabindex="-1"><a class="header-anchor" href="#remove-方法-1" aria-hidden="true">#</a> remove 方法</h3><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token doc-comment comment">/**
* Remove the entry for key.
*/</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token operator">?</span><span class="token punctuation">></span></span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">Entry</span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>
<span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
<span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">key<span class="token punctuation">.</span></span>ThreadLocalHashCode</span> <span class="token operator">&</span> <span class="token punctuation">(</span>len<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">Entry</span> e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
e <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//将entry的key置为null</span>
e<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//将该entry的value也置为null</span>
<span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>该方法逻辑很简单,通过往后环形查找到与指定 key 相同的 entry 后,先通过 clear 方法将 key 置为 null 后,使其转换为一个脏 entry,然后调用 expungeStaleEntry 方法将其 value 置为 null,以便垃圾回收时能够清理,同时将 table[i]置为 null。</p><h2 id="threadlocal-的使用场景" tabindex="-1"><a class="header-anchor" href="#threadlocal-的使用场景" aria-hidden="true">#</a> ThreadLocal 的使用场景</h2><p><strong>ThreadLocal 不是用来解决共享对象的多线程访问问题的</strong>,数据实质上是放在每个 thread 实例引用的 ThreadLocalMap中的,也就是说<strong>每个不同的线程都拥有专属于自己的数据容器(ThreadLocalMap),彼此不影响</strong>。</p><p>因此 ThreadLocal 只适用于 <strong>共享对象会造成线程安全</strong> 的业务场景。比如<strong>Hibernate 中通过 ThreadLocal 管理 Session</strong>就是一个典型的案例,不同的请求线程(用户)拥有自己的 session,若将 session 共享出去被多线程访问,必然会带来线程安全问题。</p><p>下面,我们自己来写一个例子,SimpleDateFormat.parse 方法会有线程安全的问题,我们可以尝试使用 ThreadLocal 包装 SimpleDateFormat,只要该实例不被多线程共享即可。</p><div class="language-java line-numbers-mode" data-ext="java"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadLocalDemo</span> <span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">SimpleDateFormat</span><span class="token punctuation">></span></span> sdf <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token generics"><span class="token punctuation"><</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token class-name">ExecutorService</span> executorService <span class="token operator">=</span> <span class="token class-name">Executors</span><span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">100</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
executorService<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">DateUtil</span><span class="token punctuation">(</span><span class="token string">"2019-11-25 09:00:"</span> <span class="token operator">+</span> i <span class="token operator">%</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DateUtil</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token class-name">String</span> date<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token class-name">DateUtil</span><span class="token punctuation">(</span><span class="token class-name">String</span> date<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>date <span class="token operator">=</span> date<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>sdf<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
sdf<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token string">"yyyy-MM-dd HH:mm:ss"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="token class-name">Date</span> date <span class="token operator">=</span> sdf<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>date<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ParseException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol><li>如果当前线程不持有 SimpleDateformat 对象实例,那么就新建一个并把它设置到当前线程中,如果已经持有,就直接使用。另外,<strong>从<code>if (sdf.get() == null){....}else{.....}</code>可以看出为每一个线程分配一个 SimpleDateformat 对象实例是从应用层面(业务代码逻辑)去保证的。</strong></li><li>上面我们说过 ThreadLocal 有可能存在内存泄漏,在使用完之后,最好使用 remove 方法将这个变量移除,就像在使用数据库连接一样,及时关闭连接。</li></ol><hr><blockquote><p>编辑:沉默王二,更多关于并发编程的源码分析,可以参考<a href="https://github.com/CL0610/Java-concurrency" target="_blank" rel="noopener noreferrer">并发编程知识总结<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span></a>这个开源知识库。</p></blockquote><hr><p>最近整理了一份牛逼的学习资料,包括但不限于 Java 基础部分(JVM、Java 集合框架、多线程),还囊括了 <strong>数据库、计算机网络、算法与数据结构、设计模式、框架类 Spring、Netty、微服务(Dubbo,消息队列) 网关</strong> 等等等等……详情戳:<a href="https://tobebetterjavaer.com/pdf/programmer-111.html" target="_blank" rel="noopener noreferrer">可以说是 2022 年全网最全的学习和找工作的 PDF 资源了<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span></a></p><p>微信搜 <strong>沉默王二</strong> 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 <strong>222</strong> 即可免费领取。</p><figure><img src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png" alt="" tabindex="0" loading="lazy"><figcaption></figcaption></figure></div><!----><footer class="page-meta"><div class="meta-item edit-link"><a href="https://github.com/itwanger/toBeBetterJavaer/edit/master/docs/thread/ThreadLocal.md" rel="noopener noreferrer" target="_blank" aria-label="编辑此页" class="nav-link label"><!--[--><svg xmlns="http://www.w3.org/2000/svg" class="icon edit-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="edit icon"><path d="M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"></path><path d="M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"></path></svg><!--]-->编辑此页<span><svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg><span class="external-link-icon-sr-only">open in new window</span></span><!----></a></div><div class="meta-item git-info"><div class="update-time"><span class="label">上次编辑于: </span><!----></div><div class="contributors"><span class="label">贡献者: </span><!--[--><!--[--><span class="contributor" title="email: www.qing_gee@163.com">itwanger</span>,<!--]--><!--[--><span class="contributor" title="email: www.qing_gee@163.com">沉默王二</span><!--]--><!--]--></div></div></footer><nav class="page-nav"><a href="/thread/CopyOnWriteArrayList.html" class="nav-link prev" aria-label="CopyOnWriteArrayList"><div class="hint"><span class="arrow start"></span>上一页</div><div class="link"><!---->CopyOnWriteArrayList</div></a><a href="/thread/BlockingQueue.html" class="nav-link next" aria-label="BlockingQueue"><div class="hint">下一页<span class="arrow end"></span></div><div class="link">BlockingQueue<!----></div></a></nav><div class="giscus-wrapper input-top" id="comment" style="display:block;"><div class="loading-icon-wrapper" style="display:flex;align-items:center;justify-content:center;height:96px"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" preserveAspectRatio="xMidYMid" viewBox="25 25 50 50"><animateTransform attributeName="transform" type="rotate" dur="2s" keyTimes="0;1" repeatCount="indefinite" values="0;360"></animateTransform><circle cx="50" cy="50" r="20" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round"><animate attributeName="stroke-dasharray" dur="1.5s" keyTimes="0;0.5;1" repeatCount="indefinite" values="1,200;90,200;1,200"></animate><animate attributeName="stroke-dashoffset" dur="1.5s" keyTimes="0;0.5;1" repeatCount="indefinite" values="0;-35px;-125px"></animate></circle></svg></div></div><!----><!--]--></main><!--]--><footer class="footer-wrapper"><div class="footer"><a href="https://beian.miit.gov.cn/" target="_blank">豫ICP备2021038026号-1</a><img src="https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/beian.png" height="15px" width="15px" /><a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=41030502000411"><span>豫公网安备 41030502000411号</span></a></div><div class="copyright">Copyright © 2023 沉默王二</div></footer></div><!--]--><!----><!----><!--]--></div>
<script type="module" src="/assets/app-736b5df9.js" defer></script>
</body>
</html>