ThreadLocal是Java中的一个类,用于解决多线程环境下的并发问题。以下是对ThreadLocal的详细解释:
定义:
ThreadLocal,即线程局部变量,是Java提供的一种线程隔离的变量存储机制。每个线程都会有一个独立的ThreadLocal变量副本,这些副本之间互不干扰,从而实现线程间的数据隔离。
原理:
ThreadLocal内部维护了一个名为ThreadLocalMap的静态内部类,该Map的键是ThreadLocal对象本身(实际上是ThreadLocal的一个弱引用),值是线程变量的副本。每个线程都有一个自己的ThreadLocalMap实例,用于存储该线程独有的变量副本。因此,当线程访问ThreadLocal变量时,会通过自身的ThreadLocalMap获取对应的变量副本,从而保证了线程间的数据隔离。
- ThreadLocal的set方法
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}}
简单来说,每个Thread线程都维护了一个ThreadLocalMap,当使用ThreadLocal对象存值时,会先根据当前线程找到ThreadLocalMap,这个ThreadLocalMap 的key是ThreadLocal对象,值就是ThreadLocal对象存储的值。因为一个线程运行期间会创建很多的ThreadLocal对象,所以这里是个Map结构。
- ThreadLocalMap
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
在ThreadLocalMap中key也就是ThreadLocal对象是弱引用的,当一个线程运行完成,如果栈内存或堆内存中没有强引用关联,则一定会被GC掉,但是value不会回收,value是和线程生命周期绑定的,线程不销毁,value也不会销毁。但是像Tomcat或者在业务系统中自定义的线程池都是会复用线程的,如果一直在创建ThreadLocal,则会导致内存泄漏,进而引发内存溢出。
- 脏读代码示例
一般的,ThreadLocal会和当前运行线程绑定,如果运行线程不销毁,且没有清理ThreadLocal中的值,则会产生脏读。
例如Tomcat每次请求来都会开一个线程处理,但是线程是被线程池管理的,执行完一次请求之后,线程会被复用,如果一个线程ThreadLocal没有及时清理,则下一个请求可能会由于业务逻辑判断错误读取到其他线程的值,我使用CompletableFuture对象来模拟实现一下。
- 定义一个数据值存储和查询对象
public class DataHandlerSafe<T> {private ThreadLocal<T> data = new ThreadLocal<>();public void setData(T t){this.data.set(t);}public T getData(){return data.get();}}
public static void main(String[] args) throws Exception {testDirtyReading();}private static void testDirtyReading() throws InterruptedException {int size = 10;ExecutorService executorService = Executors.newFixedThreadPool(size);DataHandlerSafe<String> dataHandler = new DataHandlerSafe<>();for (int i = 0; i < size; i++) {CompletableFuture.runAsync(() -> {String threadData = UUID.randomUUID().toString();dataHandler.setData(threadData);System.out.println(Thread.currentThread().getName() + " ---> 设置的数据 :" + threadData);},executorService);}Thread.sleep(1000);System.out.println(" ================= ");for (int i = 0; i < size + 10; i++) {CompletableFuture.runAsync(() -> {String data = dataHandler.getData();System.out.println(Thread.currentThread().getName() + " ---> 获取的数据 :" + data);},executorService);}}
使用Executors创建了一个线程池对象,循环十次,让线程执行任务,每个线程都设置了对应的ThreadLocal值。
继续循环二十次,此时控制台打印,这二十次由于都是复用了十个线程,是所以一定会有重复打印的值。
pool-1-thread-1 ---> 设置的数据 :448cbfea-0450-42fc-9cd9-ec0b264964e5
pool-1-thread-10 ---> 设置的数据 :a722a8e7-43e6-4d8d-ada1-a60856f30c74
pool-1-thread-6 ---> 设置的数据 :f306a0ae-a439-4d46-b497-ec71ab790e4b
pool-1-thread-7 ---> 设置的数据 :0c39bb11-4f72-4ee8-aa48-a8b9ef135693
pool-1-thread-2 ---> 设置的数据 :36ed5fcf-6d43-4c32-9797-34dfa1bf6ebb
pool-1-thread-3 ---> 设置的数据 :8729b8ee-f155-48cd-a741-721cdf0d37ac
pool-1-thread-8 ---> 设置的数据 :6f193df1-8983-4c40-b5ba-30a84f892e38
pool-1-thread-5 ---> 设置的数据 :2419f367-2bce-4bef-89cd-0e43a98c8f82
pool-1-thread-4 ---> 设置的数据 :616a3d4c-87fe-4e88-b3c8-bf13976ba04b
pool-1-thread-9 ---> 设置的数据 :3d93c72b-9d72-4b63-996d-075393511151=================
pool-1-thread-2 ---> 获取的数据 :36ed5fcf-6d43-4c32-9797-34dfa1bf6ebb
pool-1-thread-7 ---> 获取的数据 :0c39bb11-4f72-4ee8-aa48-a8b9ef135693
pool-1-thread-1 ---> 获取的数据 :448cbfea-0450-42fc-9cd9-ec0b264964e5
pool-1-thread-7 ---> 获取的数据 :0c39bb11-4f72-4ee8-aa48-a8b9ef135693
pool-1-thread-10 ---> 获取的数据 :a722a8e7-43e6-4d8d-ada1-a60856f30c74
pool-1-thread-8 ---> 获取的数据 :6f193df1-8983-4c40-b5ba-30a84f892e38
pool-1-thread-6 ---> 获取的数据 :f306a0ae-a439-4d46-b497-ec71ab790e4b
pool-1-thread-8 ---> 获取的数据 :6f193df1-8983-4c40-b5ba-30a84f892e38
pool-1-thread-6 ---> 获取的数据 :f306a0ae-a439-4d46-b497-ec71ab790e4b
pool-1-thread-10 ---> 获取的数据 :a722a8e7-43e6-4d8d-ada1-a60856f30c74
pool-1-thread-7 ---> 获取的数据 :0c39bb11-4f72-4ee8-aa48-a8b9ef135693
pool-1-thread-1 ---> 获取的数据 :448cbfea-0450-42fc-9cd9-ec0b264964e5
pool-1-thread-4 ---> 获取的数据 :616a3d4c-87fe-4e88-b3c8-bf13976ba04b
pool-1-thread-9 ---> 获取的数据 :3d93c72b-9d72-4b63-996d-075393511151
pool-1-thread-2 ---> 获取的数据 :36ed5fcf-6d43-4c32-9797-34dfa1bf6ebb
pool-1-thread-5 ---> 获取的数据 :2419f367-2bce-4bef-89cd-0e43a98c8f82
pool-1-thread-3 ---> 获取的数据 :8729b8ee-f155-48cd-a741-721cdf0d37ac
pool-1-thread-10 ---> 获取的数据 :a722a8e7-43e6-4d8d-ada1-a60856f30c74
pool-1-thread-6 ---> 获取的数据 :f306a0ae-a439-4d46-b497-ec71ab790e4b
pool-1-thread-8 ---> 获取的数据 :6f193df1-8983-4c40-b5ba-30a84f892e38
在多线程场景下,在使用完ThreadLocal后,一定要能保证try catch finally 来释放对应资源。