ThreadLocal解析
ThreadLocal解析
-
-
- 基本概念
- 使用示例
- 实现原理
- ThreadLocalMap
- ThreadLocal不支持继承性
- InheritableThreadLocal类
- 总结
-
基本概念
在多线程编程中,线程间数据共享是非常常见的问题,但是容易出现并发问题,特别是多个线程对同一共享变量进行写入时。为了保证线程安全,一般使用者在访问成员变量时需要进行适当的同步。
我们通常会使用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//第一次调用就创建当前线程对应的HashMapcreateMap(t, value);}/* 从当前线程的ThreadLocalMap中获取值。*/public T get() {//获取当前线程Thread t = Thread.currentThread();//将当前线程作为key,查找对应的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//通过this:当前实例对象获取ThreadLocalThreadLocalMap.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//第一次调用就创建当前线程对应的HashMapcreateMap(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.valueEntry 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
,我们可以在保证线程安全的前提下,又不会带来太大的性能问题。