简述
实现变量的共享可以使用public static变量的形式,所有的线程都会共享这一变量,如果想要实现每一个线程都有属于自己的共享变量,就需要用到ThreadLocal。
例子
通过一个例子来描述以下需求:
- 创建两个线程A和B
- 在线程A、线程B以及主线程main中分别使用同一个ThreadLocal设值,然后进行多次读取
- 观察不同线程读到的值是否是本线程所有
【ThreadA】
public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("ThreadA" + (i + 1));
} else {
System.out.println("ThreadA get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
【ThreadB】
public class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("ThreadB" + (i + 1));
} else {
System.out.println("ThreadB get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
【ThreadLocal】
public class Tools {
public static ThreadLocal tl = new ThreadLocal();
}
【测试】
public class Run {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("Main" + (i + 1));
} else {
System.out.println("Main get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
【运行结果】
//截取部分运行结果
Main get Value=Main1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
Main get Value=Main1
可以发现,三个线程读到的值是自己独有的,互不影响
介绍
在ThreadLocal中,常用的方法有两个:set()和get()
- set()方法为ThreadLocal设值,get()方法进行取值,当没有设值时,get取到的值为null
- 可以为ThreadLocal对象设置初值,只需要重写initialValue()方法即可,这样即使没有设过值,get方法也会返回设置过的默认值如:
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return 1;
}
}
如果没有设值,并多次调用get,则get返回的默认值以第一次为准,比如返回一个格式化好的时间串,第一次调用get和五秒后调用get得到的值是一样的,都是第一次的值
需要注意,不同线程可以往同一个ThreadLocal对象设置,取值,他们之间互不影响,但是如果使用多个ThreadLocal对象,这多个对象之间是各自独立的。
源码简单看
下面来简单看一下ThreadLocal的源码,了解ThreadLocal内部是如何进行存值取值的
【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);
}
- 首先获取到当前线程t
- 根据t得到一个ThreadLocalMap,这个map是存取数据的关键!
- 如果map存在,设值,不存在则创建并设值,下面来看看这个map
【createMap】
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这里我们看到,线程t内部存在着一个ThreadLocalMap类型的变量threadLocals,
(Thread类中存在ThreadLocalMap类型的成员变量threadLocals,这里的ThreadLocalMap是ThreadLocal的一个静态内部类)
ThreadLocal.ThreadLocalMap threadLocals = null;
正是这个map维护了数据的存取,并实现了线程间的隔离(因为它为不同的线程new了不同的ThreadLocalMap对象)
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);
}
进一步看ThreadLocalMap的构造函数,在createMap的时候,传进来了this(即当前ThreadLocal对象)和我们想要设置的值,这里分别作为Entry的key和value,这里初始化了一个Entry数组,因为一个线程可以使用多个ThreadLocal,这个数组就用来维护不同的ThreadLocal以及对应的值。
我们对ThreadLocal进行设值,实际上就是对Entry进行设值(Entry是ThreadLocalMap的一个静态内部类)
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这里Entry类继承了WeakReference,将Entry的key设置为一个弱引用,而value是一个强引用,至于为什么这样设计,以及这样设计会导致内存泄露问题,大家可以先去自行了解
【get方法】
前面set方法分析过后,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拿到对应的ThreadLocalMap,如不为空,再继续拿Entry,然后拿到value返回;如果为空,则返回设置的初始值
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方法去设置,你要是没有设置的话,就返回null了
简单总结
讲了这么多,简单就是下面的流程
- ThreadLocal有个静态内部类ThreadLocalMap,ThreadLocalMap有个静态内部类Entry
- Thread类中存在ThreadLocalMap类型的成员变量threadLocals,它的初始值为null
- 当一个线程内部第一次使用任意的ThreadLocal对象时,threadLocals进行初始化(只初始化一次),每个线程初始化的threadLocals是不同的,从而实现了数据隔离
- threadLocals内部的Entry数组类型的成员变量table对传入的key和value进行维护,table在初始化threadLocals时被初始化
-
Entry对象的key是ThreadLocal类型,value是Object类型,当然你可以使用泛型的方式去规定value的具体类型