ThreadLocal是一个用于创建线程局部变量的类。当前线程通过ThreadLocal的set()方法设置的变量只对当前线程可见,通过get()获取设置的变量。
使用
支持泛型
1
ThreadLocal<String> threadLocal = new ThreadLocal<>();
当前线程通过ThreadLocal对象的set(value)/get()设置变量和获取设置的变量
1
2threadLocal.set("jinshuai");
threadLocal.get();
原理
- 每个线程Thread维护一个ThreadLocalMap<key,value>
- key是ThreadLocal对象,value是通过set(value)设置的值
- 当前线程调用threadLocal.set(“jinshuai”);
- 首先获取当前线程所维护的ThreadLocalMap
- 然后判断当前线程是否已经创建过这个ThreadLocalMap
- 如果已经创建,会将ThreadLocal对象当作key,和当前线程要设置的值当作value放到ThreadLocalMap。
- 如果没有创建,会创建并初始化一个ThreadLocalMap(类似HashMap 初始化数组长度为2的幂,设置扩充阈值…)然后同上↑
1 | public void set(T value) { |
- 当前线程调用threadLocal.get();
- 首先获取当前线程所维护的ThereadLocalMap
- 然后将ThreadLocal对象作为key获取对应的Entry
- 如果Entry不为空获取Entry的value
- 如果Entry为空直接返回一个setInitvalue()值也就是null
1 | public T get() { |
应用场景
- 如果一个对象非线程安全,但又不想通过加锁的方式实现线程安全,可以通过ThreadLocal.set()对象的值,比如SimpleDataFormat不是线程安全的,此时可以每个线程设置一个SimpleDataFormat对象
1 | private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<>(){ |
- 当某一个变量比如User对象在多个方法中传递时,会变得比较乱此时可以通过ThreadLocal设置变量
- 比如在Servlet中:
1 | doGet(HttpServletRequest req, HttpServletResponse resp) { |
- 每个方法都需要一个user,不够优雅(咳咳…),此时可以通过设置一个ThreadLocal单例,然后set(user):
1 | doGet(HttpServletRequest req, HttpServletResponse resp) { |
注意
- 用完以后应该调用remove()移除设定的值,防止内存泄漏
1 | // 会移除这个Entry |
- 在线程池中由于线程会被复用,所以不会停止,导致每个线程中ThreadLocalMap里的key和虚引用Entry关联(Entry继承了虚引用),GC时会将key引用的对象回收,但是Entry中的value对象一直有一个强引用value不会被回收造成内存泄漏。
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |