关于java:面试官解释一下ThreadLocal-核心原理

7次阅读

共计 12239 个字符,预计需要花费 31 分钟才能阅读完成。

在并发编程中,除了能够应用锁机制来保障线程平安,JDK 中还提供了 ThreadLocal 类来保障多个线程可能平安访问共享变量。本章简略介绍 ThreadLocal 的外围原理。

本文波及的知识点如下。

—ThreadLocal 的基本概念。

—ThreadLocal 的应用案例。

—ThreadLocal 的外围原理。

—ThreadLocal 变量的继承性。

—InheritableThreadLocal 的应用案例。

—InheritableThreadLocal 的外围原理。

ThreadLocal 的基本概念

在并发编程中,多个线程同时拜访同一个共享变量,可能会呈现线程平安的问题。为了保障在多线程环境下访问共享变量的安全性,通常会在访问共享变量的时候加锁,以实现线程同步的成果。

应用同步锁机制保障多线程访问共享变量的安全性的原理如图 14-1 所示。该机制可能保障同一时刻只有一个线程访问共享变量,从而确保在多线程环境下访问共享变量的安全性。

另外,为了更加灵便地确保线程的安全性,JDK 中提供了一个 ThreadLocal 类,ThreadLocal 类可能反对本地变量。在应用 ThreadLocal 类访问共享变量时,会在每个线程的本地内存中都存储一份这个共享变量的正本。在多个线程同时对这个共享变量进行读写操作时,实际上操作的是本地内存中的变量正本,多个线程之间互不烦扰,从而防止了线程平安的问题。应用 ThreadLocal 访问共享变量的示意图如图 14-2 所示。

ThreadLocal 的应用案例

本节次要实现两个通过 ThreadLocal 操作线程本地变量的案例,以此加深读者对 ThreadLocal 的了解。

案例一的次要实现逻辑:在案例程序中别离创立名称为 Thread-A 和 Thread-B 的两个线程,在 Thread-A 线程和 Thread-B 线程的 run() 办法中通过 ThreadLocal 保留本地变量,随后打印 Thread-A 线程和 Thread-B 线程中保留的本地变量。最初,启动 Thread-A 线程和 Thread-B 线程。

案例一的外围代码如下。

/**
*@author binghe
*@version 1.0.0
*@description ThreadLocal 案例程序
*/
public class ThreadLocalTest {

private static final ThreadLocal<String> THREAD_LOCAL = new 
ThreadLocal<String>();

public static void main(String[] 
args){ Thread threadA = new 
Thread(()->{THREAD_LOCAL.set("ThreadA:" + Thread.currentThread().getName());
 System.out.println(Thread.currentThread().getName() + "本地变量中的值为:"
+ THREAD_LOCAL.get());
}, "Thread-A");

Thread threadB = new Thread(()->{THREAD_LOCAL.set("ThreadB:" + Thread.currentThread().getName());
 System.out.println(Thread.currentThread().getName() + "本地变量中的值为:"
+ THREAD_LOCAL.get());
}, "Thread-B");

threadA.start();
 threadB.start();}
}

运行上述代码,输入后果如下。

Thread-A 本地变量中的值为: ThreadA: Thread-A

Thread-B 本地变量中的值为: ThreadB: Thread-B

从输入后果能够看出,Thread-A 线程和 Thread-B 线程通过 ThreadLocal 保留了本地变量,并未正确打印出后果。

案例二的次要实现逻辑:在案例一的根底上为 Thread-B 线程减少删除 ThreadLocal 中保留的本地变量的操作,随后打印后果来证实删除 Thread-B 线程中的本地变量不会影响 Thread-A 线程中的本地变量。

案例二的外围代码如下。

