ThreadLocal
1.定义
线程本地的变量副本,属于每个线程独有。每个线程使用ThreadLocal设置自己的值,设置的值之间不受影响,但是使用同一个ThreadLocal对象。所以设置的每个变量,是给每个线程一个独有的变量副本
2.简单使用
public class HelloThreadLocal {
private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>();
public static void main(String[] args) {
//线程1 使用threadLocal设置自己的变量副本
new Thread(() -> {
threadLocal.set(new Loan("zhangsan", "1000.00"));
System.out.println("线程-1loan:" + threadLocal.get());
}).start();
//线程2 使用threadLocal设置自己的变量副本
new Thread(() -> {
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
}
HelloThreadLocal.Loan loan = threadLocal.get();
System.out.println("线程-2loan:" + loan);
threadLocal.set(new Loan("lisi", "2000.00"));
loan = threadLocal.get();
System.out.println("线程-2loan:" + loan);
}).start();
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
System.out.println("main-线程loan:" + threadLocal.get());
threadLocal.set(new Loan("wangwu", "1000.00"));
System.out.println("main-线程loan:" + threadLocal.get());
}
@Data
@AllArgsConstructor
public static class Loan {
private String name;
private String amount;
}
}
输出结果:
线程-1loan:HelloThreadLocal.Loan(name=zhangsan, amount=1000.00)
线程-2loan:null
线程-2loan:HelloThreadLocal.Loan(name=lisi, amount=2000.00)
main-线程loan:null
main-线程loan:HelloThreadLocal.Loan(name=wangwu, amount=1000.00)
可以看出,每个线程无法获取到其它线程设置的loan对象
3.源码分析
1.get方法
首先获取当前线程的ThreadLocalMap变量,如果map为空的话调用setInitialValue方法返回默认值,如果map不为空获取entry中key对应的value值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
getMap方法:
threadLocals变量用于存储当前线程自身的ThreadLocal,所以虽然使用的是ThreadLocal的get方法,但是操作的实际是当前线程的threadLocals本地遍历副本的Map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
setInitialValue方法:
首先初始化value为null,然后当前线程获取map,如果为null的话,创建map,否则直接set,这里会返回null,所以get方法也会返回null的值
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;
}
2.set方法
如果是第一次使用set方法,创建一个默认大小为16的ThreadLocalMap,并将key设为ThreadLocalMap对象,value用传入的value,如果不是第一次直接map.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);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.ThreadLocalMap
ThreadLocalMap中的核心结构是一个Entry,用来存储key-value数据:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这里继承了弱引用,弱引用是只要gc发现了它,就会回收掉,这里的key使用弱引用是为了防止内存泄漏,因为如果key是强引用的话,当一个threadLocal对象为null后,还是会指向它,导致该对象不能被回收,造成内存泄漏,这里虽然key使用了弱引用,但是还是存在value指向的强引用,所以需要用remove方法来删除之前的key,不然还是会造成内存泄漏
4.应用场景
1.Spring的Transaction机制中,将一个线程中的事务放入ThreadLocal中,可以在整个方法调用栈中随时取出事务的信息进行操作,不会影响其它线程
2.Log4j2等日志框架中的MDC
3.HDFS edits_log的txId自增后放入线程本地副本,HDFS的每条edit_log都有一个txId,会将这个txId记录到当前线程方便在整个线程过程中随时取用
小结:
ThreadLocal最常用的2个场景就是:
1.线程中,在各个方法需要共享变量时使用。除了方法之间传递入参,通过ThreadLocal可以很方便的做到这一点
2.多线程操作时,防止并发冲突,保证线程安全。比如一般会拷贝一份数据到线程本地,自己修改本地变量,是线程安全的