java多线程环境下资源隔离机制ThreadLocal详解
背景
在我们实际业务开发过程中,会遇到这样的场景,需要在单个线程整个生命周期内部保存全局的数据信息,比如说:单个线程内部全局唯一数据库连接、唯一的日志记录id,线程用户信息等。
那么为什么不直接在线程内部采用局部变量呢?
这个涉及到线程内部方法调用传递参数比较麻烦,如果直接用Thread对象获取更加方便。
Thread线程对象
每个线程有自己独立的Thread对象
组成部分 | 说明 |
---|---|
虚拟机栈 | 方法调用栈、局部变量表(线程私有) |
本地方法栈 | 执行 native 方法使用的栈(线程私有) |
线程对象 Thread 本身 | 每个线程创建时对应一个 Thread 实例 |
ThreadLocalMap | ThreadLocal 变量专属的存储空间(线程私有) |
程序计数器(PC) | 当前执行的指令地址(线程私有) |
ThreadLocalMap 的存储
Thread对象包含了一个私有变量
ThreadLocal.ThreadLocalMap threadLocals = null;
线程内部可以通过这个threadLocals成员属性拿到ThreadLocalMap,
ThreadLocalMap存储了什么呢?
他的每一个节点是一个Entry结构
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
这是什么意思呢?
就是相当于是一个键值对,他的key就是ThreadLocal对象,并且是弱引用,强引用就是我们在实际的方法栈中引用堆上的实例数据,弱引用是当JVM进行GC会回收掉他,前提是没有其他强引用。这样做是防止当前线程结束,线程回到了线程池,导致线程对象仍然间接引用着key,没有被垃圾回收,导致内存泄漏。
value值就是我们存储的数据值。
重要
key是弱引用,但是value是强引用,如果使用线程池,不释放,就会出现value内存泄漏,所以在线程结束时需要调用threadlocal的remove方法清楚数据。
threadLocalMap是一个Entry数组结构
private Entry[] table;
每一个节点是一个ThreadLocal对象key,和一个值。
ThreadLocal对象
ThreadLocal对象是我们在代码中创建的存储数据的结构
例如:
ThreadLocal local = new ThreadLocal();local.set(5);local.set(6);
ThreadLocal的set方法执行过程:
- 获取当前线程Thread对象的ThreadLocalMap实例对象
- 如果没有,null,那么就创建一个ThreadLocalMap,并且赋值给Thread对象的threadLocals
- 如果存在,就从ThreadLocalMap中获取到当前ThreadLocal对象为key的值,因为ThreadLocalMap是数组,就通过对ThreadLocal对象进行hash求知取到他的数组下标,如果这个下标不等于这个ThreadLocal对象,说明存在hash冲突,就通过
开放寻址法
,继续遍历下一个数组下标的元素,直到末尾。 - 找到之后,就可以设置value
- get方法也是一个逻辑