/**
*@author binghe
*@version 1.0.0
* @description ThreadLocal 案例程序
*/
public class ThreadLocalTest {
private static final ThreadLocal<String> THREAD_LOCAL = new 
ThreadLocal<String>();

public static void main(String[] 
args){ Thread threadA = new 
Thread(()->{THREAD_LOCAL.set("ThreadA:" + Thread.currentThread().getName()); 
System.out.println(Thread.currentThread().getName() + "本地变量中的值为:"
+ THREAD_LOCAL.get());
System.out.println(Thread.currentThread().getName() + "未删除本地变量,本地变量中的值为:" + THREAD_LOCAL.get());
}, "Thread-A");

Thread threadB = new Thread(()->{THREAD_LOCAL.set("ThreadB:" + Thread.currentThread().getName()); 
System.out.println(Thread.currentThread().getName() + "本地变量中的值为:"
+ THREAD_LOCAL.get());
THREAD_LOCAL.remove();
System.out.println(Thread.currentThread().getName() + "删除本地变量后,本地变量中的值为:" + THREAD_LOCAL.get());
}, "Thread-B");

threadA.start(); 
threadB.start();

运行上述代码,输入后果如下。

Thread-A 本地变量中的值为: ThreadA: Thread-A

Thread-A 未删除本地变量,本地变量中的值为: ThreadA: Thread-A

Thread-B 本地变量中的值为: ThreadB: Thread-B

Thread-B 删除本地变量后,本地变量中的值为: null

论断:Thread-A 线程和 Thread-B 线程存储在 ThreadLocal 中的变量互不烦扰,Thread- A 线程中存储的本地变量只能由 Thread-A 线程拜访,Thread-B 线程中存储的本地变量只能由 Thread-B 线程拜访。

从输入后果能够看出,删除 Thread-B 线程中的本地变量后,Thread-B 线程中保留的本地变量的值为 null。同时,删除 Thread-B 线程中的本地变量后,不会影响 Thread-A 线程中保留的本地变量。

ThreadLocal 的外围原理

ThreadLocal 可能保障每个线程操作的都是本地内存中的变量正本。在底层实现上,调用 ThreadLocal 的 set() 办法会将本地变量保留在具体线程的内存空间中,而 ThreadLocal 并不负责存储具体的数据。

Thread 类源码

在 Thread 类的源码中,定义了两个 ThreadLocal.ThreadLocalMap 类型的成员变量,别离为 threadLocals 和 inheritableThreadLocals,源码如下。

public class Thread implements Runnable {
/*********** 省 略 N 行 代 码 *************/ 
ThreadLocal.ThreadLocalMap threadLocals = null; 
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/*********** 省略 N 行代码 *************/
}

在 Thread 类中定义成员变量 threadLocals 和 inheritableThreadLocals,二者的初始值都为 null,并且只有当线程第一次调用 ThreadLocal 或者 InheritableThreadLocal 的 set() 办法或者 get() 办法时才会实例化变量。

上述代码也阐明,通过 ThreadLocal 为每个线程保留的本地变量不是存储在 ThreadLocal 实例中的,而是存储在调用线程的 threadLocals 变量中的。也就是说,调用 ThreadLocal 的 set() 办法存储的本地变量在具体线程的内存空间中,而 ThreadLocal 类只是提供了 set() 和 get() 办法来存储和读取本地变量的值,当调用 ThreadLocal 类的 set() 办法时,把要存储的值存储在调用线程的 threadLocals 变量中,当调用 ThreadLocal 类的 get() 办法时,从以后线程的 threadLocals 变量中获取保留的值。

set() 办法

ThreadLocal 类中 set() 办法的源码如下。

public void set(T value) {
// 获取以后线程
Thread t = Thread.currentThread();
// 以以后线程为 key,获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 获取的 ThreadLocalMap 对象不为空
if (map != null)
// 设置 value 的值
map.set(this, value); else
// 获取的 ThreadLocalMap 对象为空,实例化 Thread 类中的 threadLocals 变量
createMap(t, value);
}

从 ThreadLocal 类中的 set() 办法的源码能够看出,在 set() 办法中,会先获取调用 set() 办法的线程,而后应用以后线程对象作为 key 调用 getMap(t) 办法获取 ThreadLocalMap 对象,其中,getMap(Thread t) 办法的源码如下。

ThreadLocalMap getMap(Thread t) 
{return t.threadLocals;}

通过 getMap(Thread t) 办法的源码能够看出,调用 getMap(Thread t) 办法获取的就是以后线程中定义的 threadLocals 成员变量。

再次回到 ThreadLocal 的 set() 办法中,调用 getMap(Thread t) 办法并将后果赋值给 ThreadLocalMap 类型的变量 map,判断 map 的值是否为空,也就是判断调用 getMap(Thread t) 办法返回的以后线程的 threadLocals 成员变量是否为空。

如果以后线程的 threadLocals 成员变量不为空,则把 value 设置到 Thread 类的 threadLocals 成员变量中。此时,保留数据时传递的 key 为以后 ThreadLocal 的 this 对象,而传递的 value 为调用 set() 办法传递的值。

如果以后线程的 threadLocals 成员变量为空,则调用 createMap(t, value) 办法来实例化以后线程的 threadLocals 成员变量,并保留 value 值。createMap(t, value) 源码如下。

void createMap(Thread t, T firstvalue) {t.threadLocals = new ThreadLocalMap(this, firstvalue);
}

至此,ThreadLocal 类中的 set() 办法剖析结束。

get() 办法

ThreadLocal 类中 get() 办法的源码如下。

public T get() {
// 获取以后线程
Thread t = Thread.currentThread();
// 获取以后线程的 threadLocals 成员变量
ThreadLocalMap map = getMap(t);
// 获取的 threadLocals 成员变量不为空
if (map != null) {
// 返回本地变量对应的值
ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) {@SuppressWarnings("unchecked") T result = (T)e.value;
return result;
}
}
// 初始化 threadLocals 成员变量的值
return setInitialvalue();}

通过 ThreadLocal 类中 get() 办法的源码能够看出,get() 办法会通过调用 getMap(Thread t) 办法并传入以后线程来获取 threadLocals 成员变量,随后判断以后线程的 threadLocals 成员变量是否为空。

如果 threadLocals 成员变量不为空,则间接返回以后线程 threadLocals 成员变量中存储的本地变量的值。

如果 threadLocals 成员变量为空,则调用 setInitialvalue() 办法来初始化 threadLocals 成员变量的值。

setInitialvalue() 办法的源码如下。

private T setInitialvalue() {
// 调用初始化 value 的办法
T value = initialvalue();
Thread t = Thread.currentThread();
// 以以后线程为 key 获取 threadLocals 成员变量
ThreadLocalMap map = getMap(t); if (map != null)
//threadLocals 不为空,则设置 value 值
map.set(this, value); else
//threadLocals 为空, 则实例化 threadLocals 成员变量
createMap(t, value); return value;
}

setInitialvalue() 办法与 set() 办法的主体逻辑大致相同,只不过 setInitialvalue() 办法会先调用 initialvalue() 办法来初始化 value 的值,同时,在办法的最初会返回 value 的值。

initialvalue() 办法的源码如下。

protected T initialvalue() 
{return null;}

能够看到,ThreadLocal 类的 initialvalue() 办法会间接返回 null,办法的具体逻辑会交由 ThreadLocal 类的子类实现。

至此,ThreadLocal 类中的 get() 办法剖析结束。

remove() 办法

ThreadLocal 类中 remove() 办法的源码如下。

public void remove() {// 调用 getMap() 办法并传入以后线程对象获取 threadLocals 成员变量
ThreadLocalMap m = getMap(Thread.currentThread()); 
if (m != null)
//threadLocals 成员变量不为空,则移除 value 值
m.remove(this);
}

remove() 办法的实现比较简单,依据调用的 getMap() 办法获取以后线程的 threadLocals 成员变量,如果以后线程的 threadLocals 成员变量不为空,则间接从以后线程的 threadLocals 成员变量中移除以后 ThreadLocal 对象对应的 value 值。

留神: 如果调用线程始终不退出,本地变量就会始终存储在调用线程的 threadLocals 成员变量中,所以,如果不再须要应用本地变量,那么能够通过调用 ThreadLocal 的 remove()

办法,将本地变量从以后线程的 threadLocals 成员变量中删除,以避免出现内存溢出的问题。

至此,ThreadLocal 类中的 remove() 办法剖析结束。

ThreadLocal 变量的不继承性

在应用 ThreadLocal 存储本地变量时,主线程与子线程之间不具备继承性。在主线程中应用 ThreadLocal 对象保留本地变量后,无奈通过同一个 ThreadLocal 对象获取到在主线程中保留的值。

例如,上面的代码在主线程中应用 ThreadLocal 对象保留了本地变量的值,然而在子线程中应用同一个 ThreadLocal 对象获取到的值为空。

/**
*@author binghe
*@version 1.0.0
*@description 测试 ThreadLocal 的继承性
*/
public class ThreadLocalInheritTest {

private static final ThreadLocal<String> THREAD_LOCAL = new 
ThreadLocal<String>();

public static void main(String[] args){
// 在主线程中通过 THREAD_LOCAL 保留值
THREAD_LOCAL.set("binghe");

// 在子线程中通过 THREAD_LOCAL 获取在主线程中保留的值
new Thread(()->{
System.out.println("在子线程中获取到的本地变量的值为:" + 
THREAD_LOCAL.get());
} ).start();

// 在主线程中通过 THREAD_LOCAL 获取在主线程中保留的值
System.out.println("在主线程中获取到的本地变量的值为:" + THREAD_LOCAL.get());
}
}

首先,在 ThreadLocalInheritTest 类中定义了一个 ThreadLocal 类型的常量 THREAD_LOCAL。而后在 main() 办法中,应用 THREAD_LOCAL 保留了一个字符串类型的本地变量,值为 binghe。接下来,在子线程中打印通过 THREAD_LOCAL 获取到的本地变量的值,最初,在 main() 办法中通过 THREAD_LOCAL 获取本地变量的值。

运行 ThreadLocalInheritTest 类的代码,输入的后果如下。

在主线程中获取到的本地变量的值为: binghe

在子线程中获取到的本地变量的值为: null

通过输入后果能够看出,在主线程中通过 ThreadLocal 对象保留值后,在子线程中通过雷同的 ThreadLocal 对象是无奈获取到这个值的。

如果须要在子线程中获取到在主线程中保留的值,则能够应用 InheritableThreadLocal 对象。

InheritableThreadLocal 的应用案例

InheritableThreadLocal 类在结构上继承自 ThreadLocal 类。所以,InheritableThreadLocal 类的应用形式与 ThreadLocal 雷同。这里,能够将 14.4 节代码中的 ThreadLocal 批改为 InheritableThreadLocal,批改后的代码如下。

/**
*@author binghe
*@version 1.0.0
*@description 测试 InheritableThreadLocal 的继承性
*/
public class InheritableThreadLocalTest {// 将创立的 ThreadLocal 对象批改为 InheritableThreadLocal 对象 private static final ThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal<String>();

public static void main(String[] args){
// 在主线程中通过 THREAD_LOCAL 保留值
THREAD_LOCAL.set("binghe");

// 在子线程中通过 THREAD_LOCAL 获取在主线程中保留的值
new Thread(()->{System.out.println("在子线程中获取到的本地变量的值为:" + THREAD_LOCAL.get());
} ).start();

// 在主线程中通过 THREAD_LOCAL 获取在主线程中保留的值
System.out.println("在主线程中获取到的本地变量的值为:" + THREAD_LOCAL.get());
}
}

能够看到,这里在 14.4 节的案例根底上,仅仅批改了 THREAD_LOCAL 常量实例化后的对象类型,由原来的 ThreadLocal 类型批改为 InheritableThreadLocal 类型。

运行 InheritableThreadLocalTest 类的源码,输入后果如下。

在主线程中获取到的本地变量的值为: binghe

通过输入后果能够看出,在主线程中通过 InheritableThreadLocal 对象保留值后,在子线程中通过雷同的 InheritableThreadLocal 对象能够获取到这个值。阐明 InheritableThreadLocal 类的对象保留的变量具备继承性。

InheritableThreadLocal 的外围原理

InheritableThreadLocal 类继承自 ThreadLocal 类,InheritableThreadLocal 类的源码如下。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childvalue(T parentvalue)
{return parentvalue;}

ThreadLocalMap getMap(Thread t) 
{ return 
t.inheritableThreadLocals;
}

void createMap(Thread t, T firstvalue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstvalue);
}

