ThreadLocal
Posted 有且仅有
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。
我们知道线程也是一个「对象」,当线程这种对象想为我们提供一个「可以存取我们自定义变量的功能时」,来看下它是怎么做的。
一、Thread 中
为
Thread
增加一个成员变量java.lang.Thread#threadLocals
,并让我们间接操作它(它是一个自定义静态内部类static class ThreadLocalMap
):public class Thread implements Runnable /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; // ...
简单分析:
null
: 代表懒加载,只在你需要存数据时才会实例化一个ThreadLocalMap
对象。default
: 默认访问修饰符,代表只想让java.lang
下被访问,就是给java.lang.ThreadLocal
用。
为什么不在
Thread
里面给我们暴露方法?这不符合面向对象思想。所以要把这个功能「封装」为一个类,称为线程本地变量
java.lang.ThreadLocal
,每一个你需要的变量就是一个此类的实例。
二、ThreadLocal 实现代码
几个用到的类关系是:Thread
拥有一个 ThreadLocalMap
,ThreadLocalMap
拥有多个Entry
,Entry
拥有一个ThreadLocal
。
既然用
Map
存,那Entry
中Key
和Value
分别是什么?Key
: 线程变量对象ThreadLocal
。Value
: 你自定义操作的那个数据Object
。
当然,这个静态内部类
Map
肯定对使用者透明所以我们不能直接操作这个
Map
,而是使用的ThreadLocal
的三个public
方法:get()/set()/remove()
set 方法
public void set(T value) // 获取当前线程 Thread t = Thread.currentThread(); // 简单的返回Thread 对象的成员变量threadLocals ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);
get 方法
public T get() Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) // 这个Map 没有get方法只有getEntry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) // 你之前设置的值 return (T)e.value; return setInitialValue();
remove 方法
public void remove() ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); /** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) if (e.get() == key) e.clear(); // 清除对ThreadLocal的引用 expungeStaleEntry(i); // 删除数组中的Entry return;
三、分析ThreadLocalMap
实线 + 燕尾箭头:代表强引用,虚线 + 燕尾箭头:代表弱引用
key
为何为弱引用?而不是强引用?设计者希望:当
ThreadLocal
没有强引用时,GC将随时有权收回其所占内存。如果是强引用,将导致用户已不再强引用
ThreadLocal
时,其被Entry
强引用而不能释放。value
为何“不是”弱引用?难道能发生:通过
key
来get value
时value
却被GC回收了这种事?内存泄漏?
在
ThreadLocal
因没有强引用被回收后,value
将处于内存泄漏状态。直到:
- 线程死亡。
- 对同一个线程上的
另一个ThreadLocal对象
的get/set
操作,可能会移除那些为key == null
的Entry
。
如何避免内存泄漏?
- 使用完
TheradLocal
要手动调用remove()
。 - 尤其要注意使用线程池的场景,线程可能永远不死,更加要注意调用
remove()
。
- 使用完
回顾下对象“可达性”
- 强可达对象:不需要遍历任意“引用对象”就在对象图上可达的对象是强可达对象。
- 软可达对象:不是强可达对象,遍历软引用可达的对象,是软可达对象。
- 弱可达对象:不是强可达和软可达对象,遍历弱引用可达的对象,是弱可达对象。
- 虚可达对象:不是强可达、软可达和弱可达对象,其
finalize()
方法已被执行,被一个虚引用引用的对象,是虚可达对象。 - 不可达对象:当不能以上述任何方法到达某一对象时,该对象是不可达对象,因此可以回收此对象。
四、ThreaLocal 使用
Example
JavaDoc 中已经给出了,摘抄一下:
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() @Override protected Integer initialValue() return nextId.getAndIncrement(); ; // Returns the current thread's unique ID, assigning it if necessary public static int get() return threadId.get();
解释:初始化和
set()
方法首先,之所以要使用「匿名内部类」及其对象,是因为想让你在一行代码内就完成初始化。除此之外,
initialValue()
和set()
的实现代码区别不大。然后,一旦哪个
Thread
引用到这个ThreadLocal
对象了,就会在自己的threadLocals
中被设置一个值。get()
就是从当前线程的Map中取即可。
以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章