基本概念
在多线程编程中,线程间数据共享是非常常见的问题,但是容易出现并发问题,特别是多个线程对同一共享变量进行写入时。为了保证线程安全,一般使用者在访问成员变量时需要进行适当的同步。
我们通常会使用synchronized
或者Lock
等加锁方式进行线程同步,但是这些方式在性能方面是有一定的损耗的。那么有没有一种方式可以在保证线程安全的前提下,又不会带来太大的性能问题呢?ThreadLocal
就是这样一种机制。
ThreadLocal
是一种线程封闭的机制,它可以将数据隔离在每个线程中,每个线程都拥有一份独立的数据副本, 实际操作的是自己本地内存的变量。这样一来,不同的线程之间就不会出现数据共享的问题了。
使用示例
下面是一个简单的使用ThreadLocal的示例代码:
public class ThreadLocalDemo {
//创建ThreadLocal变量
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//创建线程
new Thread(() -> {
//设置threadLocal本地变量的值
threadLocal.set("hello world");
//使用get方法获取该值
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}, "thread1").start();
//创建线程
new Thread(() -> {
threadLocal.set("你好 世界");
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}, "thread2").start();
}
}
在这个示例代码中,我们创建了两个线程,分别向ThreadLocal
中设置了不同的值。由于ThreadLocal
是线程封闭的机制,因此每个线程都拥有自己的独立的数据副本,两个线程之间不会互相影响。输出结果如下:
thread1 hello world
thread2 你好 世界
实现原理
ThreadLocal的实现原理其实比较简单,它主要依赖于ThreadLocalMap
字段实现,其类图结构如下:
ThreadLocal
的类结构比较简单,主要包含以下几个方法:
set
:向当前线程的ThreadLocalMap
中存储值get
:获取当前线程的ThreadLocalMap
中的值remove
:清除当前线程的ThreadLocalMap中的值initialValue
:初始化值,可在子类中覆盖此方法以提供默认值
ThreadLocal
源码:
public class ThreadLocal<T> {
/**
* 返回当前线程对应的ThreadLocalMap。
* 这里的threadLocals是ThreadLocalMap类型的变量,作为存储多个ThreadLocal变量的容器,后面再具体解析
*/
private ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 在当前线程的ThreadLocalMap中设置值。
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//更新当前ThreadLocal变量的值,注意,这里this值ThreadLocal类的当前实例对象
map.set(this, value);
else
//第一次调用就创建当前线程对应的HashMap
createMap(t, value);
}
/**
* 从当前线程的ThreadLocalMap中获取值。
*/
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//将当前线程作为key,查找对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过this:当前实例对象获取ThreadLocal
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
//ThreadLocalMap为空则初始化当前线程的ThreadLocal变量值
return setInitialValue();
}
/**
* 从当前线程的ThreadLocalMap中清除值。
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 初始化值。
*/
protected T initialValue() {
return null;
}
/**
* 创建一个新的ThreadLocalMap并初始化值。
*/
void createMap(Thread t, T firstValue) {
//传入的key值是当前ThreadLocal的this引用
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* 获取初始值,如果子类未提供初始值,则返回null。
*/
T setInitialValue() {
//初始化为空
T value = initialValue();
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//第一次调用就创建当前线程对应的HashMap
createMap(t, value);
return value;
}
}
注:一开始被
ThreadLocal
、threadLocals
、ThreadLocalMap
搞晕了,后面发现其实很简单,直接把threadLocals
给踢了,只剩下ThreadLocal
、ThreadLocalMap
,这样就好理解多了。threadLocals
这玩意只是个变量名而已,不用关心。记住
ThreadLocal
类对应一个变量,里面有一些对应的方法,而ThreadLocalMap
是一个Map,里面有很多ThreadLocal
, 取的时候通过当前线程和ThreadLocal对象的哈希码,找到对应的Entry对象,然后返回它的value字段。
在ThreadLocal
类中,主要是通过对当前线程的ThreadLocalMap
的操作来实现线程本地变量的存储和获取,具体实现细节可以通过源码来了解。
ThreadLocalMap
Thread
类中有一个ThreadLocalMap
类型的字段threadLocals
,它是线程本地变量的存储容器。每个ThreadLocal
对象都有一个对应的entry
在threadLocals
中,用来存储线程对应的值。
ThreadLocalMap
是一个自定义的HashMap
,它的键为ThreadLocal
对象,值为线程保存的数据对象。在每个线程中, ThreadLocalMap
都是独立的,因此不同的线程之间不会出现数据共享的问题。由于ThreadLocalMap
是线程独立的,因此在多线程环境下不会出现竞争的问题,从而保证了线程安全性。
ThreadLocalMap
中的键值对的生命周期是跟随线程的,当线程结束后,ThreadLocalMap
中的键值对也会被回收。如何线程一直不消亡,会导致这些本地变量一直存在,所以可能造成内存溢出,因此,使用结束后记得要调用ThreadLocal
的remove
方法删除对应线程ThreadLocalMap
中存储的本地变量。
ThreadLocal
对象通过调用ThreadLocal
的set
方法将值存储到当前线程的ThreadLocalMap
中,通过调用ThreadLocal
的get
方法可以获取当前线程的值。ThreadLocal
的remove
方法可以清除当前线程中的ThreadLocalMap
中的键值对,从而释放资源。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
在Thread
类中使用到两个变量:threadLocals
和 inheritableThreadLocals
,都是 ThreadLocalMap
类型的变量。
ThreadLocals
是线程本地变量的存储容器。每个ThreadLocal
对象都有一个对应的entry
在ThreadLocals
中,用来存储线程对应的值。这里也就刚好引出另一个问题了:为什么threadLocals
被设计成map结构?
很明显,
ThreadLocals
要作为容器存储多个ThreadLocal本地变量,并通过每个线程进行关联,自然我们就想到了map了。
ThreadLocal不支持继承性
ThreadLocal
不支持继承性,也就是说,子线程无法访问父线程中的ThreadLocal
变量,因为子线程中的ThreadLocal
变量是子线程自己的,而不是从父线程中继承的。
举个例子,假设有一个父线程和一个子线程,父线程中有一个ThreadLocal
变量,当父线程启动子线程时,子线程无法访问父线程中的ThreadLocal
变量,因为子线程中的ThreadLocal
变量是子线程自己的,而不是从父线程中继承的。
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Hello, World!");
Thread thread = new Thread(() -> {
System.out.println("Child thread value: " + threadLocal.get());
});
thread.start();
System.out.println("Main thread value: " + threadLocal.get());
}
}
运行结果如下:
Main thread value: Hello, World!
Child thread value: null
可以看到,子线程中的ThreadLocal变量值为null,而不是从父线程中继承的值。
那有没有办法可以让子线程能够访问到父线程中的值呢?当然!
InheritableThreadLocal类
InheritableThreadLocal
类是Java
中的一个线程本地变量类,它继承了ThreadLocal
类,扩展了ThreadLocal
以提供从父线程到子线程的值继承。
下面是一个使用InheritableThreadLocal
类的例子:
public class InheritableThreadLocalExample {
public static void main(String[] args) {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("Hello World");
Thread thread = new Thread(() -> {
System.out.println(inheritableThreadLocal.get());
});
thread.start();
}
}
在这个例子中,我们创建了一个InheritableThreadLocal
对象,然后在主线程中设置了一个值。接着,我们创建了一个子线程,并在子线程中获取了这个值。由于InheritableThreadLocal
类提供了从父线程到子线程的值继承,所以子线程中可以获取到这个值,输出结果为"Hello World"
。
使用场景
InheritableThreadLocal
类的应用场景是当线程需要从父线程继承某些值时,可以使用InheritableThreadLocal
类。
例如,当需要在多个线程之间共享用户ID或事务ID等线程本地变量时,可以使用InheritableThreadLocal
类。InheritableThreadLocal
类提供了从父线程到子线程的值继承,因此子线程可以获取父线程中的值。
源码分析
Thread
类中定义了叫inheritableThreadLocals
的变量,类型为ThreadLocalMap
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
在InheritableThreadLocal
类中继承了ThreadLocal
,并重写了三个方法,替换了原有的threadLocals
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* 用于在子线程中创建一个新的InheritableThreadLocal对象时,为该对象提供一个初始值。
* 在默认情况下,该方法返回父线程中的值,因此子线程中的InheritableThreadLocal对象的初始值与父线程中的值相同。
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* 获取的是inheritableThreadLocals变量
* 而不再是ThreadLocals变量
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* 创建当前线程的inheritableThreadLocals变量实例
* @param t the current thread
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
再具体看一下childValue
方法的执行流程,以及如何访问父线程的本地变量
当我们在主线程中start一个子线程时,会new 一个Thread。创建线程时发生了什么才让父子线程的
InheritableThreadLocal
可以传递在
Thread
类中,有多个默认构造方法, 经过重载,都最终调用了init
方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
//最终调用
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//获取当前线程(父线程)
Thread parent = currentThread();
……
/* 如果inheritThreadLocals为true并且父线程中存在InheritableThreadLocal对象,
* 则使用ThreadLocal.createInheritedMap方法创建一个新的Map对象,
* 该对象包含父线程中所有InheritableThreadLocal对象的值。这个新的Map对象将作为
* 子线程的InheritableThreadLocalMap对象的初始值。
*/
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
inheritThreadLocals变量的值复制一份到新的//创建一个新的Map对象
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//将父线程的inheritThreadLocals变量的值复制一份到新的对象中
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//这里调用的是InheritableThreadLocal重写后的childValue方法
Object value = key.childValue(e.value);//返回e.value
Entry c = new Entry(key, value);
//计算hash值
int h = key.threadLocalHashCode & (len - 1);
//找到一个空的位置添加Entry实例,就是hashmap添加元素的过程
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
总结
ThreadLocal
是一种线程封闭的机制,它可以将数据隔离在每个线程中,每个线程都拥有一份独立的数据副本。
ThreadLocal
主要是通过ThreadLocalMap
和Thread
类中的ThreadLocalMap
字段实现,ThreadLocalMap
是一个自定义的HashMap
,用来存储线程本地变量的键值对,而Thread类中的ThreadLocalMap
字段threadLocals
则是用来存储每个线程的ThreadLocalMap
。
通过使用ThreadLocal
,我们可以在保证线程安全的前提下,又不会带来太大的性能问题。