[TOC]

ThreadLocal

ThreadLocal为变量在每个线程中都创建了一个副本,每个线程访问自己内部的副本变量,而彼此不会相互影响。

例子:

public static void main(String[] args) {
final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(2020);
System.out.println("parent-thread-s:" + threadLocal.get());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child-thread-s:" + threadLocal.get());
threadLocal.set(2021);
System.out.println("child-thread-e:" + threadLocal.get());
}
});
thread.start();
System.out.println("parent-thread-e:" + threadLocal.get());
}

输出:

parent-thread-s:2020
parent-thread-e:2020
child-thread-s:null
child-thread-e:2021

Process finished with exit code 0

有利必有弊,虽然解决了上面说到的问题,但是每个线程都创建了变量副本,这时就要考虑它对资源的消耗,使用ThreadLocal时占用的内存资源肯定是会比没有使用的时占用的多。

在之前的文章有提到synchronized,同样是解决线程安全的,那么他俩到底有啥区别呢?

synchronized,采用的是阻塞的方式,让线程等待,保证有序进行访问;不占用其他资源,可以说是采用了时间换取空间的策略;而ThreadLocal和线程同步机制相比较,恰好相反,它采用的是以空间换取时间的策略。

下面来看下ThreadLocal的源码

set(T value)

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 getMap(Thread t) {
return t.threadLocals;
}

从代码中可以看到,一个线程中的多个ThreadLocal是通过该线程的ThreadLocalMap来管理的;也就是说,每个线程都维护有一个ThreadLocalMap。当在线程中使用来自其他线程的ThreadLocal时,在set的时候,是在根据线程名查找自己线程中是否有ThreadLocalMap,若没有则创建。

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap构造函数

ThreadLocalMap是ThreadLocal的一个静态内部类,一个简单的Map结构,阈值为16*2/3,底层为数组,数组元素为Entry,Entry采用的是弱引用的方式。

private static final int INITIAL_CAPACITY = 16;
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);
}
private void setThreshold(int len) {
threshold = len * 2 / 3;
}

Entry持有ThreadLocal的弱引用,而table属于ThreadLocalMap,因而,ThreadLocalMap的key采用对ThreadLocal的弱引用。弱引用的生命周期短语软引用,远短于强引用,因此,还需注意垃圾回收的情况。

/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

get()

get方法先是获取到当前线程的ThreadLocalMap,再根据ThreadLocal这个key来获取值;

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();
}

根据hashcode值来进行获取数组中对应的Entry对象元素;

private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

如果没有获取到,那么用线性碰撞的方式;

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;

while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

该位置置空,再哈希;

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;
}

remove()

移除的是当前线程中ThreadLocalMap对应的ThreadLocal key值,解除弱引用;

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
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;
}
}
}

总结

  • ThreadLocal为变量在每个线程中都创建了一个副本,每个线程访问自己内部的副本变量,而彼此不会相互影响;
  • 一个ThreadLocal对应着多个ThreadLocalMap,每个ThreadLocalMap对应着一个线程;
  • 如果我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来,那么通常在这个类中定义private static类型的ThreadLocal 实例。