ThreadLocal是如何存储数据的?理解源码,规避ThreadLocal坑
通常我们为了规避单例模式下多线程之间共享全局变量的问题,会使用ThreadLocal,不过很多人不知道ThreadLocal里数据的存储方式和我们平时理解的不太一样。
源码解析(直奔主题):
每个线程都有一个ThreadLocalMap对象:
这个Map里存了所有线程变量中保存的数据,保存的方式:
以ThreadLocal对象的弱引用作为key,ThreadLocal里“存放”的数据作为value,放在该Map中。
在我们创建了一个线程变量 maxLife=new ThreadLocal<Integer>(),之后执行其set(xxx)方法,其实是以maxLife这个对象为键,以xxx为值,将这组键值对放入当前线程的ThreadLocalMap对象中。
这种方式的存储有一个坑必须注意:
1、ThreadLocal对象一旦创建,那么在线程运行过程中不可随意修改其引用,替换会导致之前保存的内容无法找到:
——如果ThreadLocal不是static的,那么线程运行过程中多次修改其引用,可能会导致当前线程之前set的值无法找到;
——如果ThreadLocal是static的或者ThreadLocal持有者对象是单例的(关键在于是否被多个线程共享),那么线程运行过程中多次修改其引用,可能会导致所有共享该变量的线程之前set的值无法找到;
我们以static的ThreadLocal变量为例,通过简易代码加深理解
public class CustomThread extends Thread{ static ThreadLocal<String> threadLocal = null; private String key; public CustomThread(String key){ this.key = key; } public void run() { threadLocal = new ThreadLocal<>(); threadLocal.set(key); try { Thread.sleep(3000l); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadLocal.get()); } public static void main(String[] args) { CustomThread t1 = new CustomThread("t1"); CustomThread t2 = new CustomThread("t2"); t1.start(); t2.start(); } }
多次执行,将看到t1或者t2只能有一个打印出来。另一个永远是null。
maxLife 这个变量由于是static的,在t1和t2中是共享的,虽然maxLife作为key在t1和t2各自的ThreadLocalMap中对应的值是不一样的
我在t1或者t2中替换maxLife的引用,将导致之前set的值无法再次获取,就好比以下代码所表达的意思一样:
import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { Map map = new HashMap<>(); String key = "key1"; map.put(key,"value1"); key = "key2"; System.out.println(map.get(key)); } }
以上验证了我们了解到的内容。
总结
所以我们定义ThreadLocal变量,应该在一开始就为其赋值,使用过程中避免修改其引用指向对象,而不是让它等于null。
正确的使用姿势——为其加一个final修饰符,建议:
final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
使用完毕后调用其remove()方法,避免产生内存溢出或浪费。
完毕。