ThreadLocal源码解析
多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。
JDK包提供了ThreadLocal,它提供了线程本地变量,也就是说,如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。如下图:
ThreadLocal简单例子
下面看一个简单的ThreadLocal的例子
1 | public class ThreadLocalTest { |
从上图的运行结果可以看出,它们的值都是线程独有的,互相不影响。
ThreadLocal实现原理
首先看一下ThreadLocal相关类的类图结构
由该图可知,Thread类中有一个threadLocals和inheritableThreadLocals变量,它们都是ThreadLocalMap类型。而ThreadLocalMap是一个定制化的HashMap。
它们默认值为null
1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建它们。起始每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面。ThreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal是一个工具壳,它通过set方法把value值放入线程的threadLocals变量里面将其拿出来使用,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。
TheadLocal主要方法
set
首先看下源码
1 | /** |
整体的流程就是:获取当前线程的引用,然后调用getMap方法,获取threadLocalMap的实例,接着设置值。
getMap
接下来看下getMap方法
1 | /** |
getMap(t)的作用是获取线程自己的变量threadLocals,threadLocal变量被绑定到了线程的成员变量上。
get
下面看下get方法
1 | /** |
流程大致是:获取当前线程的引用,继而获取该线程的threadLocalMap实例,接着从map中获取对应线程的值,如果有的话。否则就调用setInitialValue()方法,设置初始值。
setInitialValue
1 | /** |
如果当前线程的threadLocals变量不为空,则设置当前线程的本地变量值为null,否则调用createMap方法创建当前线程的createMap变量。
initialValue方法可以被重写,默认为返回null。
createMap方法会创建一个新的ThreadLocalMap对象。
1 | /** |
remove
remove方法实现为:如果当前线程的threadLocals变量不为空,则删除当前线程中执行ThreadLocal实例的本地变量。
1 | /** |
总的来说,在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法深处对应线程的threadLocals中的本地变量。
ThreadLocal不支持继承性
看下下面这个例子
1 | public class ThreadLocalTest { |
它的结果是:
也就是说,同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。因为子线程thread里面调用get方法时当前线程为thread线程,而这里调用set方法设置线程变量的是main线程,两者是不同的线程,自然子线程访问时返回null。
InheritableThreadLocal类
InheritableThreadLocal类继承自ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。
InheritableHereadLocal继承了ThreadLocal类,并重写了getMap、createMap和childValue三个方法。
1 | public class InheritableThreadLocal<T> extends ThreadLocal<T> { |
getMap
1 | /** |
当调用getMap方法获取ThreadLocal变量时,不再返回threadLocals,而是返回inheritableThreadLocals变量。
createMap
1 | /** |
当第一次调用set方法时,创建的是当前的inheritableThreadLocals变量的实例,而不是threadLocals。
childValue
1 | /** |
childValue方法的应用场景比较复杂,得从创建线程的时候说起。
在创建Thread时候,有这么几句:
1 | private void init(ThreadGroup g, Runnable target, String name, |
在创建线程的时候,构造函数会调用init方法,第4行先获取了当前线程的引用,作为父线程,然后判断主线程的inheritThreadLocals的值是否为null,不为null的话就调用createInheritedMap方法,传入父线程的inheritableThreadLocals参数。
1 | /** |
接下来看下ThreadLocalMap方法
1 | /** |
在17行那句代码,会调用重写的childValue方法,将父线程的值,复制到子线程的ThreadLocalMap对象中。
因此,只要把ThreadLocal不支持继承性章节中的例子改为:
public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();