在 InheritableThreadLocal 类中重写了 ThreadLocal 类中的 childvalue() 办法、getMap() 办法和 createMap() 办法。应用 InheritableThreadLocal 保留变量,当调用 ThreadLocal 的 set() 办法时,创立的是以后线程的 inheritableThreadLocals 成员变量,而不是以后线程的 threadLocals 成员变量。

通过剖析 Thread 类的源码可知,InheritableThreadLocal 类的 childvalue() 办法是在 Thread 类的构造方法中调用的。通过查看 Thread 类的源码可知,Thread 类的构造方法如下。

public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}

Thread(Runnable target, AccessControlContext acc) {init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

public Thread(ThreadGroup group, Runnable target) {init(group, target, "Thread-" + nextThreadNum(), 0);
}
init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {init(group, null, name, 0);
}

public Thread(Runnable target, String name) {init(null, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {init(group, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {init(group, target, name, stackSize);
}

能够看到,在 Thread 类的每个构造方法中,都会调用 init() 办法,init() 办法的局部代码如下。

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
/************ 省略局部源码 ************/
if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

this.stackSize = stackSize; tid = nextThreadID();}

能够看到,在 init() 办法中,会判断传递的 inheritThreadLocals 变量是否为 true,同时会判断父线程中的 inheritableThreadLocals 成员变量是否为 null。如果传递的 inheritThreadLocals 变量为 true,同时父线程中的 inheritableThreadLocals 成员变量不为 null,则调用 ThreadLocal 类的 createInheritedMap() 办法来创立 ThreadLocalMap 对象。

ThreadLocal 类的 createInheritedMap() 办法的源码如下。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) 
{return new ThreadLocalMap(parentMap);
}

在 ThreadLocal 类的 createInheritedMap() 办法中,会应用父线程的 inheritableThreadLocals 成员变量作为入参调用 ThreadLocalMap 类的构造方法来创立新的 ThreadLocalMap 对象。并在 Thread 类的 init() 方 法中将创 建的 ThreadLocalMap 对象赋值 给以后线程 的 inheritableThreadLocals 成员变量,也就是赋值给子线程的 inheritableThreadLocals 成员变量。

接下来,剖析 ThreadLocalMap 类的构造方法,源码如下。

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++;
}
}
}
}

