ThreadLocal
1 : 简介
JDK1.8中有下面这段话
此类提供线程局部变量。这些变量不同于它们的正常对应项是,每个访问一个线程的线程(通过{@code get}或{@code set}方法)有自己的独立初始化变量的副本。{@code ThreadLocal}实例通常是私有的希望将状态与线程关联的类中的静态字段(例如。,用户ID或事务ID)。
以下是jdk_api中的部分说明:
其中有如下三段话:
1、这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
2、例如,下面的类生成每个线程本地的唯一标识符。 线程的ID在第一次调用ThreadId.get()时被分配,并在后续调用中保持不变。
3、只要线程存活并且ThreadLocal实例可以访问,每个线程都保存对其线程局部变量副本的隐式引用; 线程消失后,线程本地实例的所有副本都将被垃圾收集(除非存在对这些副本的其他引用)。
其实大概几个点
1、ThreadLocal是局部变量。
2、每个线程自己独立初始化变量副本。
3、线程保存对其线程局部变量副本的隐式引用(线程存活,实例可访问)。
4、线程消失变量副本被垃圾收集。
2 : 方法
通过看其源码,会发现在ThreadLocal中有一个静态内部类ThreadLocalMap,而内部类ThreadLocalMap中又有一个内部类Entry,Entry类中包含Object属性,其构造函数中,将T赋值给了Object。
3 : set方法
源码如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
其中ThreadLocalMap 为内部类,map.set(this, value);这一步操作,其实就是将ThreadLocalMap中key为当前线程的value替换掉,源码如下:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
createMap(t, value);这一步操作,就是创建实例ThreadLocalMap,源码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
至于怎么new的,下面就是源码,其实这个不需要关心,只需要知道new了就行了。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
至此,set做的事情也就清晰了,用当前线程从内部类ThreadLocalMap获取值,不为空就更新掉,为空就创建一个ThreadLocalMap
4 : get方法
源码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
可以看到T的取值,ThreadLocal -> ThreadLocalMap -> Entry -> Object属性,就是拿当前线程当做Key去取值,取到值直接返回。
当取不到值时,会调用 setInitialValue(); 方法。
源码如下:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
其中initialValue();源码
protected T initialValue() {
return null;
}
如果刚才认真看了set方法,那么是不是对这个setInitialValue非常熟悉。
多了个value,修饰符修改成了private。通过源码可以知道value的默认值为null,后面就是和set方法操作基本一致了,也就是把null,放入key为当前线程的ThreadLocalMap中去。
至此,get方法,其实做的操作就很明白了,取ThreadLocal.ThreadLocalMap.Entry.value的值,取不到就设置默认值null。
5 : remove方法
同理贴上源码如下:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
内部类ThreadLocalMap的remove方法源码:
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();
expungeStaleEntry(i);
return;
}
}
}
e.clear();源码:
public void clear() {
this.referent = null;
}
expungeStaleEntry(i);源码:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
其操作也很简单,取ThreadLocalMap,不为空就把它清理掉。
通俗点就是6里面的那一套比喻,掏掏口袋,有东西就拿出来全扔掉。
6 : 关于ThreadLocal可能会导致的内存泄露
https://blog.csdn.net/qunqunstyle99/article/details/94717256
7 : 理解
写一些自己的总结
ThreadLocal用一种比喻的说法来说。
每个小团体(比如公司,家庭)都有很多人(每个进程都有很多线程)
团体的每个人都有口袋(进程的每个线程都有ThreadLocal)
每个人的口袋想装东西就可以装,不想装东西就可以空着(每个线程的ThreadLocal可以放T,也可以不放)
我的口袋的东西不会给你,你的也不会给我(每个线程都有自己独立初始化的变量副本,注意是独立的,这也就解释了互不干扰)
写到这也就差不多了,感兴趣的可以看一下源码。这种设计思路挺有趣的。
有什么不对的还望指正,书写不易,觉得有帮助就点个赞吧!