彻底搞懂ThreadLocal底层实现原理
当访问共享的可变数据时,通常需要使用同步。一种避免同步的方式就是不共享数据,仅在单线程内部访问数据,就不需要同步。该技术称之为线程封闭。
当数据封装到线程内部,即使该数据不是线程安全的,也会实现自动线程安全性。
文章目录
维持线程封闭性可以通过Ad-hoc线程封闭、栈封闭来实现,一种更加规范的方法是使用ThreadLocal类。ThreadLocal类提供线程局部变量,通过get、set等方法访问变量,为每个使用该变量的线程创建一个独立的副本。
一、ThreadLocal使用案例
案例中只开启了一个线程threadA,展示了在线程内部设置、获取、清除局部变量。
public class ThreadLocalTest { // 初始化ThreadLocal变量 static ThreadLocal<String> localVariable = new ThreadLocal<>(); static void print(String str) { // 打印当前线程本地内存中的变量值 System.out.println(str + ": " + localVariable.get()); // 清除当前线程本地内存中的变量 localVariable.remove(); } public static void main(String[] args) { // 创建线程A Thread threadA = new Thread(new Runnable() { @Override public void run() { // 设置线程A中的本地变量的值 localVariable.set("threadA localVariable"); print("threadA"); // 获取线程A中的本地变量的值 System.out.println("threadA remove after: " + localVariable.get()); } }); // 启动线程 threadA.start(); } } 运行结果: threadA: threadA localVariable threadA remove after: null
案例二:线程唯一标识符生成器,为每个调用ThreadId.get()方法的线程创建id。
public class ThreadId { // 下一个要被分配的线程id private static final AtomicInteger nextId = new AtomicInteger(0); // 线程局部变量 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // 返回当前线程唯一的id public static int get() { return threadId.get(); } }
二、ThreadLocal类的实现原理
在Thread类中有一个threadLocals成员变量,其类型是ThreadLocalMap,默认情况下为null。
ThreadLocal.ThreadLocalMap threadLocals = null;
当某线程首次调用ThreadLocal变量的get或set方法时,会进行对象创建。在线程退出时,当前线程的threadLocals变量被清空。
private void exit() { ... threadLocals = null; inheritableThreadLocals = null; ... }
每个线程的局部变量不是存放于ThreadLocal实例中,而是存放于线程的threadLocals变量,即线程内存空间中。threadLocals变量本质上是Map数据结构,可以存放多个ThreadLocal变量键值对。
【助解】ThreadLocal类可以看出一个外壳,线程中调用某ThreadLocal变量的set方法可以将变量值放入到该线程的threadLocals变量中,数据格式是<当前线程中该ThreadLocal变量的this引用,变量值>。当调用线程调用ThreadLocal变量的get方法时,从当前线程的threadLocals变量中取出key(引用)对应的value值。
2.1 核心方法set()
将ThreadLocal变量的当前线程副本的值设置为指定value值。
public void set(T value) { // 获取调用方法的当前线程 Thread t = Thread.currentThread(); // 获取当前线程自身的threadLocals变量 ThreadLocalMap map = getMap(t); if (map != null) // map不为空,则设置 map.set(this, value); else // map为空,说明第一次调用,初始化线程的threadLocals变量 createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
线程的threadLocals变量,即ThreadLocal.ThreadLocalMap,是HashMap结构,它的key是当前ThreadLocal的实例对象引用,value值是该ThreadLocal实例对象调用set方法设置的值。
2.2 核心方法get()
返回ThreadLocal变量在当前线程副本中的值。如果当前线程中没有该变量的值,返回值会被首次初始化为initialValue()方法的值。
public T get() { // 获取当前线程以及其threadLocals变量 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 如果threadLocals变量不为空 if (map != null) { // 根据当前ThreadLocal对象应用获取Entry,存在则直接返回value值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // threadLocals为空,初始化当前线程threadLocals变量 return setInitialValue(); } // threadLocals存在,设置初始值;不存在,初始化threadLocals变量 private T setInitialValue() { // 返回当前ThreadLocal变量的当前线程初始值 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } protected T initialValue() { return null; }
2.3 核心方法remove()
当前线程threadLocals变量存在的话,删除当前线程的ThreadLocal实例对象。
public void remove() { ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread()); if (var1 != null) { var1.remove(this); } }
三、ThreadLocal.ThreadLocalMap结构分析
ThreadLocalMap内部类实现细节,由于内容较多独立成了一篇博客。ThreadLocal.ThreadLocalMap实现细节
四、ThreadLocal内存泄漏问题
每个线程的ThreadLocal变量都存放在该线程的threadLocals变量中,如果当前线程一直不退出,这些ThreadLocal变量会一直存在,因此可能会导致内存泄漏。通过调用ThreadLocal类的remove方法避免这一问题。
ThreadLocalMap中采用ThreadLocal弱引用作为Entry的key,如果一个ThreadLocal没有外部强引用来引用它,下一次系统GC时,这个ThreadLocal必然会被回收,ThreadLocalMap中就会出现key为null的Entry。
ThreadLocal类的set、get、remove方法都可能触发对key为null的Entry清理操作。expungeStaleEntry方法会清空Entry及其value,Entry会在下次GC被回收。
如果当前线程一直在运行,并且一直不执行get、set、remove方法,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,导致这些key为null的Entry的value永远无法回收,造成内存泄漏。
参考资料
- 《Java并发编程实战》
- 《Java并发编程之美》
- 点赞
- 收藏
- 分享
- 文章举报
- 深入解析ThreadLocal底层实现原理
- 一文彻底搞懂CAS实现原理 & 深入到CPU指令
- [Android] 彻底了解Binder机制原理和底层实现
- 一文彻底搞懂CAS实现原理
- [Android] 彻底了解Binder机制原理和底层实现
- ThreadLocal的底层实现原理与应用场景
- 彻底了解Binder机制原理和底层实现
- 为什么你每次被问到HashMap底层原理都一知半解,这次彻底搞定它
- C++虚函数的底层实现原理
- iOS 多线程编程<十三、NSOperation图片下载,SDWebImage底层实现原理>
- ThreadLocal的实现原理(读书笔记)
- iOS KVO底层实现原理
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(一) .
- STL中容器的底层实现原理
- HashMap底层实现原理和源码分析
- 窥探ASP.Net MVC底层原理 实现跨越Session的分布式TempData
- ThreadLocal用法和实现原理
- C++虚函数的底层实现原理
- csdn精华目录 (技术实现,底层原理,值得关注的开发者)
- [JDK源码分析] ArrayList底层实现原理