ThreadLocal深度理解
# ThreadLocal深度理解
# ThreadLocal简介
通常情况下,我们创建的变量可以被任何一个线程访问并修改的。如果想实现每一个线程都自己的专属本地变量可以使用 ThreadLocal类,ThreadLocal 类主要解决的是让每个线程绑定自己的值。
如果创建了一个 ThreadLocal 变量,那么访问这个变量的线程都会有这个变量的本地副本。他们可以使用get()
和set()
方法来获取默认值或将其值更改为当前线程所存放的副本的值,避免线程安全问题。
⭐ ThreadLocal示例
创建一个存储 String 类型的 ThreadLocal 对象,并在 A 个线程中分别为对象赋值。依次在 A 线程和 B 线程和主线程中获取 ThreadLocal 对象的值。
public class ThreadLocalTest {
public static ThreadLocal<String> holder = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
/* 创建A线程并启动 */
new Thread(() -> {
holder.set("A"); // B线程设置holder值
System.out.println("线程" + Thread.currentThread().getName() + ": " + holder.get()); // A线程获取holder值
}, "A").start();
/* 创建B线程并启动 */
new Thread(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("线程" + Thread.currentThread().getName() + ": " + holder.get()); // B线程获取holder值
}, "B").start();
Thread.sleep(300);
System.out.println(Thread.currentThread().getName() + ": " + holder.get()); // main线程获取holder值
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
⭐ 控制台输出
线程A: A
线程B: null
main: null
2
3
对于 A 线程,holder的值为 A;对于 B 线程和 main 线程,holder 的值为空,因为我们从未对其赋值。
# ThreadLocal数据结构
Thread
类有一个类型为 ThreadLocal.ThreadLocalMap
的实例变量 threadLocals
,也就是说每个线程有一个自己的ThreadLocalMap
。
每个线程在往ThreadLocal
里放值的时候,都会往自己的ThreadLocalMap
里存,读也是以ThreadLocal
作为引用,在自己的map
里找对应的key
,从而实现了线程隔离。
ThreadLocalMap
有点类似HashMap
的结构,只是HashMap
是由数组+链表实现的,而ThreadLocalMap
中并没有链表结构。
我们还要注意Entry
, 它的key
是ThreadLocal<?> k
,继承自WeakReference
, 也就是我们常说的弱引用类型。
# ThreadLocal中的弱引用
在看这部分之前,一定要先了解 Java 的强、软、弱、虚四大引用。
ThreadLocal 的弱引用体现在 ThreadLocalMap 中的 Entry 上。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
2
3
4
5
6
7
8
9
对于这个 Entry,它的键是弱引用,引用一个 ThreadLocal 对象;值是强引用,可以引用任意对象。
对于每一个线程都有属于自己的 ThreadLocalMap,从下面这段源码可以看出
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
2
3
4
5
对于简介中的示例代码,他们的引用关系应该是这样:
ThreadLocal对象引用
与ThreadLocal对象
之间引用是「强引用」ThreadLocalMap.Entry中的key
与ThreadLocal对象
之间的引用是「弱引用」ThreadLocalMap.Entry中的value
与引用的对象
之间的引用是「强引用」
从 JVM 内存结构上看它们的更为具体的引用关系,首先我们要知道:
- 堆是线程共享的,栈是线程私有的
- 实例对象存储在堆中,静态变量的引用存储在方法区中,静态变量的实例对象存储在堆中。
简介示例代码中的 ThreadLocal 是类的静态变量,所以他的对象引用存储在方法区,而不是主线程的栈中。
public class ThreadLocalTest {
public static ThreadLocal<String> holder = new ThreadLocal<>();
...
}
2
3
4
如果我们在 main 方法中实例化 ThreadLocal 对象,那么其他线程是无法获取到这个对象的,因为这个对象只存储在 main 线程的栈中。所以只有当 ThreadLocal 为静态变量时,其他的线程才能访问到。
当我们令 holder = null
,并且进行了垃圾回收后,ThreadLocal 对象将会被垃圾回收器回收。因为 Entry 的 key 是弱引用,所以不会影响垃圾回收器对 ThreadLocal 对象的回收,此时 key 的值为 null,但 value 的值不为 null,这将导致内存泄露问题。
# ThreadLocal内存泄露问题
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value永远无法被GC回收,这时候就产生了内存泄漏。
ThreadLocalMap实现已经考虑了这种情况,在调用 set、get、remove 方法的时候,会清理掉 key 为 null 的记录。所以在使用ThreadLocal 方法后,最好手动调用 remove 方法。
# InheritableThreadLocal
启动另外一个线程,那么在主线程设置的 threadlocal 值如何能被子线程拿到?
正常情况
public class ThreadLocalTest1 {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("test");
System.out.println("outer thread is: " + Thread.currentThread().getName() + ", value is: " + threadLocal.get());
new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("inner thread is: " + Thread.currentThread().getName() + ", value is: " + threadLocal.get());
}).start();
}
}
2
3
4
5
6
7
8
9
10
11
12
输出为:
outer thread is: main, value is: test inner thread is: Thread-0, value is: null
可以使用 InheritableThreadLocal,可继承的 ThreadLocal
public class ThreadLocalTest1 {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("test");
System.out.println("outer thread is: " + Thread.currentThread().getName() + ", value is: " + threadLocal.get());
new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("inner thread is: " + Thread.currentThread().getName() + ", value is: " + threadLocal.get());
}).start();
}
}
2
3
4
5
6
7
8
9
10
11
12
输出为:
outer thread is: main, value is: test inner thread is: Thread-0, value is: test
InheritableThreadLocal 的原理:
Thread 类里面有 threadLocals 和 inheritableThreadLocals 两个类变量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
2
在 Thread 构造函数中,有这样一段代码:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
2
3
也就是会去获得父 inheritableThreadLocals 的值,并赋值给自己。