ThreadLocal的原理解析
-
ThreadLocal:
ThreadLocal
-
概念:多线程访问同一个共享变量时容易出现并发问题,特别是多个线程需要对一个共享变量写入时, 为了保证线程安全,在访问共享变量时需要进行适当的同步。
-
同步的措施一般是加锁,但是使用加锁的方式增加了性能的损耗,因此可以使用ThreadLocal来实现。
-
ThreadLocal是JDK提供的,提供了
线程本地变量
,也就是如果创建了一个TreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本
。因此当多个线程操作这个ThreadLocal变量时,其实操作的是自己本地内存
里面的变量,从而避免了线程安全问题。public class ThreadLocalTest { //创建ThreadLocal变量 static ThreadLocal<String> threadLocal = new ThreadLocal<>(); //print函数 static void print(String string){ //打印当前线程本地内存中的threadLocal System.out.println(string+":"+threadLocal.get()); //清除当前线程本地内存中的threadLocal // threadLocal.remove(); } public static void main(String[] args) { //创建线程 Thread threadA = new Thread(new Runnable() { @Override public void run() { //设置线程A中本地变量threadLocal的值 threadLocal.set("threadA"); //调用打印函数 print("threadA"); System.out.println("threadA remove "+threadLocal.get()); } }); //创建线程 Thread threadB = new Thread(new Runnable() { @Override public void run() { //设置线程B中本地变量threadLocal的值 threadLocal.set("threadB"); //调用打印函数 print("threadB"); System.out.println("threadB remove "+threadLocal.get()); } }); threadA.start(); threadB.start(); } }
执行threadLocal.remove()之后结果
-
以上代码中创建了一个共享变量threadLocal,两个线程,线程A和线程B,当我们在线程A中对共享变量设置值时,并不会影响线程B中threadLocal的值,线程中通过set方法设置threadLocal的值,其实设置的是线程中本地内存中的一个副本,这个副本线程B是访问不了的。同时通过get获取的是当前线程本地内存中的值。
-
实现原理
:
ThreadLocal的结构 -
通过图中我们可以看到
Thread
类中有一个threadLocals
和inheritableThreadLocals
,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的HashMap
。在默认情况下每个线程中的threadLocals和inheritableThreadLocals都为null,当线程第一次调用set或get时才会创建
它们。 -
每个线程的本地变量并不存在ThreadLocal实例里面,而是存放在线程中的
threadLocals
变量中,因此ThreadLocal就是一个工具壳,通过set将值放入线程中的threadLocals中,通过get将值从threadLocals中取出来,也可以通过调用remove将当前线程的threadLocals中的值删除。 -
那么为什么threadLocals为什么被设置成map结构,因为
一个线程可能关联多个ThreadLocal变量
。 -
每个线程内部都有一个threadLocals的成员变量,该变量类型为HashMap,
key
为ThreadLocal实例对象的引用
,value
则是需要设置的值
。每个线程
的本地变量都存放在线程自己的内存变量threadLocals
中,如果线程不死亡,那么该变量会一直存在,因此会造成内存溢出,因此使用完毕应当调用remove
删除threadLoccals中的变量。从源码中我们也可以看到是通过threadLocals来实现的
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); 调用getMap,当前线程作为key,去查找对应的线程变量 ThreadLocalMap map = getMap(t); //如果map不为空,则调用set方法,key为当前ThreadLocal的实例对象引用,value是传递的值。 if (map != null) map.set(this, value); else //如果是第一次则调用 createMap(t, value); } ThreadLocalMap getMap(Thread t) { //在getMap中获取的是当前线程的threadLocals return t.threadLocals; }
另一个类
lnheritableThreadLocal
类是为了解决子线程可以访问父线程中设置的本地变量
。public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * * @param parentValue the parent thread's value * @return the child thread's initial value */ protected T childValue(T parentValue) { return parentValue; } /** * Get the map associated with a ThreadLocal. * * @param t the current thread */ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } /** * Create the map associated with a ThreadLocal. * * @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); } }
InheritableThreadLocal继承ThreadLocal
,并重写三个方法。因此当调用set
方法时,创建
的是当前线程的inheritableThreadLocals
变量的实例而不再是threadLocals,调用get
方法时获取当前线程内部的map变量时,获取的是inheritableThreadLocals
而不再是threadLocals。因此在InheritableThreadLocal的世界里,变量inheritableThreadLocals替代
了threadLocals。
-