ThreadLocal 能做什么
ThreadLocal 是 JDK 提供的一个工具类,它可以为每个使用它的线程创建一个线程本地的副本,从而能保证多个线程在访问时的安全问题。当多个线程在使用这个变量时,其实是在使用自己线程本地内存的变量,由于是线程级别的,因此就能完全避免多个线程访问时,资源竞争的安全问题。
ThreadLocal 的原理
要讲解 ThreadLocal 的原理,我们首先需要看 JDK 的源码,了解了 ThreadLocal 类的主要方法实现就能理解其原理了。使用 ThreadLocal 时主要就是调用它的两个方法即 get 方法和 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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
由于 ThreadLocal 是一个带泛型的类,所以在创建 ThreadLocal 对象时,需要指定对应的泛型而 set 方法的入参就是定义的泛型对象;
接下来看具体的逻辑:
1首先创建了一个当前调用 set 方法的线程实例对象;
2、调用 getMap 方法,入参为当前线程的实例对象,返回一个当前线程对象的 threadLocals 属性值,threadLocals 是Thread 类的一个属性,也就是说 Java 的所有线程对象都有这个属性,这个属性是一个 ThreadLocalMap 对象(ThreadLocalMap 其实就是一个简化版的 HashMap);
3、如果返回的 ThreadLocalMap 为 null(threadLocals 属性的默认值就是 null),则会创建一个 ThreadLocalMap 对象,会调用createMap 方法;
4、createMap 方法接收两个入参,一个为 当前调用线程的实例对象,一个为 firstValue(默认 firstValue 为 null)因此这个方法会创建一个 key 为当前调用线程实例,value 为当前 set 方法传入的值 的ThreadLocalMap 对象,并将这个对象赋值给当前线程实例的 threadLocals 属性;
总结:要想了解 set 方法,就需要了解 threadLocals 属性,简单理解它就是一个 key 为当前线程实例对象的 HashMap;set 方法就是把 set 的入参设置到这个 hashMap 的 value 中的过程;
接下来看 get 方法:
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();
}
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;
}
其实 get 方法和 set 方法一样,本质上都是操作当前线程实例的 threadLocals 属性,只是 get 方法是获取该属性的 value 值;
接下来看具体的逻辑:
1、前面的两个操作和 set 方法一样,获取当前调用线程实例的 threadLocals 属性值;
2、如果不为空则直接返回当前属性值的 value 值;
3、如果为空则为当前线程实例初始化一个 value 值为空的 threadLocals 属性(initialValue 方法的返回值为 null);
原理总结
1、通过以上源码介绍,其实 ThreadLocal 是一个带泛型的类,使用的时候就是调用它的 get 方法和它的 set 方法;
2、这两个方法的本质就是操作当前调用线程实例的 threadLocals 属性值;
3、threadLocals 属性是一个 Thread LocalMap 对象,这个对象就是一个简单的 HashMap,而它的 key 为当前线程的实例对象,value 为某个需要被共享的变量值;
4、由于这个值是放在当前线程实例的一个 Map 中,因此多个线程访问的时候是访问自己本地的变量,因此不可能有多线程安全访问的问题;
ThreadLocal 应用 Demo
先上代码
public static void main(String[] args) {
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
stringThreadLocal.set("main threadLocal");
System.out.println("main threadLocal value:"+stringThreadLocal.get());
Thread threadA = new Thread(() -> {
stringThreadLocal.set("threadA threadLocal");
System.out.println("threadA threadLocal value:" + stringThreadLocal.get());
});
Thread threadB = new Thread(() -> {
stringThreadLocal.set("threadB threadLocal");
System.out.println("threadB threadLocal value:" + stringThreadLocal.get());
});
threadA.start();
threadB.start();
}
结果
main threadLocal value:main threadLocal
threadA threadLocal value:threadA threadLocal
threadB threadLocal value:threadB threadLocal
上面是对 ThreadLocal 最简单的使用,虽然对 ThreadLocal 来说是一个对象,并且是被三个线程共同访问,正常情况下由于多线程的问题一定会表现为结果不可预知;但是由于 ThreadLocal 的特性,是当前线程的本地副本设置属性值,因此不会出现多线程安全的问题;可以用它来记录一下上下文的一内容信息,或者用来设置一个日志的唯一标识 uuid 等,来标识当前线程的执行日志,方便查找问题。