1. ThreadLocal简述
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。
如果想实现每一个线程都有自己的专属本地变量该如何解决呢?
在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
ThreadLocal
类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal
类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
如果你创建了一个ThreadLocal
变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal
变量名的由来。
可以使用 get()
和 set()
方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
举个栗子:
比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。
2. ThreadLocal栗子
举个栗子:
public class ThreadLocalDemo implements Runnable {
private static final ThreadLocal<SimpleDateFormat> FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
SimpleDateFormat yyyyMMdd_hHmm = new SimpleDateFormat("yyyyMMdd HHmm");
return yyyyMMdd_hHmm;
});
public static void main(String[] args) throws InterruptedException {
ThreadLocalDemo obj = new ThreadLocalDemo();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(obj, "" + i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name= " + Thread.currentThread().getName() + "; default Formatter = " + FORMAT_THREAD_LOCAL.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won't reflect to other threads
FORMAT_THREAD_LOCAL.set(new SimpleDateFormat());
System.out.println("Thread Name= " + Thread.currentThread().getName() + " formatter = " + FORMAT_THREAD_LOCAL.get().toPattern());
}
}
测试结果:
Thread-0 已经改变了 formatter 的值,但仍然是 thread-2 默认格式化程序与初始化值相同,其他线程也一样。
3. ThreadLocal源码分析
Thread类源码:
public class Thread implements Runnable {
//......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......
}
从上面Thread类 源代码可以看出Thread类中有一个threadLocals和 一个inyertableThreadLocals变量;
它们都是 ThreadLocalMap类型的变量,可以把 ThreadLocalMap理解为ThreadLocal类实现的定制化的HashMap。
默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal类的set或get方法时才创建它;
实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()、set()方法。
ThreadLocal的set方法
public void set(T value) {
//获取当前线程实例
Thread t = Thread.currentThread();
//以实例t为key,获取对应的线程的集合对象
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
分析上面set方法代码:
- 最终的变量是放在了当前线程的
ThreadLocalMap
中,并不是存在ThreadLocal
上; ThreadLocal
可以理解为只是ThreadLocalMap
的封装,传递了变量值。-
ThreadLocal
类中可以通过Thread。currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap
对象。
ThreadLocalMap是ThreadLocal的静态内部类,
每个Thread
中都具备一个ThreadLocalMap
,而ThreadLocalMap
可以存储以ThreadLocal
为 key ,Object 对象为 value 的键值对。
进入ThreadLocalMap这个类看一下:
如下图,比如我们在同一个线程中声明了两个 ThreadLocal
对象的话,会使用 Thread
内部都是使用仅有那个ThreadLocalMap
存放数据的,ThreadLocalMap
的 key 就是 ThreadLocal
对象,value 就是 ThreadLocal
对象调用set方法设置的值。