ThreadLocal的设计思想
既然多线程访问同一个变量会造成线程安全的问题,那么创建出来一个变量,需要使用这个变量的线程将该变量拷贝一份,并且拷贝到每一个线程的变量是线程私有的,使得变量在线程之间隔离起来使用,避免了线程之间的交错使用数据造成的线程安全问题。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,它中间有一个内部类 Entry。
// 将Entry的key设置为弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
// 构造方法,ThreadLocal作为键,value作为值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap将Entry设置为弱引用,是为了防止内存泄露。
将key设置为弱引用,在下一次GC的时候,将弱引用指向的对象回收。在多线程中,假设多个线程使用同一个ThreadLocal类型的变量,也就是每个线程的ThreadLocalMap的其中一个Entry中的key使用的是同一个ThreadLocal类型的变量。
举例
三个线程中的每个线程的 ThreadLocalMap 的其中一个 Entry 中的 key 使用的是同一个 ThreadLocal 类型变量的地址。都指向了 ThreadLocal1 。
此时假设是强引用:多个线程依赖同一个 ThreadLocal1 ,此时 线程 1 的 ThreadLocal1 使用结束了想要释放内存,但是由于是强引用(因为还有其他线程还在指向 ThreadLocal1),这就导致了线程1 的持有 ThreadLocal1 的 Entry 占有的内存无法释放,导致了内存泄露,使用弱引用的时候,这种问题就可以解决。
ThreadLocal中还设计了Entry数组来存放多个Entry。
private Entry[] table;
使用ThreadLocal
下面是一段演示代码,main函数作为线程,对ThreadLocal进行操作。
package com.school.service;
public class Main {
public static void main(String[] args) {
ThreadLocal t = new ThreadLocal();
t.set("jqz");
t.set("zy");
String s = (String)t.get();
System.out.println(s);
}
}
set方法
点进set方法,进去为下方代码,为其加上注释。
// set方法,参数为要插入的值
public void set(T value) {
// 获取对当前执行线程对象的引用。
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
点进getMap(t)这个方法,代码如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap方法参数为线程对象,可以看见返回值为ThreadLocal,点进去t.threadLocals;
可以看到
ThreadLocal.ThreadLocalMap threadLocals = null;
这是在Thread类里的,Thread类中内置了ThreadLocalMap这个类型的参数,可以发现每个线程都是有自己独立的ThreadLocalMap对象。
ThreadLocal通过getMap方法获取线程对象的ThreadLocal。
继续向下看set方法。
// set方法,参数为要插入的值
public void set(T value) {
// 获取对当前执行线程对象的引用
Thread t = Thread.currentThread();
// 获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果map里面已经插入过值了
if (map != null)
// 对map中再set进去当前传入的值,它的键为这个ThreadLocal对象
map.set(this, value);
// 如果map里面没有插入过值
else
// 创建新的ThreadLocalMap对象,插入值进去,它的键为这个ThreadLocal对象
createMap(t, value);
}
get方法
点进get方法,进去为下方代码,为其加上注释。
// get方法,返回当前线程,在该ThreadLocal中的值
public T get() {
// 获取对当前执行线程对象的引用
Thread t = Thread.currentThread();
// 获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果ThreadLocalMap存在
if (map != null) {
// 通过ThreadLocal作为键,获取ThreadLocalMap的Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果Entry不为空
if (e != null) {
// 获取结果
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
// 将value设为空值
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 将控制设置进ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
// 放回空值
return value;
}
场景
如果有两个线程使用相同的ThreadLocal,访问值会相同吗?
如果相同的线程访问两个不同的ThreadLocal,访问值会相同吗?
答
只有相同对象访问相同的ThreadLocal,访问值才会相同。