@[toc]
ThreadLocal 简介
ThreadLocal
类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal
实例通常来说都是 private static 类型的,用于关联线程和线程上下文。
验证 ThreadLocal 隔离性
public class ThreadLocalDemo01 {
ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
ThreadLocalDemo01 threadLocalTest = new ThreadLocalDemo01();
threadLocalTest.showTwoThread();
}
public void showTwoThread() {
new Thread(new Runnable() {
public void run() {
testQuarantine();
}
},"t1").start();
new Thread(new Runnable() {
public void run() {
testQuarantine();
}
},"t2").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void testQuarantine() {
System.out.println(Thread.currentThread().getName() + " 线程未设置之前: " + threadLocal.get());
threadLocal.set(Thread.currentThread().getName());
try {
System.out.println("线程: " + Thread.currentThread().getName() + " 已放入到 threadLocal");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 线程设置之后: " + threadLocal.get());
}
}
**控制台打印结果如下: **
t1 线程未设置之前: null
线程: t1 已放入到 threadLocal
t2 线程未设置之前: null
线程: t2 已放入到 threadLocal
t1 线程设置之后: t1
t2 线程设置之后: t2
由此可以看到,我们在线程中使用了 ThreadLocal
做到了线程之间彼此隔离的作用。
ThreadLocal 如何做到线程隔离
早期的 JDK 版本 ThreadLocal 设计如下图:
每个
ThreadLocal
都创建一个Map
,然后用线程作为Map
的key
,要存储的局部变量作为Map
的value
,从而达到使各个线程的局部变量隔离的效果。
JDK 8 优化设计方案 如下图:
每个
Thread
维护一个ThreadLocalMap
,这个Map的key
是ThreadLocal
实例本身,value
才是真正要存储的值Object
。具体流程如下:
每个 Thread 线程内部都有一个 Map (
ThreadLocalMap
)Map 里面存储
ThreadLocal
对象(key)和线程的变量副本(value)Thread内部的Map是由
ThreadLocal
维护的,由ThreadLocal
负责向map获取和设置线程的变量值。对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
这样子的调整带来了什么好处
当
Thread
销毁之后,对应的ThreadLocalMap
也会随之销毁,能减少内存的使用。在实际运用当中, 往往
ThreadLocal
的数量要少于 Thread 的数量。这样设计之后每个Map
存储的Entry
数量就会变少。
ThreadLocal 使用场景
由于 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
共享变量
计算的中间结果共享给其他方法
@TODO 代码演示
ThreadLocal 源码解析
ThreadLocal 相关代码
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程下的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 后续调用 ThreadLocalMap的set方法插入数据
map.set(this, value);
else
// 第一次会创建 ThreadLocalMap
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private void set(ThreadLocal<?> key, Object value) {
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)]) {
// 拿到当前的ThreadLocal对象
ThreadLocal<?> k = e.get();
// 如果相同的ThreadLocal,直接更新即可
if (k == key) {
e.value = value;
return;
}
// 当前的key=null, 则表示虚引用的ThreadLocal是被GC回收的状态
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 走到这里说明下标 i 的位置上,是没有元素的,所以可以直接将新建的 Entry元素插入
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots: 存在陈旧的 Entry 且已经被清除
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap 相关代码
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
// ThreadLocal#ThreadLocalMap 的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 1. 初始化table, 默认数值为16
table = new Entry[INITIAL_CAPACITY];
// 2. 获取下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建 Entry 对象并插入到 对应下标 i的 table 中
table[i] = new Entry(firstKey, firstValue);
// 调整容量大小
size = 1;
// 调整负载系数
setThreshold(INITIAL_CAPACITY);
}
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#get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 得到当前 entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// 初始化(和Set流程一致,不懂老爷子为啥不再去调用一下set方法)
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果不为空且当前 key 相等
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 循环遍历当前entry
while (e != null) {
ThreadLocal<?> k = e.get();
// 找到即返回
if (k == key)
return e;
if (k == null)
// 清理陈旧的key
expungeStaleEntry(i);
else
// 遍历下一个
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
ThreadLocal#remove
public void remove() {\
// 拿到当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 获取当前key的所属下标
int i = key.threadLocalHashCode & (len-1);
// 循环找出当前的key
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)])
{
// 若找到引用置为空并清理陈旧的entry
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
开放地址法
根据以上hash函数计算数组下标时,当遇到数据存放的冲突时就需要重新找到数组的其他位置。关于开放地址法通常有三种方法: 线性探测、二次探测、再哈希法。
具体的区别这里先不展开, 只是先阐述一下开放地址与链地址法的区别 :
1.开放地址法:容易产生堆积问题;不适于大规模的数据存储;散列函数的设计对冲突会有很大的影响;插入时可能会出现多次冲突的现象,删除的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂;结点规模很大时会浪费很多空间;
2.链地址法:处理冲突简单,且无堆积现象,平均查找长度短;链表中的结点是动态申请的,适合构造表不能确定长度的情况;相对而言,拉链法的指针域可以忽略不计,因此较开放地址法更加节省空间。插入结点应该在链首,删除结点比较方便,只需调整指针而不需要对其他冲突元素作调整。
ThreadLocalMap
采用开放地址法原因是什么?
ThreadLocal
往往存放的数据量不会特别大(而且key 是弱引用又会被垃圾回收,及时让数据量更小)。采用开放地址法简单的结构会更节省空间,同时数组的查询效率也是非常高,加上第一点的保障,冲突概率也比较低。
探测式清理
ThreadLocal#expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
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;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
启发式清理
ThreadLocal#cleanSomeSlots
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
// 探测式清理
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
内存泄漏分析
创建与 ThreadLocalMap 一样的结构测试代码演示
public class ThreadLocalDemo02 {
public static void main(String[] args) {
Student student = new Student(new Person("Rocky"));
Person person = new Person("Rocky-BCRJ"); // 强引用
Student s1 = new Student(person);
System.gc();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("gc之后: " + student.get());
System.out.println("gc之后是否为null: " + (student.get() == null));
System.out.println("gc之后: " + student.get());
System.out.println("===============测试强引用是否被gc========================");
System.out.println("s1 gc之后: " + s1.get());
System.out.println("s1 gc之后是否为null: " + (s1.get() == null));
System.out.println("s1 gc之后: " + s1.get());
}
}
class Person {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalize: " + this);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends WeakReference<Person> {
public Student(Person referent) {
super(referent);
}
}
finalize: Person{name='Rocky'}
gc之后: null
gc之后是否为null: true
gc之后: null
===============测试强引用是否被gc========================
s1 gc之后: Person{name='Rocky-BCRJ'}
s1 gc之后是否为null: false
s1 gc之后: Person{name='Rocky-BCRJ'}
内存泄漏代码演示
public class ThreadLocalForOOM {
/**
* -Xms50m -Xmx50m
*/
static class OOMObject {
private Long[] a = new Long[2 * 1024 * 1024];
}
final static ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
final static ThreadLocal<OOMObject> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
int finalI = i;
pool.execute(() -> {
threadLocal.set(new OOMObject());
System.out.println("oom object--->" + finalI);
OOMObject oomObject = threadLocal.get();
System.out.println("oomObject---->" + oomObject);
// threadLocal.remove(); // 记得remove 防止内存泄露,此时一定要在使用完remove
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们运行上述代码,会出现 OOM 异常:Exception in thread "pool-1-thread-4" java.lang.OutOfMemoryError: Java heap space
。在使用 线程池 + threadLocal 之后,需要及时的进行 remove 删除操作避免内存泄漏问题。
如何给子线程传递父线程数据
前面也说了, ThreadLocal
具有线程隔离的特性, 再来温习一下叭!
public class ThreadLocalDemo03 {
static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
threadLocal.set(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + " 线程获取到: " + threadLocal.get());
Thread t1 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 线程获取到: " + threadLocal.get());
},"t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果输出
main 线程获取到: main
t1 线程获取到: null
方法1 InheritableThreadLocal
public class ThreadLocalDemo04 {
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
public static void main(String[] args) {
threadLocal.set(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + " 线程获取到: " + threadLocal.get());
Thread t1 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 线程获取到: " + threadLocal.get());
},"t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果输出
main 线程获取到: main
t1 线程获取到: main
需要注意
变量的传递是发生在线程创建的时候,如果不是新建线程,而是用了线程池里的线程,就不灵了
变量的赋值就是从主线程的map复制到子线程,它们的value是同一个对象,如果这个对象本身不是线程安全的,那么就会有线程安全问题
方法2 TransmittableThreadLocal
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
public class ThreadLocalDemo05 {
static ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
static ExecutorService executorService = Executors.newFixedThreadPool(1);
static ExecutorService executorService2 = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
public static void main(String[] args) {
// 验证TTL 传递性
threadLocal.set(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + " 线程获取到: " + threadLocal.get());
Thread t1 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " 线程获取到: " + threadLocal.get());
},"t1");
t1.start();
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
// 验证线程池传递
for (int i = 0; i < 3; i++) {
inheritableThreadLocal.set("inheritable -value-> " + i);
transmittableThreadLocal.set("transmittable -value-> " + i);
executorService2.submit(() -> {
System.out.println(transmittableThreadLocal.get());
});
executorService.submit(()->{
System.out.println(inheritableThreadLocal.get());
});
}
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
executorService.shutdown();
executorService2.shutdown();
inheritableThreadLocal.remove();
transmittableThreadLocal.remove();
}
}
}
结果演示
main 线程获取到: main
t1 线程获取到: main
transmittable -value-> 0
transmittable -value-> 1
transmittable -value-> 2
inheritable -value-> 0
inheritable -value-> 0
inheritable -value-> 0
参考博客
参考1: https://xiaohuang.blog.csdn.net/article/details/129979690
参考2: https://www.jianshu.com/p/3bb70ae81828
参考3: https://blog.csdn.net/u010445301/article/details/111322569
参考4: https://blog.csdn.net/wxy941011/article/details/80740716
参考5: https://blog.csdn.net/A_art_xiang/article/details/125889527
参考6: https://baijiahao.baidu.com/s?id=1736593241526122157&wfr=spider&for=pc
文末
相信大家在看完这篇文章之后 对于 ThreadLocal
有了比较清晰的地方, 如果还有不懂的地方可以评论留言, 我们可以一起探讨研究一下。同时文章中还有许多地方写得不好不够详细,还请多多包涵, 也希望你发现错误问题的同时能及时像博主反馈以免误人子弟, 有些不够详细的地方我也会在后续的文档中慢慢完善, 最后也想说的是, 我会努力提升自己,写出更优质的文章出来。谢谢大家。