导图
一、基本概念
ThreadLocal为线程变量,也常说线程局部变量;提供了线程内存储变量的能力,这些变量在每一个线程之间存取都是相互独立的。
官方注释
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
* /
ThreadLocal的意义
在多线程环境下,如果要访问和操作同一个共享变量势必要考虑线程安全的问题,就要创建同步方法,加锁等等方式;可能会因为一个简单的需求增加系统的开销提高了软件的复杂程度,此时ThreadLocal可以比较好的方式处理多线程下共享变量操作的问题。
二、ThreadLocal的使用
以一个在不同线程中获取代码运行耗时的例子,如果不使用ThreadLocal 那么我们需要定义好一个long变量,然后在方法开始时获取当前的时间戳;在代码结束时用当前时间戳减去开始时的时间戳,如若要统计不同的代码或者方法,重复代码就比较多了;
使用ThreadLocal的实现:
public class ThreadLocalTest {
private ThreadLocal<Long> threadLocal;
public void start() {
threadLocal = new ThreadLocal<>();
new Thread(new TestRunnable1()).start();
new Thread(new TestRunnable2()).start();
}
private void begin() {
threadLocal.set(System.currentTimeMillis());
}
private void end() {
System.out.println(Thread.currentThread().getName() + " " + (System.currentTimeMillis() - threadLocal.get()) + "ms");
}
private class TestRunnable1 implements Runnable {
@Override
public void run() {
begin();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
end();
}
}
private class TestRunnable2 implements Runnable {
@Override
public void run() {
begin();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
end();
}
}
}
public class ThreadLocalMain {
public static void main(String[] args) {
new ThreadLocalTest().start();
}
}
输出:
Thread-0 3001ms
Thread-1 4001ms
void set(T value)
设置当前线程的线程局部变量的值
T get()
返回当前线程所对应的线程局部变量
void remove()
将当前线程局部变量的值删除
initialValue
为ThreadLocal赋初值;如果set方法没有被调用,ThreadLocalMap未被创建,此时使用threadLocal.get()方法得到的值为初始值,即initialValue返回的数值,使用方式如下:
private ThreadLocal<Long> threadLocal = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return 0L;
}
};
三、实现原理
首先大致介绍一下ThreadLocal的具体结构~
每一个线程Tread会持有一个ThreadLocalMap,ThreadLocalMap在ThreadLocal中定义;该ThreadLocalMap可以通过threadLocal.getMap(Thread t)拿到;ThreadLocalMap本身维护了一个Entry数组,存储键值对,键为ThreadLocal对象,Value为我们需要设置和获取的值;由于一个Thread只有一个ThreadLocalMap,因此当一个Thread中使用多个ThreadLocal时,不同的ThreadLocal会通过hash&&length-1的方式插入到Entry中。
1、set方法
set方法作用为,拿到当前线程的ThreadLocalMap,如果没有则创建;拿到ThreadLocal对应的下标位置,插入Value;
public void set(T value) {
//获取当前的线程,这里为调用set方法所在线程
Thread t = Thread.currentThread();
//通过当前线程对象拿到ThreadLocalMap
//==>分析一
ThreadLocalMap map = getMap(t);
if (map != null)
//如果map不为空,将Value设置到map中
//==> 分析二
map.set(this, value);
else
//首次调用set函数,创建ThreadLocalMap
//==> 分析三
createMap(t, value);
}
==>分析一
ThreadLocalMap getMap(Thread t) {
//拿到Thread中的threadLocals,默认为空
return t.threadLocals;
}
Thread类中
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
==>分析二
ThreadLocalMap中的set方法,ThreadLocalMap存在于ThreadLocal中
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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;
//key 为当前ThreadLocal自身,这里做hash运算并且&len-1得到当前ThreadLocal对象对应
//在ThreadLocalMap(Entry数组)中的位置
int i = key.threadLocalHashCode & (len-1);
//遍历Entry数组,如果当前位置存在,则更新value
//如果当前位置不存在,则插入
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//没有遍历成功,创建新值
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
==>分析三
创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//构建Entry数组,默认初始大小为16
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
set 方法流程图
2、get方法
get方法主要作用为获取到当前线程持有的ThreadLocalHashMap;再根据当前的ThreadLocal对象从Entry数组中拿到指定的Value。源码分析如下:
public T get() {
//先通过当前线程对象,拿到对应的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果map不为空,则根据当前ThreadLocal对象,拿到Entry对象
//分析一
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//设置初始值,详见第4点
return setInitialValue();
}
// 分析一
private Entry getEntry(ThreadLocal<?> key) {
//找到key对应Entry数组中的下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
3、remove方法
remove方法的作用为移除当前Entry数组中的元素
public void remove() {
//拿到当前线程所对应的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//移除当前ThreadLocal对应的Entry
//分析一
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
//拿到当前的Entry数组
Entry[] tab = table;
int len = tab.length;
//拿到ThreadLocal对应的数组下标
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;
}
}
}
4、initialValue
初始化方法
//如果需要设置初始值,该方法需要自己实现;
protected T initialValue() {
return null;
}
private T setInitialValue() {
//调用initialValue拿到初始值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//向ThreadLocalMap中设置初始值
map.set(this, value);
else
createMap(t, value);
return value;
}
四、ThreadLocal的隐患以及InheritableThreadLocal
ThreadLocal的虽然功能强大,但存在过度设计导致一些隐患的发生;
1、内存泄漏
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap的Entry数组的Key是弱引用,在GC时会被回收掉;但Entry的值Value是强引用,所以就会导致出现Key为nul的Entry数据出现。
2、脏数据
造成脏数据的主要原因在于,使用者调用set函数设置值后,没有显示调用remove方法释放数据,当使用线程池复用Thread对象时,会造成Thread对象中的threadLocals也会被复用。导致Thread对象在执行其他任务时通过get()方法获取到的是之前Thread任务设置的数据,产生脏数据。
1、2的解决:
在任务结束后显示调用remove方法,将当前任务中的数据清除。
3、子线程如何访问父线程中的局部变量?
ThreadLocal用于同一个线程内,只能在当前类中使用;但在实际开发中,很多会在线程中继续创建子线程,那么这时候子线程就获取不到ThreadLocal数据。
InheritableThreadLocal
简单使用
public class Test {
private InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal();
public void run(){
new Thread(new FuRunnable()).start();
}
private class FuRunnable implements Runnable{
@Override
public void run() {
//父线程中通过inheritableThreadLocal设置值,是可以在ZiRunnable中拿到的
inheritableThreadLocal.set("666");
new Thread(new ZiRunnable()).start();
}
}
private class ZiRunnable implements Runnable{
@Override
public void run() {
System.out.println(inheritableThreadLocal.get());
}
}
}
输出:666
InheritableThreadLocal继承自ThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @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);
}
}
讲ThreadLocal的时候说过,每个Thread当中会维护一个ThreadLocalMap;其实每个Thread除了维护一个ThreadLocalMap之外,还有一个inheritableThreadLocals,这个对象本身也是一个ThreadLocalMap,但只有在线程之间传值当中使用。
Thread创建后,会调用到init函数
Thread parent = currentThread();
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
parent代表当前调用创建线程方法所在的线程,即父线程;当父线程的inheritableThreadLocals不为空时,会将其inheritableThreadLocals通过createInheritedMap传递给子线程;
//parentMap是父线程传过来的
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//遍历父线程ThreadLocalMap中的Entry,将其中的元素全部赋给新的Entry数组table
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) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
此时ThreadLocal的table数组已经被赋值为当前线程的父线程中Entry中的数据;再发生get方法时,getMap就是inheritableThreadLocals,其对应的Entry数据就是父线程传递过来的数据。