ThreadLocal与InheritableThreadLocal的使用

​ ThreadLocal,线程本地存储,为变量在每个线程中都创建了一个副本,那么每个线程可以独立地改变和访问自己的副本变量,而不会影响其它线程所对应的副本变量。从线程的角度看,目标变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

​ ThreadLocal不是用来解决对象共享访问问题的,而是提供了保持对象的方法和避免参数传递的对象访问方式。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象。

​ 在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个 threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为 value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

在Thread类中会发现有threadLocals与inheritableThreadLocals两个成员变量,都是ThreadLocal.ThreadLocalMap类。

1
2
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocalMap这个类是ThreadLocal类的一个内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

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

可以看出ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

总的来说,实际上是通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中,threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,这样每个线程中可有多个threadLocal变量。

简单示例:

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
public class ThreadLocalTest {
ThreadLocal<StringBuilder> a = new ThreadLocal<>();
ThreadLocal<String> b = new ThreadLocal<String>() {
protected String initialValue() {
return Thread.currentThread().getName() + "---zero";
}
};

public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test = new ThreadLocalTest();

System.out.println(test.a.get());
System.out.println(test.b.get());

Thread thread1 = new Thread() {
public void run() {
test.a.set(new StringBuilder("hello"));
System.out.println(test.a.get());
System.out.println(test.b.get());
};
};
thread1.start();
thread1.join();

System.out.println(test.a.get());
System.out.println(test.b.get());
}
}

运行结果:

1
2
3
4
5
6
null
main---zero
hello
Thread-0---zero
null
main---zero

​ InheritableThreadLocal类继承于ThreadLocal类,所以它具有ThreadLocal类的特性,但又是一种特殊的 ThreadLocal,其特殊性在于InheritableThreadLocal变量值会自动传递给所有子线程,即在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。而普通ThreadLocal变量不行。

​ 如果一个子线程调用InheritableThreadLocal的get(),那么它将与它的父线程看到同一个对象。为保护线程安全性,应该只对不可变对象(一旦创建,其状态就永远不会被改变的对象)使用InheritableThreadLocal,因为对象被多个线程共享。

​ 简单示例:

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
public class InheritableThreadLocalTest {
InheritableThreadLocal<StringBuilder> a = new InheritableThreadLocal<>();
InheritableThreadLocal<String> b = new InheritableThreadLocal<String>();

public static void main(String[] args) throws InterruptedException {
final InheritableThreadLocalTest test = new InheritableThreadLocalTest();

test.a.set(new StringBuilder("main---hello"));
test.b.set("main---zero");
System.out.println(test.a.get());
System.out.println(test.b.get());

Thread thread1 = new Thread() {
public void run() {
System.out.println(test.a.get());
System.out.println(test.b.get());
StringBuilder a = test.a.get().append("---subThread");
test.a.set(a);
test.b.set("subThread--zero");
System.out.println(test.a.get());
System.out.println(test.b.get());
};
};
thread1.start();
thread1.join();

System.out.println(test.a.get());
System.out.println(test.b.get());
}
}

运行结果:

1
2
3
4
5
6
7
8
main---hello
main---zero
main---hello
main---zero
main---hello---subThread
subThread--zero
main---hello---subThread
main---zero

可以看出,如果InheritableThreadLocal存储的是可变性(mutable)的对象,如StringBuilder,对于主线程设置的值,子线程可以通过get函数获取,但子线程调用set函数设置新值后,对主线程没有影响,对其它子线程也没有影响,只对自己可见,但如果子线程先get获取再修改对象的属性,那么这个修改对主线程和其它子线程是可见的,因为共享的是同一个引用。为了保护线程的安全性,一般建议只传递不可变(Immuable)对象,即没有状态的对象。