Java学习笔记(2)—ThreadLocal
不积小流无以成江海,不积跬步无以至千里
一、概念
ThreadLocal 是一个关于创建线程局部变量的类,主要作用是做数据隔离,保存到 ThreadLocal 中的数据只属于当前线程,该数据对其他线程而言是隔离的。即使用 ThreadLocal 保存的数据只能被当前线程访问,其他线程无法访问和修改。在多线程环境下,防止自己的变量被其他线程篡改。
注意:ThreadLocal 设计的目的就是为了能够在当前线程中有属于自己的变量,并不是为了解决并发或者共享变量的问题。
1.1 场景
public class Main {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 主线程设置值
threadLocal.set("value1");
System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
// 子线程
new Thread(new Runnable() {
@Override
public void run() {
// 子线程获取的值是:null
System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
}
}).start();
}
}
场景1:主线程初始化了一个 ThreadLocal 对象 threadLocal,并通过 threadLocal.set() 方法保存了一个值:“value1”,然后使用 threadLocal.get() 拿到设置的值。其中,子线程也使用 threadLocal.get() 去拿值。
问题:取值为null;
public class Main {
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 主线程设置值
User user = new User();
threadLocal.set(user);
System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
// 子线程
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(user);
// 获取到的对象地址是一样的,如果对它的值进行了修改,那么其他线程拿到的值也会改变。
System.out.println(Thread.currentThread().getName() + " = " + threadLocal.get());
}
}).start();
}
}
场景2:把变量换成是一个共享的对象保存到 ThreadLocal 中,那么多个线程的 ThreadLocal.get() 取得的还是这个共享对象本身;
问题:还是有并发访问问题。
三、源码分析
3.1 Set方法
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 利用当前线程获取一个 ThreadLocalMap 的对象
ThreadLocalMap map = getMap(t);
// 如果上面获取的 ThreadLocalMap 对象不为空,则设置值,否则创建这个 ThreadLocalMap 对象并设置值
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从 set 源码中,我们得知 ThreadLocalMap 是利用当前线程 Thread 作为参数获取的。源码如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
其实,上面的代码获取的是 Thread 对象的 threadLocals 变量。源码如下:
public class Thread implements Runnable {
省略其他内容...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
省略其他内容...
}
结论:可以知道 ThreadLocal 的数据是放入了当前线程的一个ThreadLocalMap 实例中,key 就是 ThreadLocal 对象本身,所以只能在本线程中访问,其他线程无法访问,从而实现了数据隔离。
3.2 get方法
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 以 ThreadLocal 对象本身作为 key,获取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果 ThreadLocalMap 对象不存在,就设置初始值并返回
// 从下面 setInitialValue() 的源码可知,设置的初始值是一个 null
return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial 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;
}
protected T initialValue() {
return null;
}
主要工作原理
- Thread 类中维护着一个 ThreadLocalMap 类型的成员变量。
- ThreadLocalMap 是一个定义在 ThreadLocal 类中的内部类,是一个 map,用Entry 来进行数据存储。
- 当调用 ThreadLocal 的 set() 方法时,先获取当前线程的 ThreadLocalMap 对象,然后以 ThreadLocal 对象作为 key 往ThreadLocalMap 中设置值。
- 当调用 ThreadLocal 的 get() 方法时,也是先获取当前线程的 ThreadLocalMap 对象,以 ThreadLocal 对象作为 key 从 ThreadLocalMap 中获取值。
- ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程在ThreadLocalMap 中获取或设置值。
参考
Star Zheng 《聊一聊Java中的ThreadLocal》