1.为什么要使用ThreadLocal
多线程访问同一个变量时,特别容易出现并发问题,特别是在写入时。为了保证线程的安全,一般在访问共享变量时需要进行同步。同步一般是通过加锁来实现,但这对使用者的要求比较高,需要对锁有一定的了解,对小白非常的不友好。
那有没有一种方式可以做到当一个变量创建后,每个线程对其进行访问时都访问的是自己的变量呢?
ThreadLocal就可以解决这个问题。Threadlocal是JDK包提供的,它提供了线程本地变量。也就是当你创建了一个ThreadLocal本地变量时,访问这个变量的每个线程都会有这个变量的一个本地副本。当多线程操作这个变量时,就相当于在操作自己本地内存里的变量,从而避免了线程安全问题。创建了一个ThreadLocal变量后,每个线程都会将变量复制到自己的本地内存中,如下图:
2.ThreadLocal如何使用?
ThreadLocal的使用很简单,直接上代码:
publicpublic class ThreadLocalTest {
static void print(String str) {
//打印本地内存中的loacalVariable变量
System.out.println(str + ":" + localVariable.get());
//清除本地内存中的loacalVariable变量
localVariable.remove();
}
//创建loacalVariable变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
public static void main(String args[]){
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中loacalVariable的值
localVariable.set("thread1 LocalVariable");
//打印线程1中loacalVariable的值
print("thread1");
System.out.println("thread1 remove after:"+localVariable.get());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程2中loacalVariable的值
localVariable.set("thread2 LocalVariable");
//打印线程2中loacalVariable的值
print("thread2");
System.out.println("thread2 remove after:"+localVariable.get());
}
});
thread1.start();
thread2.start();
}
}
上述代码分别创建了两个线程。在线程中通过set方法设置本地localVariable的值,每个线程中都拥有localVariable的本地值,两个线程间不受影响。最后结果打印:
3.ThreadLocal源码解析
先看一下ThreadLocal相关类的类图
可以看出,Thread类中有两个ThreadLocalMap的变量。ThreadLocalMap是一个定制化的hashmap,在默认情况下,每个线程中这两个变量的值都为null,当线程第一次调用ThreadLocal的set方法或get方法时才会创建它们。
其实每个线程的本地变量不是存放在ThreadLocal实例中的,而是存放在线程自身的threadLocal变量里面。也就是说ThreadLocal变量只是一个空壳,它并不是真正存放线程的本地变量的地方。我们只是通过ThreadLocal变量来set或get线程里的threadLocals中的值。这也是为什么threadLocals是ThreadLocalMap的原因,因为一个线程可以存放多个ThreadLocal。
接下来简单分析一下ThreadLocal的源码:
1.set(T value)
void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //根据当前线程得到它的threadLocals变量
if (map != null)
map.set(this, value); //以此ThreadLocal变量为key,放入到此线程的value中
else
createMap(t, value); //第一次调用,创建新的ThreadLocalMap
}
set方法先获取了当前执行的线程,然后得到当前线程的ThreadLocalMap对象。ThreadLocalMap变量中存放了以ThreadLocal变量为Key的值。如果map对象为空,则创建新的ThreadLocalMap。getMap(Thread t)的代码如下:
ThreadLocalMap getMap(Thread t){
return t.threadLocals;
}
创建ThreadLocalMap的方法createMap(Thread t ,T firstValue)代码如下:
voidcreateMap(Thread t,T firstValue){
t.threadLocals = new ThreadLocalMap(this,firstValue);
}
2.T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
if (map != null) {
//使用当前ThreadLocal变量作为key,取得这个Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //ThreadLocalMaps为空,则初始化当前线程的threadLocals成员变量
}
get方法也是先获取当前线程,从当前线程的threadLocals变量中获取以当前ThreadLocal变量为key的值。如果thredLocals为空,就先创建一个。setInitialValue()方法实现如下:
private T setInitialValue() {
T value = initialValue(); //初始化为null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) //再一次判断当前Map是不是空
map.set(this, value);
else
createMap(t, value); //本质还是调用了createMap函数
return value;
}
protected T initialValue() {
return null;
}
3.void remove()
public void remove(){
ThreadLocalMap map = getMap(Thread.currentThread());
if(m != null)
m.remove(this);
}
如果当前线程的threadLocals不为空,则删除指定ThreadLocal实例的本地变量。
使用ThreadLocal能不能在子线程中获取父线程的值呢?答案是不行,因为子线程和父线程不是同一个线程,在执行Thread。currentThread()时,得到的不是同一个线程,所以获取不到,那有没有解决方法,让子线程能够访问到父线程的值呢?可以看我的下一篇博文InheritableThreadLocal全面解析(附源码)