ThreadLocal 线程隔离
刘水镜 人气:0对于 ThreadLocal 我们都不陌生,它的作用如同它的名字——用于存放「线程本地」变量。
先通过一个小例子感受一下:
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) throws Throwable { Thread threadOne = new Thread(()->{ threadLocal.set("ThreadOne:" + Thread.currentThread().getName()); log.info("线程 One 本地变量值为:{}", threadLocal.get()); threadLocal.remove(); log.info("线程 One remove 后本地变量值为:{}", threadLocal.get()); }); Thread threadTwo = new Thread(()->{ threadLocal.set("ThreadTwo:" + Thread.currentThread().getName()); log.info("线程 Two 本地变量值为:{}", threadLocal.get()); }); threadOne.start(); threadTwo.start(); }
运行结果:
线程 One 本地变量值为:ThreadOne:Thread-0
线程 One remove 后本地变量值为:null
线程 Two 本地变量值为:ThreadTwo:Thread-1
OK,从效果上看,ThreadLocal 确实是线程隔离的,那么,它是如何做到线程隔离的呢?下面我们扒一扒源码,看看它是如何做到的:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
set() 方法的逻辑如下:
- 获取当前线程
- 根据当前线程获取一个 ThreadLocalMap 对象
- 如果 map 不为 null 则保存
- 如果 map 为 null 则创建一个 map
getMap() 和 createMap() 方法都干了啥呢?我们点进去看:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
进入到两个方法内部后发现,不管执行哪个分支,最终是把值保存到了当前线程的 threadLocals 属性中。
查看 Thread 类的源码,你会发现类中定义了一个 threadLocals 属性,且初始值为 null,其类型为ThreadLocal.ThreadLocalMap。
public class Thread implements Runnable { // ... ThreadLocal.ThreadLocalMap threadLocals = null; // ... }
到此,我们发现了,原来 ThreadLocal 就是把我们要传递的对象放到了当前线程的 threadLocals 属性中。也就是说每个线程在用 ThreadLocal 保存对象时,其实就是将对象放到了当前线程实例对象的 threadLocals 属性里面。这样一来线程之间自然就是互相独立的啦。
再看看 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; }
ThreadLocal 的 get() 方法其实和 set() 方法逻辑很相似,先从当前线程的 threadLocals 属性中取,如果该属性为 null,那么就初始化。
当线程结束时,会调用当前线程实例的 exit() 方法,将 threadLocals 设置为 null,以便垃圾回收器将其回收掉。
// THread 类中的方法 private void exit() { // ... threadLocals = null; // ... }
最后,有一点需要格外注意:用完 ThreadLocal 一定要记得手动调用 remove() 方法,否则可能会产生脏数据甚至产生内存泄漏。
为啥呢?上面不是说线程结束时,会将 threadLocals 置为 null 吗?
是的,线程结束时,确实会做清理工作。
但,如果线程一直不结束呢?如果线程会被复用呢?比如使用了线程池。
所以,使用 ThreadLocal 一定要手动 remove()。
补充:下面看下ThreadLocal 是什么?有哪些使用场景?
ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。
说人话就是,ThreadLocal在每个线程都创建副本,每个线程可以访问自己的副本,线程之间相互不影响。
使用场景:
多线程环境中为每一个jdbc分配一个Connection连接,使用ThreadLocal去保存连接,这样就保证了每个线程在自己的连接上操作数据库,不会出现A线程关闭B的Connnection操作
Web中的session管理时,可以使用ThreadLocal记录每个线程的Session,这样保证每个线程都可以获取自己的session
解决线程安全问题,对于需要进行线程隔离的变量,可以使用ThreadLocal存储,确保线程隔离
总结:
ThreadLocal可以简单看作一个map,每个线程保存一个map,这个map只能存储一组数据,key为线程id, value就是要存的值
一个ThreadLocal只能存储一个变量,如果要存多个就创建多个ThreadLocal对象。
加载全部内容