ThreadLocal分析
什么是ThreadLocal
ThreadLocal是一种变量类型,与普通的局部变量和全局变量所不同的是,ThreadLocal是一种“线程变量”,在jdk中一开始设计是用来存储线程上下文的,作用域与线程绑定。通常使用private 和 static修饰 ThreadLocal变量,此时表示作用域为本类中的线程使用到的方法。当线程被销毁,对应的线程变量也会被清除(个人是这样理解的,因为使用Entry存储对应的value是虚引用,当对应的线程被销毁时,线程)。但是在当前大多数使用线程池来管理线程的场景中,线程是不会被销毁的,这也就代表着使用线程池管理线程的时候,必须要手动清除线程变量, 否则将会造成内存泄漏。
ThreadLocal变量通常被static修饰,好处是它可以避免重复创建TSO(Thread Specific Object,即ThreadLocal所关联的对象)所导致的浪费。坏处是这样做可能正好形成内存泄漏所需的条件。
在大多数精密的多线程代码中都有ThreadLocal的影子,理解ThreadLocal能帮助我们更好的理解java多线程。
ThreadLocal使用示例
public class ThreadlocalTest {
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void test() throws InterruptedException {
threadLocal.set("parent");
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
threadLocal.set("child");
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
});
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
}
public static void main(String[] args) throws InterruptedException {
new ThreadlocalTest().test();
}
}
输出结果:
Thread-0 ===> null
Thread-0 ===> child
main ===> parent
通过上面的示例,我们可以看到ThreadLocal在每个线程之间独立的,父线程与子线程之间的ThreadLocal与子线程的ThreadLocal之间没有关系,真正的每个线程拥有自己的属性。
源码分析
首先,我们要清楚,Thread、ThreadLocal、ThreadLocalMap还有ThreadLocalMap.Entry的关系;它们的关系可以理解为每一个线程拥有一个ThreadLocal.ThreadLocalMap属性的变量,而这个ThreadLocalMap变量中有一个内部类Entry,Entry中存储的<referent, value>键值对,其中referent是线程变量的引用,value则是我们通过ThreadLocal.set()设置的内容。多线程环境中,Thread、ThreadLocal、ThreadLocalMap还有ThreadLocalMap.Entry之间的关系如下图所示:
Thread中存储ThreadLocal.ThreadLocalMap属性的有两个变量:threadLocals、inheritableThreadLocals;threadLocals不可以在父子线程之间使用,而inheritableThreadLocals可以在父子线程之间使用。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal.set()方法以及set相关操作:
public class ThreadLocal<T>{
private final int threadLocalHashCode = nextHashCode();
public void set(T value) {
// 获取当前调用者线程
Thread t = Thread.currentThread();
// 尝试根据调用者线程获取该线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 向ThreadLocalMap插入<ThreadLocal, value>键值对
map.set(this, value);
else
// 在第一次set()的时候创建该线程的ThreadLocalMap
createMap(t, value);
}
// 在第一次set()或get()的时候创建该线程的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static class ThreadLocalMap{
/**
* 存储Entry的
*/
private Entry[] table;
// ThreadLocalMap 中用来存储<ThreadLocal引用变量,ThreadLocal 所保存的值>键值对
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 初始化ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始Entry大小为16
table = new Entry[INITIAL_CAPACITY];
// 地址下标 = 第一个(当前)ThreadLocal变量的hash值&15,获取一个必然小于15的值作为下标存储value
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 设置扩容阀值 = Entry大小(INITIAL_CAPACITY) * 2/3; 后续扩容大小 = 当前大小*2,扩容阀值=Entry大小* 2/3;
setThreshold(INITIAL_CAPACITY);
}
// 向Entry中插入<ThreadLocal,value>键值对
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
// 计算标
int i = key.threadLocalHashCode & (len-1);
// 使用 开放寻址法 解决hash冲突
for (Entry e = tab[i];
e != null;
// nextIndex(i,len) = ((i + 1 < len) ? i + 1 : 0)
e = tab[i = nextIndex(i, len)]) {
// 获取e的ThreadLocal引用
ThreadLocal<?> k = e.get();
// e的ThreadLocal引用 == 当前调用者线程,则将value覆盖原有value
if (k == key) {
e.value = value;
return;
}
// e的ThreadLocal引用为空,而e不为空,说明当前的ThreadLocal引用已经不可达,
// 将需要set的value和对应的ThreadLocal引用设置到当前的Entry中
// 该操作需要先解决ThreadLocal引用失效导致的hash值计算不连续问题,关于这一点可以了解一下hash表开放寻址法查找元素
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 新增结点到Entry
tab[i] = new Entry(key, value);
int sz = ++size;
// 尝试清除后续hash table中的结点
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}
}
ThreadLocal.get()方法以及get相关操作:
public class ThreadLocal<T> {
// 获取当前线程变量对应的value
public T get() {
// 获取当前调用者线程
Thread t = Thread.currentThread();
// 获取线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取ThreadLocalMap中的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
// Enrty不为空时尝试获取对应的value;
// 为空则表示在这之前进行过get()操作创建了ThreadLocalMap
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 返回默认值,默认返回null,可以通过子类重写该方法修改默认返回值
return setInitialValue();
}
// 获取当前线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
// 返回Thread中的ThreadLocalMap
return t.threadLocals;
}
/**
* ThreadLocal中用来存储<当前线程,存储的值>键值对
*/
static class ThreadLocalMap {
// hash表
private Entry[] table;
// 返回ThreadLocalMap中的hash表
private Entry getEntry(ThreadLocal<?> key) {
// 计算 hashKey
int i = key.threadLocalHashCode & (table.length - 1);
// 从hashTable Entry[]中获取对应的ThreadLocal引用
Entry e = table[i];
if (e != null && e.get() == key)
// Entry命中的条件是e存在与Entry && e的引用和key相同
return e;
else
// Entry未命中(e不存在||存在hash冲突导致e的引用和key不同),向后寻找可以命中的Entry或者返回最后一个null结点
return getEntryAfterMiss(key, i, e);
}
// 由于hash冲突导致的未命中,向后寻找可以命中的Entry,或者
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// e为空就直接返回null
// e不为空则向后寻找可以命中的Entry
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
// hash命中,返回e
return e;
if (k == null)
// 找到最后一个可以存放
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
}
/**
* 还没有通过set创建ThreadLocalMap时,使用get方法,默认创建一个ThreadLocalMap
* @return the initial value
*/
private T setInitialValue() {
// ThreadLocal类默认返回null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 创建一个新的ThreadLocalMap
createMap(t, value);
return value;
}
/**
* ThreadLocal中value默认值,可以通过子类重写该方法修改get()默认返回的值
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
}
ThreadLocal.remove()方法以及get相关操作:
public class ThreadLocal<T> {
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清除Entry中staleSlot下标的值
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 整理hashTable(Entry)
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
}
}
ThreadLocal总结
- 对于一个Thread来说,ThreadLocal只是其一个属性变量,其中使用ThreadLocalMap存放了该线程所存放的所有线程变量,主要以<ThreadLocal, value>形式存储。
- Thread与ThreadLocal是一对一的关系,也就是说Thread与ThreadLocalMap是一对一的关系。
- ThreadLocal为什么要使用ThreadLocalMap? 因为一个Thread中可能存放多个线程变量,存在一对多的关系,所以一个Thread要能够对应多个线程变量,线程变量还需要与自己的value一对一对应。
- 哈希冲突存在两种解决方法:开放寻址法和链表法。ThreadLocalMap中的Entry为什么要使用开放寻址法?使用开放寻址法解决冲突的散列表,装载因子的上限不能太大。这也导致这种方法比链表法更浪费内存空间。但是另一方面,链表法需要额外的空间,当结点规模较小时,开放寻址法较为节省空间。对于ThreadLocal线程变量来讲,将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放寻址法中的冲突,从而提高平均查找速度。
- 对于ThreadLocal中的ThreadLocalMap、ThreadLocalMap.Entry有疑问,看看hashTable的原理,可以很好的帮助理解ThreadLocal。
- 下图可以很好的帮助理解ThreadLocal变量、ThreadLocal.ThreadLocalMap以及Entry之间的关系。
InheritableThreadLocal分析
InheritableThreadLocal使用背景
其实上述讲解ThreadLocal的时候,使用到的示例中展示了ThreadLocal无法在父子线程之间传递ThreadLocal变量。但是有些时候我们是需要将父线程中的某些线程变量传递给子线程的,比如父线程的线程变量存储着用户信息,此时父线程开启了多个子线程,子线程需要把用户信息以及一些业务信息存储到数据库中,那么这个时候就需要把用户的信息传递给子线程的;对于这种场景有两种解决方案,方案一就是把用户信息提取为一个普通局部变量,作为参数传递给子线程的方法中;方案二则是将用户信息直接传给子线程,但是单纯凭借ThreadLocal无法实现变量传递,此时InheritableThreadLocal就可以开始发挥作用了。
InheritableThreadLocal使用实例
public class ThreadlocalTest {
private ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public void test() throws InterruptedException {
threadLocal.set("parent");
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
threadLocal.set("child");
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
});
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get());
}
public static void main(String[] args) throws InterruptedException {
new ThreadlocalTest().test();
}
}
输出结果
Thread-0 ===> parent
Thread-0 ===> child
main ===> parent
源码分析
先看下InheritableThreadLocal类,以及它所重写ThreadLocal的setMap和getMap方法:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* 创建子线程的时候浅拷贝返回父线程持有的InheritableThreadLocal.ThreadLocalMap.Entry.value的引用
* 如果想要深拷贝value,则需要子类重写该方法
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* 返回线程t的inheritableThreadLocals变量
* 使用时利用向下转型获取InheritableThreadLocal变量,所以InheritableThreadLocal.get()只会获取对应线程的inheritableThreadLocals变量
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* 设置线程t的inheritableThreadLocals变量
* 使用时利用向下转型设置InheritableThreadLocal变量,所以InheritableThreadLocal.set()只会设置对应线程的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);
}
}
那子线程又是如何获取到父线程的ThreadLocal变量呢,查看Thread类中新建一个线程源码:
public
class Thread implements Runnable {
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
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);
}
/**
* Initializes a Thread. 初始化线程
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread 是否从调用方继承InheritableThreadLocal变量
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 当前调用者线程为父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
// 如果inheritThreadLocals == true(默认new Thread都是true)并且父线程存在InheritableThreadLocal变量,
// 当前线程将会复制父线程的InheritableThreadLocal到本线程中
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
}
public class ThreadLocal<T> {
// 子线程复制父线程的ThreadLocal变量
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
static class ThreadLocalMap {
// 深拷贝ThreadLocalMap,浅拷贝Entry中的value
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
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.ThreadLocalMap.Entry.value
// InheritableThreadLocal默认使用浅拷贝(直接返回引用)
// 如果需要深拷贝,则需要InheritableThreadLocal子类重写childValue方法
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
// 计算下一个hash下标
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
}
}
InheritableThreadLocal 拷贝机制
InheritableThreadLocal与ThreadLocal的区别就在于父线程可以将InheritableThreadLocal变量深拷贝一份到子线程中,但是Entry中的vaue则是浅拷贝,也就是说假如子线程通过get()方法获取到对应的value之后,直接修改value的值,父线程中获取到的value的值也会进行相应的修改,如下:public class ThreadlocalTest {
private ThreadLocal<Person> threadLocal = new InheritableThreadLocal<>();
public void test() throws InterruptedException {
threadLocal.set(new Person("a", 13));
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get().toString() + "===>" +threadLocal.get().hashCode());
threadLocal.get().setAge(15);
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get().toString() + "===>" +threadLocal.get().hashCode());
});
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + " ===> " + threadLocal.get().toString() + "===>" +threadLocal.get().hashCode());
threadLocal.remove();
System.out.println();
}
public static void main(String[] args) throws InterruptedException {
new ThreadlocalTest().test();
}
}
输出结果:
Thread-0 ===> Person{name='a', age=13}===>2024651980
Thread-0 ===> Person{name='a', age=15}===>2024651980
main ===> Person{name='a', age=15}===>2024651980
在子线程中修改了get()获取到的value中的属性值,父线程通过get()获取到的value也发生了改变,且父子线程中get()获取到的value的地址是相同的。
InheritableThreadLocal总结
- 相比较ThreadLocal,InheritableThreadLocal完成了父子线程之间线程变量的传递,实现方案是父线程创建子线程的时候,父线程本身新建了一个InheritableThreadLocal变量存放inheritableThreadLocals中,子线程从父线程拷贝inheritableThreadLocals属性,而InheritableThreadLocal继承自ThreadLocal,使得其完美承载了ThreadLocal的属性,只是重写其中的setMap和getMap方法后使得其返回的map为线程的inheritableThreadLocals变量。
- 父线程新建子线程的时候,ThreadLocalMap是深拷贝,但是Entry中的value是浅拷贝,也就意味着子线程通过get()获取到的value修改之后,父线程再通过get()获取value也是修改过后的。
思考
目前已经很少出现通过自己手动new Thread启动线程了,一般都是通过ThreadPool实现。在使用线程池的情况下,因为线程池是复用线程,不会重复创建,而InheritableThreadLocal是在创建子线程的时候才会将父线程的值复制到子线程中,但是线程池不会重复创建,所以多次使用后,仍然记录的是第一次提交任务时的外部线程的值,会造成数据的错误。