浅谈ThreadLocal----每个线程一个小书包
<p><span style="font-family: "Microsoft YaHei"">ThreadLocal是什么?</span><br><span style="font-family: "Microsoft YaHei"">thread是线程,local是本地的意思</span><br><span style="font-family: "Microsoft YaHei"">字面意思是线程本地。</span><br><span style="font-family: "Microsoft YaHei"">其实更通俗的理解是给每个线程设置一个缓存。这个缓存用来存储当前线程在未来的业务逻辑中需要执行到的变量。</span><br><span style="font-family: "Microsoft YaHei"">我们先来看怎么用:</span></p><p><span style="font-family: "Microsoft YaHei"">首先创建全局变量ThreadLocal,</span><br><span style="font-family: "Microsoft YaHei"">各自启动一个线程任务:</span><br><span style="font-family: "Microsoft YaHei"">线程任务将变量设置到缓存中。</span><br><span style="font-family: "Microsoft YaHei"">线程任务需要用到缓存中的变量时,直接从缓存中取即可。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.util.concurrent.TimeUnit;
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 128, 0, 1)">/**</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 128, 0, 1)"> * @discription
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ThreadLocalLearn {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">static</span> ThreadLocal<String> threadLocal = <span style="color: rgba(0, 0, 255, 1)">new</span> ThreadLocal<><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> Runnable r = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> @Override
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> threadLocal<strong><span style="color: rgba(255, 0, 0, 1)">.set</span></strong>(Thread.currentThread().getName());
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> sayMyName();
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)"> threadLocal.<strong><span style="color: rgba(255, 0, 0, 1)">remove</span></strong>();
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">17</span>
<span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> sayMyName() {
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i < 3; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">20</span> String name =<span style="color: rgba(0, 0, 0, 1)"> threadLocal.<strong><span style="color: rgba(255, 0, 0, 1)">get</span></strong>();
</span><span style="color: rgba(0, 128, 128, 1)">21</span> System.out.println(Thread.currentThread().getName() + " say: im a thread, name:" +<span style="color: rgba(0, 0, 0, 1)"> name);
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">23</span> TimeUnit.SECONDS.sleep(3<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">24</span> } <span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...</span>
<span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)"> };
</span><span style="color: rgba(0, 128, 128, 1)">30</span> Thread t1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)"> t1.start();
</span><span style="color: rgba(0, 128, 128, 1)">32</span> Thread t2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r);
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)"> t2.start();
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">35</span> }</pre>
</div>
<p><span style="font-family: "Microsoft YaHei"">它的使用非常简单,</span><br><span style="font-family: "Microsoft YaHei"">(1)先set()存储值;</span><br><span style="font-family: "Microsoft YaHei"">(2)使用时get()取出值;</span><br><span style="font-family: "Microsoft YaHei"">(3)用完了使用remove()清理掉;</span></p>
<p><span style="font-family: "Microsoft YaHei"">输出如下:</span></p>
<div class="cnblogs_code">
<pre>Connected to the target VM, address: '127.0.0.1:56863', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">
Thread</span>-0 say: im a thread, name:Thread-0<span style="color: rgba(0, 0, 0, 1)">
Thread</span>-1 say: im a thread, name:Thread-1<span style="color: rgba(0, 0, 0, 1)">
Thread</span>-0 say: im a thread, name:Thread-0<span style="color: rgba(0, 0, 0, 1)">
Thread</span>-1 say: im a thread, name:Thread-1<span style="color: rgba(0, 0, 0, 1)">
Thread</span>-1 say: im a thread, name:Thread-1<span style="color: rgba(0, 0, 0, 1)">
Thread</span>-0 say: im a thread, name:Thread-0<span style="color: rgba(0, 0, 0, 1)">
Disconnected from the target VM, address: </span>'127.0.0.1:56863', transport: 'socket'</pre>
</div>
<p><span style="font-family: "Microsoft YaHei"">很多人第一次见到ThreadLocal,第一直觉它的实现是用Map<Thread,Object> 。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )但是深入研究之后,你会发现threadLocal的实现要比这样一个map 精妙的多,也好用的多。</span><br><span style="font-family: "Microsoft YaHei"">我们通过查看java源码,可以依次探索ThreadLocal是如何实现缓存的:</span></p>
<p><span style="font-family: "Microsoft YaHei"">类整体的关系大概是这样的:</span></p>
<p><img src="https://img2024.cnblogs.com/blog/704073/202509/704073-20250929152250727-1012654768.png" alt="tlleitu" loading="lazy"></p>
<p><span style="font-family: "Microsoft YaHei""> 查看源码,我们可以发现如下特性:</span></p>
<p><span style="font-family: "Microsoft YaHei"">1、ThreadLocal本身并不是缓存,它只是起到一个缓存的key 的作用。我们每次创建一个ThreadLocal 并不是真正的创建了一个缓存,其实只是创建了一个缓存的标识。</span><br><span style="font-family: "Microsoft YaHei"">源码如下:this 就是ThreadLocal实例</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> set(T value) {
</span><span style="color: rgba(0, 128, 128, 1)">2</span> Thread t =<span style="color: rgba(0, 0, 0, 1)"> Thread.currentThread();
</span><span style="color: rgba(0, 128, 128, 1)">3</span> ThreadLocalMap map =<span style="color: rgba(0, 0, 0, 1)"> getMap(t);
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (map != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">5</span> map.set(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, value);
</span><span style="color: rgba(0, 128, 128, 1)">6</span> } <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">7</span> <span style="color: rgba(0, 0, 0, 1)"> createMap(t, value);
</span><span style="color: rgba(0, 128, 128, 1)">8</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">9</span> }</pre>
</div>
<p><span style="font-family: "Microsoft YaHei"">2、真正的缓存保存在Thread中,缓存被定义为:</span><br><span style="font-family: "Microsoft YaHei"">ThreadLocal.ThreadLocalMap threadLocals;</span><br><span style="font-family: "Microsoft YaHei"">从名字可以发现,这个缓存的类型是在ThreadLocal 中定义的一个静态内部类。这个类就是用来真正存放缓存的地方。这就像是thread小书包一样,每个线程有一个自己的独立的存储空间。</span><br><span style="font-family: "Microsoft YaHei"; color: rgba(255, 0, 0, 1)">设计疑问:它(ThreadLocalMap)为什么没有定义在Thread类中,毕竟它是Thread的缓存。</span></p>
<p><span style="font-family: "Microsoft YaHei"">源码如下:<em>Thread.java</em></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> ThreadLocal values pertaining to this thread. This map is maintained
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 128, 0, 1)"> * by the ThreadLocal class. </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> ThreadLocal.ThreadLocalMap threadLocals = <span style="color: rgba(0, 0, 255, 1)">null</span>;</pre>
</div>
<p><span style="font-family: "Microsoft YaHei""> 3、查看ThreadLocalMap的源码,我们发现它并没有实现Map接口,就像其他map一样,ThreadLocalMap实现了常用的Map中的set,get,getEntry,setThreshold,,remove 等方法。</span></p>
<p><span style="font-family: "Microsoft YaHei"">并且它内部使用了线性探测法来解决哈希冲突。</span><br><span style="font-family: "Microsoft YaHei"; color: rgba(255, 0, 0, 1)">设计疑问:它(ThreadLocalMap)为什么没有实现Map接口?</span><br><span style="font-family: "Microsoft YaHei"">源码如下:<span style="font-style: italic">ThreadLocal.Java</span></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ThreadLocalMap {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> INITIAL_CAPACITY = 16<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Entry[] table;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>
<span style="color: rgba(0, 128, 128, 1)">10</span>
<span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> size = 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">12</span>
<span style="color: rgba(0, 128, 128, 1)">13</span>
<span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> threshold; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Default to 0</span>
<span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setThreshold(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> len) {
</span><span style="color: rgba(0, 128, 128, 1)">18</span> threshold = len * 2 / 3<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">20</span>
<span style="color: rgba(0, 128, 128, 1)">21</span>
<span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 255, 1)">private</span> Entry getEntry(ThreadLocal<?><span style="color: rgba(0, 0, 0, 1)"> key) {
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)"> ...
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">25</span>
<span style="color: rgba(0, 128, 128, 1)">26</span>
<span style="color: rgba(0, 128, 128, 1)">27</span>
<span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> set(ThreadLocal<?><span style="color: rgba(0, 0, 0, 1)"> key, Object value) {
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)"> ...
</span><span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">31</span>
<span style="color: rgba(0, 128, 128, 1)">32</span>
<span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> remove(ThreadLocal<?><span style="color: rgba(0, 0, 0, 1)"> key) {
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)"> ...
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">36</span>
<span style="color: rgba(0, 128, 128, 1)">37</span>
<span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> rehash() {
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 0, 0, 1)"> ...
</span><span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">41</span>
<span style="color: rgba(0, 128, 128, 1)">42</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> resize() {
</span><span style="color: rgba(0, 128, 128, 1)">43</span> <span style="color: rgba(0, 0, 0, 1)"> ...
</span><span style="color: rgba(0, 128, 128, 1)">44</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">45</span> <span style="color: rgba(0, 0, 0, 1)"> ....
</span><span style="color: rgba(0, 128, 128, 1)">46</span> }</pre>
</div>
<p><span style="font-family: "Microsoft YaHei"">4、继续看源码,我们发现ThreadLocalMap类像其他Map实现一样,在内部定义了Entry。并且这个Entry居然继承了弱引用,弱引用被定义在Entry的key上,而且key的类型是ThreadLocal。</span></p>
<p><span style="font-family: "Microsoft YaHei"">至于什么是弱引用,我以前的文章中介绍过,请看(浅谈Java中的引用 https://www.cnblogs.com/jilodream/p/6181762.html),一定要对弱引用了解,否则ThreadLocal的核心实现以及它会存在的问题,就无法更深理解了。</span></p>
<p><span style="font-family: "Microsoft YaHei""><span style="color: rgba(255, 0, 0, 1)">这里又会有疑问,为什么要使用弱引用,使用强引用不好吗?弱引用万一被回收导致空引用等问题怎么办?</span><br></span></p>
<p><span style="font-family: "Microsoft YaHei"">源码如下:ThreadLocal.Java</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span> Entry <span style="color: rgba(0, 0, 255, 1)">extends</span> WeakReference<ThreadLocal<?>><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> The value associated with this ThreadLocal. </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 0, 1)"> Object value;
</span><span style="color: rgba(0, 128, 128, 1)">4</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> Entry(ThreadLocal<?><span style="color: rgba(0, 0, 0, 1)"> k, Object v) {
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(k);
</span><span style="color: rgba(0, 128, 128, 1)">7</span> value =<span style="color: rgba(0, 0, 0, 1)"> v;
</span><span style="color: rgba(0, 128, 128, 1)">8</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">9</span> }</pre>
</div>
<p><span style="font-family: "Microsoft YaHei"">我们依次回答这几个问题:</span><br><span style="color: rgba(255, 0, 0, 1)"><strong><span style="font-family: "Microsoft YaHei"">(1)设计疑问:它(ThreadLocalMap)为什么没有定义在Thread类中,毕竟它是Thread的缓存</span></strong></span><br><span style="font-family: "Microsoft YaHei"">这恰恰是Thread符合开闭原则的优秀设计。如果是将ThreadLocalMap添加到Thread中,那么Thread类就太重了,以后只要和线程相关的业务都要将代码添加到Thread中,那Thread就无限膨胀了,变成超级类了,试想什么业务和线程能脱离关系呢?</span><br><span style="font-family: "Microsoft YaHei"">况且他们只是类依赖关系而不是组合关系(对类关系不了解的同学可以看我的这篇文章:</span>统一建模语言UML---类图 https://www.cnblogs.com/jilodream/p/16693511.html<span style="font-family: "Microsoft YaHei"">)。</span></p>
<p><span style="font-family: "Microsoft YaHei"">Map怎么实现,缓存怎么维护,这些都是Thread不需要考虑的,我们就是需要用到你的特性。</span></p>
<p><strong><span style="font-family: "Microsoft YaHei"; color: rgba(255, 0, 0, 1)">(2)设计疑问:它(ThreadLocalMap)为什么没有实现Map接口?</span></strong><br><span style="font-family: "Microsoft YaHei"">实现接口是为了统一化提供接口,让外界可以只依赖接口,而不是接口的实现。但是ThreadLocalMap并不是给外界使用的,并不需要暴露出来。他就是为了给ThreadLocal业务使用的。只要完成最核心的Map能力,用空间换时间,将理论时间复杂度推向O(1)即可。因此完全没有必要实现Map接口。实现了Map接口反而要将内部方法暴露为public,这也不符合最少知道原则。一句话就是没必要,还添乱。</span></p>
<p><strong><span style="font-family: "Microsoft YaHei"; color: rgba(255, 0, 0, 1)">(3)为什么要使用弱引用,使用强引用不好吗?弱引用万一被回收导致空引用等问题怎么办?</span></strong><br><span style="font-family: "Microsoft YaHei"">我们需要先了解弱引用的特性:当一个变量只有弱引用关联时,那么在下次GC回收时,不论我们内存是否足够,都将回收掉该内存。</span><br><span style="font-family: "Microsoft YaHei"">第一眼感觉这很危险,毕竟我们非常担心就是一个变量用着用着突然不能用了,出现空引用了,漫天的空引用这太不可控了。</span><br><span style="font-family: "Microsoft YaHei"">其实这完全多虑了,注意看:我们是如何使用缓存的,是通过threadlocal.get(),也就是说我们想要使用缓存就一定要使用threadlocal的实例,也就是强引用,</span><br><span style="font-family: "Microsoft YaHei"">有了强引用,使用时就一定不会被回收。因此完全不用担心使用缓存中,弱引用key突然变为null的情况了。</span><br><span style="font-family: "Microsoft YaHei"">那什么时候弱引用key会被回收呢?</span><br><span style="font-family: "Microsoft YaHei"">这就是当外界的强引用被手动设置为null时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )或者是作为局部变量跳出了方法栈,超出生命周期被回收掉了。</span><br><span style="font-family: "Microsoft YaHei"">试想一下,真要是发生这两种情况,那么其实这个缓存也就根本无法再用到了同时,key被尽快回收,反而对内存更有利。</span><br><span style="font-family: "Microsoft YaHei"">那么弱引用这么好用,为什么value不设置为弱引用呢?</span><br><span style="font-family: "Microsoft YaHei"">其实细想一下就会发现value一定不能设置为弱引用,为什么呢?</span><br><span style="font-family: "Microsoft YaHei"">key设置为弱引用,是因为想要使用这个缓存,key就一定要有强引用关联。而value则不一定有外界强引用关联,它在外界的强引用可能早就消失了。比如下面这个例子:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.util.concurrent.TimeUnit;
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 128, 0, 1)">/**</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 128, 0, 1)"> * @discription
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ThreadLocalLearn {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">static</span> ThreadLocal<UserInfo> userContext = <span style="color: rgba(0, 0, 255, 1)">new</span> ThreadLocal<><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> Runnable r = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> @Override
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> setUserInfo();
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> handle();
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)"> userContext.remove();
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">17</span>
<span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> handle() {
</span><span style="color: rgba(0, 128, 128, 1)">19</span> UserInfo user =<span style="color: rgba(0, 0, 0, 1)"> userContext.get();
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(255, 0, 0, 1)">注意倘若map中的value被定义为弱引用,则此处的user可能为null</span>
<span style="color: rgba(0, 128, 128, 1)">21</span> System.out.println(" i am:" +<span style="color: rgba(0, 0, 0, 1)"> user.toString());
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">do sth</span>
<span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">24</span> TimeUnit.SECONDS.sleep(3<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">25</span> } <span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)"> };
</span><span style="color: rgba(0, 128, 128, 1)">30</span> Thread t1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)"> t1.start();
</span><span style="color: rgba(0, 128, 128, 1)">32</span> Thread t2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r);
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)"> t2.start();
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">35</span>
<span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setUserInfo() {
</span><span style="color: rgba(0, 128, 128, 1)">37</span> UserInfo user = <span style="color: rgba(0, 0, 255, 1)">new</span> UserInfo();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 假装是从db中获取的</span>
<span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)"> userContext.set(user);
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(255, 0, 0, 1)">跳出该方法后,userInfo的在外部的直接强引用就被回收了</span>
<span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">41</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">42</span>
<span style="color: rgba(0, 128, 128, 1)">43</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UserInfo {
</span><span style="color: rgba(0, 128, 128, 1)">44</span> <span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String name;
</span><span style="color: rgba(0, 128, 128, 1)">45</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> age;
</span><span style="color: rgba(0, 128, 128, 1)">46</span>
<span style="color: rgba(0, 128, 128, 1)">47</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">....</span>
<span style="color: rgba(0, 128, 128, 1)">48</span> }</pre>
</div>
<p><span style="font-family: "Microsoft YaHei"">我们在A方法中设置了缓存 currentUserId,跳出A方法,currentUserId在外界的引用被断开,倘若此时value也被定义为弱引用,value就随时可能被回收。而我们又可以通过</span></p>
<p><strong><span style="font-family: "Microsoft YaHei"; color: rgba(255, 0, 0, 1)">(key)Threadlocal --> threadLocals(ThreadLocalMap) --> entry --> value </span></strong></p>
<p><span style="font-family: "Microsoft YaHei"">这样的调用关系来拿到缓存value。这样缓存的使用就不可控了。</span><br><span style="font-family: "Microsoft YaHei"">那么value一定不能设置为弱引用或及时回收么?</span><br><span style="font-family: "Microsoft YaHei"">并不是,</span><br><span style="font-family: "Microsoft YaHei"">其实我们只要在key回收时,顺手对value也做一个回收,但是这是GC完成的,再key消失时,联动对所有线程中关联的Map都进行一遍清理。(实现过于复杂)</span><br><span style="font-family: "Microsoft YaHei"">亦或者清理key(threadlocal)的强引用时,将value的强引用也一并被清理。</span><br><span style="font-family: "Microsoft YaHei"">可行,也是ThreadLocal推荐的方式,需要手动调用ThreadLocal.remove 方法。</span><br><span style="font-family: "Microsoft YaHei"">在调用remove方法后,ThreadLocalMap会对所有垃圾数据进行清理,还会压缩哈希表。</span><br><span style="font-family: "Microsoft YaHei"">为了解决ThreadLocalMap的value 延迟清理的情况,ThreadLocalMap在set get remove等方法中,都会对ThreadLocalMap存在的这种<null,Object> 垃圾数据进行一定程度的清理(注意这里要分各种情况,具体只能详细分析源码了,一篇博文很难说清)。</span></p>
<p><strong><span style="font-family: "Microsoft YaHei"; color: rgba(255, 0, 0, 1)">(4)这样又会有一个新的问题,如果key 被回收了,但是value没有被回收,因此value就常驻内存了,那么value不就会导致内存泄露吗?</span></strong><br><span style="font-family: "Microsoft YaHei"">很不幸,这样的确是会导致内存的泄露。</span><span style="font-family: "Microsoft YaHei"">(这里简单提一下,java中的内存泄露是指,可以通过强引用关联到他,gc无法回收掉它。与此同时,业务按照正常逻辑又无法使用到它。也就是又用不到,又回收不掉,就称之为内存泄露)</span><br><span style="font-family: "Microsoft YaHei"">但是这种内存泄露出现的概率非常低。</span></p>
<p><span style="font-family: "Microsoft YaHei"">它需要同时满足以下三个条件才可以:</span><br><span style="font-family: "Microsoft YaHei"">1、需要线程的生命周期永远不会结束。如果线程生命周期结束了,那么ThreadLocalMap就会被回收,里边出现的无其他关联的key value 也都会被回收。</span><br><span style="font-family: "Microsoft YaHei"">这种一般是守护线程或者线程池(线程复用出现)</span></p>
<p><span style="font-family: "Microsoft YaHei"">2、ThreadLocal在设置为null时,没有手动调动remove方法</span></p>
<p><span style="font-family: "Microsoft YaHei"">3、线程中的ThreadLocalMap在后续使用中,没有再调用任何get set remove方法,也就是线程没再使用ThreadLocal</span></p>
<p><span style="font-family: "Microsoft YaHei"">概率低,是不是代表不太需要关注,当然不是。</span><br><span style="font-family: "Microsoft YaHei"">因为内存泄露不仅仅是减少了可用内存,还增加了GC负担,系统性能就会收到影响,这就说的远了。</span></p>
<p><span style="font-family: "Microsoft YaHei"">其实ThreadLocal最大的问题,并不是泄露的问题,而是被滥用的问题,不规范使用的问题。很多人把ThreadLocal当成是线程的私有仓库,所有变量参数都往里边塞,</span><br><span style="font-family: "Microsoft YaHei"">导致写代码和维护时,非常不方便,出现问题也给维护人员造成很大的困扰。</span></p>
<p><span style="font-family: "Microsoft YaHei"">接下来我们简单说下ThreadLocal的使用(<span style="color: rgba(255, 0, 255, 1)"><em>后边我会再写一篇,如何使用ThreadLocal,毕竟我们学习技术目的是能够驾驭它,而不仅仅是知其所以然</em></span>):</span><br><span style="font-family: "Microsoft YaHei"">我们一般是将上下文信息,或者当前需要频繁使用的,与实际业务直接关系不大的系统数据方便携带。放置到thread的小书包中。</span><br><span style="font-family: "Microsoft YaHei"">(1)上下文信息</span><br><span style="font-family: "Microsoft YaHei"">如我们在controller层,将用户的上下文信息传入,如traceId(方便链路追踪),如用户token,后续可能调用其他鉴权接口等</span><br><span style="font-family: "Microsoft YaHei"">(2)解耦数据库连接等连接池信息,</span><br><span style="font-family: "Microsoft YaHei"">比如Springboot运行事务时,我们每次getconnection(),就只使用ThreadLocal中贮存好的这个连接,整个方法使用的是同一个数据库连接。</span><br><span style="font-family: "Microsoft YaHei"">以上场景不使用ThreadLocal可以吗?</span><br><span style="font-family: "Microsoft YaHei"">也可以,他并不是一定要使用。但是你这样就要把很多的参数传来传去,暴露很多的问题。</span><br><span style="font-family: "Microsoft YaHei"">甚至在很多第三方实现的框架中,他不支持你传这些参数,他就是要用通过ThreadLocal来回传值。</span></p>
<p><span style="font-family: "Microsoft YaHei"">(3)为线程安全提供了方案,减少了锁竞争:</span><br><span style="font-family: "Microsoft YaHei"">如果说锁是从资源竞争的角度,解决了数据安全的问题。</span><br><span style="font-family: "Microsoft YaHei"">ThreadLocal则是在每个线程中,只保存(只隔离)出与自己当前业务相关的数据。</span><br><span style="font-family: "Microsoft YaHei"">注意他只是保证了数据的独立性,并不是独立创建了一份副本,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )所以如果使用全局数据放置到value中时,一样可能会有数据安全问题。(当然这也是不推荐的用法)</span><br><span style="font-family: "Microsoft YaHei"">比如有一份UserCache的全局缓存,多线程使用时,</span><br><span style="font-family: "Microsoft YaHei"">我可以在全局中对UserCache进行加锁处理,也可以每个线程独立引用自己的UserInfo,线程之间互不干扰。结构就像这个样子:</span></p>
<p><span style="font-family: "Microsoft YaHei"">全局加锁:</span></p>
<p><img src="https://img2024.cnblogs.com/blog/704073/202509/704073-20250929154021459-1004970887.png" alt="tljingzheng" loading="lazy"></p>
<p><span style="font-family: "Microsoft YaHei"">线程各自引用:</span></p>
<p><img src="https://img2024.cnblogs.com/blog/704073/202509/704073-20250929153958989-1539276905.png" alt="tlyinyong" loading="lazy"></p>
<p><span style="font-family: "Microsoft YaHei"">不知讲到这里大家还有没有最初的直觉了,为啥不设计一个全局的 Map<Thread,Object>。这样不是更简单,也更好定位问题:</span></p>
<p><span style="font-family: "Microsoft YaHei"">细想一下,就会发现这样并不好:</span><br><span style="font-family: "Microsoft YaHei"">方案1,全局只有一个Map,value是当前线程的所有缓存数据。那么Object就是一个非常复杂的数据,每次对Object进行读取都要解析的特别复杂。</span><br><span style="font-family: "Microsoft YaHei"">方案2,全局定义的很多个Map,每个map是一个业务的缓存,比如User,就有userMap,token就有tokenMap。先不论Map本来就会有竞争的问题,对于管理大量的Map就是一件头痛的事情。</span></p>
<p><span style="font-family: "Microsoft YaHei"">当然还是要根据具体业务来看,不能一概而论,并不能说任何时候使用ThreadLocal更好,使用全局Map更弱</span></p>
</div>
<div id="MySignature" role="contentinfo">
<p style="background: #C0FFFF; padding: 10px; border: 1px dashed #E0E0E0; font: 100% 微软雅黑; color: #F00; text-align: center">
如果你觉得写的不错,欢迎转载和点赞。
转载时请保留作者署名jilodream/王若伊_恩赐解脱(博客链接:http://www.cnblogs.com/jilodream/</p><br><br>
来源:https://www.cnblogs.com/jilodream/p/19118986
頁:
[1]