ThreadLocalMap 类的构造方法是公有的,在 ThreadLocalMap 类的构造方法中,有如下一行代码。

Object value = key.childvalue(e.value);

这行代码调用了 InheritableThreadLocal 类重写的 childvalue() 办法,也就是说,在 ThreadLocalMap 类的构造方法中调用了 InheritableThreadLocal 类重写的 childvalue() 办法。另外,在 InheritableThreadLocal 类中重写了 getMap() 办法来 确保获取 的是线程 的 inheritableThreadLocals 成员变量,同时,重写了 createMap() 办法来确保创立的是线程的 inheritableThreadLocals 成员变量的对象。

而线程在通过 InheritableThreadLocal 类的 set() 办法和 get() 办法保留和获取本地变量时,会通过 InheritableThreadLocal 类重写的 createMap() 办法来创立以后线程的 inheritableThreadLocals 成员变量的对象。

如果在某个线程中创立子线程,就会在 Thread 类的构造方法中把父线程的 inheritableThreadLocals 成员 变量 中保 存的 本地 变量 复制 一份 保留 到子 线程的 inheritableThreadLocals 成员变量中。

总结

本文次要对 ThreadLocal 的外围原理进行了简略的介绍。首先,介绍了 ThreadLocal 的基本概念并简略介绍了 ThreadLocal 的应用案例,而后,联合 ThreadLocal 的源码介绍了 ThreadLocal 的外围原理和 ThreadLocal 变量的不继承性。接下来,介绍了 InheritableThreadLocal 的应用案例,并阐明了 InheritableThreadLocal 变量的继承性。最初,联合 InheritableThreadLocal 和 Thread 的源码,详细分析了 InheritableThreadLocal 的外围原理。

正文完
 0