ThreadLocal是通过将变量设置成Thread的局部变量,即使用该变量的线程提供一个独立的副本,可以独立修改,不会影响其他线程的副本,这样来解决多线程的并发问题。ThreadLocal主要是线程不安全的类在多线程中使用,一般常用于数据库连接和Session管理。看下ThreadLocal的官方注释:
{@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID).//就是说ThreadLocal一般是作为private static变量来使用的,用于标记一个线程的一些状态
自己写的demo以及运行结果如下:
可以看出,打印出来的值是不一样的,这个是为什么?来分析下它的源码
点到ThreadLocal里面看了下,发现它是Thread类内部的一个属性:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;//本片主角 /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//使用它可以实现子线程可以获取父线程中的ThreadLocal变量
来看下ThreadLocal的Set()方法:
这里getMap其实获取的就是上面的ThreadLocal.ThreadLocalMap threadLocals,然后讲ThreadLocal的引用设置进去。
再来看下它的get()方法
:
也是获取到当前线程的ThreadLocalMap变量,然后讲当前ThreadLocal做为key,想要保存的值作为value设置到map中。
总结下个人理解:
每个线程的内部都会有一个ThreadLocalMap的变量。当我们使用同一个ThreadLocal时,实际上每次都是往不同的map中设置值,只是这些map的key都是同一个。这样自然就不会有线程安全问题了,因为我们使用的是value,不是key!!!
为什么要使用map是因为一个线程可以有不止一个的ThreadLocal变量。
同时也说明了一个问题,value是存在线程安全问题的:
看如下这个例子
public class ThreadLocalTest { private static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal(); private static List<Integer> list = new ArrayList<>(); public static void main(String[] args) { list.add(0); threadLocal.set(list); System.out.println(threadLocal.get()); for (int i = 0; i < 1; i++) { new Thread(new MyThread()).start(); } } static class MyThread implements Runnable { @Override public void run() { threadLocal.set(list); System.out.println(threadLocal.get().get(0)); } } }
看下它的输出:
可以看到MyThread线程中获取到了主线程中的值,所以使用ThreadLocal时,每个线程对应的value得是不同的,不能是共享的变量!
ThreadLocal为什么会引发内存泄露问题:
我们已经知道,ThreadLocal变量最终是设置到了Thread的ThreadLocalMap中。Key是ThreadLoacl变量的引用,Value是设置到ThreadLocal中的值。但是要注意的是,这个Key是一个弱引用:
为什么Key要设置成弱引用?来看下ThreadLocal内存结构图:
个人理解是,当ThreadLocal被废弃时,可以回收掉当前ThreadLocalMap中的Key。这个时候,如果再有对这个ThreadLocal的操作,那么就会把原先的Value置为null,然后会在下一次GC的时候回收掉。
但是,如果ThreadLocal引用变为null之后,不再对这个ThreadLocal变量进行操作了。那么就会由于Value这个强引用一直存在,一直都会有内存泄露的问题,个人理解这个就是ThreadLocal内存泄露的本质。
所以,最好的办法就是ThreadLocal使用完毕之后,手动调用remove方法将其回收掉。
参考:
https://www.zhihu.com/question/23089780(ThreadLocal和Synchroniezd区别)
https://www.cnblogs.com/chenkeyu/p/7623653.html(ThreadLocal中放置Session)
https://blog.csdn.net/tmr1016/article/details/100141446(ThreadLocal内存结构图)