多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。
同步的措施一般是加锁,需要使用者对锁有一定的了解,加重了使用者的负担。
ThreadLocal 是 JDK 包提供的,它提供了线程本地变量,如果你创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里的变量,从而避免了线程安全问题。创建一个 ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存。
public class TheadLocalTest {
/**
* print 函数
* @param str
*/
static void print(String str){
//打印当前线程本地内存中 localVeriable 变量的值
System.out.println(str + ":" + localVeriable.get());
//清除当前线程本地内存中的 localVeriable 变量
localVeriable.remove();
}
/**
* 创建 localVeriable 变量
*/
static ThreadLocal<String> localVeriable = new ThreadLocal<String>();
public static void main(String[] args) {
Thread threadOne = new Thread(new Runnable() {
public void run() {
//设置线程 one 中本地变量 localVeriable 的值
localVeriable.set("threadOne local variable");
//调用打印函数
print("threadOne");
//打印本地变量值
System.out.println("threadOne remove after:" + localVeriable.get() );
}
});
Thread threadTwo = new Thread(new Runnable() {
public void run() {
//设置线程 two 中本地变量 localVeriable 的值
localVeriable.set("threadTwo local variable");
//调用打印函数
print("threadTwo");
//打印本地变量值
System.out.println("threadTwo remove after:" + localVeriable.get() );
}
});
threadOne.start();
threadTwo.start();
}
}
ThreadLocal的实现原理
Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals,他们是 ThreadLocalMap 类型的变量,而 ThreadLocalMap 是一个定制化的 HashMap。
默认情况下,每个线程中的者两个变量都为 null, 只有当前线程第一次调用 ThreadLocal 的 set 或者 get 方法时才会创建他们。每个线程的本地变量不是存放在 ThreadLocal 实例里面而是存放在调用线程的 threadLocals 里变量里面。
也就是说,ThreadLocal 类型的本地变量放在具体的线程内存空间中。ThreadLocal 也就是一个工具壳,它通过 set 方法把 value 值放入调用线程的 threadLocals 里面存放起来,当调用线程调用它的 get 方法时,再从当前线程的 threadLocals 变量里面将其拿出来使用。
如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的 threadLocals 变量里面,所以当不需要使用本地变量时可以通过调用 ThreadLocal变量的 remove方法,从当前线程的 threadLocals 里面删除该本地变量。
因为每个线程可以管理多个 ThreadLocal 变量,所以 Thread 里面的 threadLocals 被设计为 map 结构。
void set(T value)
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//将当前线程作为key,去查找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//第一次调用就创建当前线程对应的 HashMap
createMap(t, value);
}
ThreadLocalMap getMap(Thread t)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap 的作用是获取线程自己的变量 threadLocals,threadLocals 变量被绑定到了线程的成员变量上。
如果 getMap 的返回值不为空,则把 value 值设到 threadLocals 中,也就是把当前变量值放入当前线程的内存变量 threadLocals 中,threadLocals 是一个 HashMap 结构,key 是当前 ThreadLocal 的实例对象引用, value 是通过 set 方法传递的值。
void createMap(Thread t, T firstValue)
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的 threadLocals 变量
ThreadLocalMap map = getMap(t);
// 如果 threadLocals 不为 null,则返回对应本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//threadLocals 为空则初始化当前线程的 threadLocals 成员变量
return setInitialValue();
}
T setInitialValue()
private T setInitialValue() {
//初始化为 null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果当前线程的 threadLocals 变量不为空
if (map != null)
map.set(this, value);
else
//如果当前线程的 threadLocals 变量为空
createMap(t, value);
return value;
}
void remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
总结
在每个线程内部都有一个名为 threadLocals 的成员变量,该变量的类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 为我们使用 set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals 中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出。所以使用完毕后要调用 ThreadLocal 的 remove 方法删除对应线程的 threadLocals 中的本地变量。
ThreadLocal 不支持继承性
同一个 ThreadLocal 变量在父线程中被设置后,在子线程中是获取不到的
public class ThreadLocalTest1 {
/**
* 1.创建线程变量
*/
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
//2.设置线程变量
threadLocal.set("hello world");
//3.启动子线程
Thread thread = new Thread(new Runnable() {
public void run() {
//4.子线程输出线程变量的值
System.out.println("thread:" + threadLocal.get());
}
});
thread.start();
//主线程输出线程变量的值
System.out.println("main:" + threadLocal.get());
}
}
ThreadLocal 中子线程 thread 里面调用 get 方法时当前线程为 thread 线程,而 调用 set 方法设置线程变量的时 main 线程,两者是不同的线程,所以子线程访问时返回null。