两大使用场景——ThreadLocal的用途
- 每个线程需要一个独享的对象(通常是工具类例如:SimpleDateFormat和Random),让某个需要用到的对象在线程间隔离
- 每个线程内需要保存全局变量,可以让不同的方法使用,避免参数传递的麻烦
使用ThreadLocal带来的好处
- 达到线程安全
- 不需要加锁,提高执行效率
- 更高效地利用内存、节省开销:相比于每个任务都新建一个SimpleDateFormat,显然用ThreadLocal可以节省内存和开销。
- 免去传参的繁琐:无论是场景一的工具类还是场景二的全局变量,都可以在任何地方通过ThreadLocal直接拿到,不需要每次传递同样的参数。ThreadLocal使得代码耦合度更低,更优雅。
主要方法
- T initialValue():初始化
- 该方法会返回当前ThreadLocal对应的初始值,这是一个延迟加载,只有在get的时候才会调用initialValue方法
- 线程在get之前调用了set方法,就不会调用initialValue方法
- initialValue只会调用一次,除非使用了remove方法后调用get
- 不重写initialValue方法,返回值是null。一般使用匿名内部类的方式来重写initialValue方法。
- void set(T t):为这个ThreadLocal设置一个新值
- T get():先取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal的引用作为参数传入,取出map中属于本ThreadLocal的value。如果是首次调用get(),则会调用initialValue来生成这个值,并创建一个map对象,并将生成的这个值与ThreadLocal类生成一个entry存入这个map中。
- void remove():删除对应这个ThreadLocal的值。
ThreadLocal原理
-
ThreadLocalMap类
ThreadLocalMap类,也就是Thread.threadLocals
ThreadLocalMap类是每个线程Thread类里面的变量,里面最重要的是一个键值对数组Extry[] table,可以认为是一个map,键值对:- 键:这个ThreadLocal
- 值:实际需要的成员变量,比如SimpleDateFormat对象。
- 处理冲突:ThreadLocalMap采用的是线性探测法,也就是如果发生冲突,就继续找下一个空位置,而不是用链表拉链。
-
两种场景都是调用的map.set()类设置值,也就是说,两种场景都会对应到ThreadLocalMap的一个Entry,只不过是起点和入口不一样。
ThreadLocal注意点
-
内存泄漏
- 什么是内存泄漏:某个对象不再有用,但是占用的内存却不能被回收。
- Key的泄漏:ThreadLocalMap中的Entry继承自WeakReference,是弱引用,弱引用的特点是,如果这个对象
只
被弱引用关联(没有任何强引用关联),那么这个对象就可以被回收
。 - ThreadLocalMap的每个Entry都是一个对key的弱引用,同时每个Entry都包含了一个对value的强引用。正常情况下,当线程终止了,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了。但是如果线程不被终止(比如线程需要保持很久,例如线程池),那么key对应的value就不能被回收。 因为有调用链:Thread → ThreadLocalMap → Entry(key为null) → Value。会导致value无法回收,就可能会出现OOM。JDK已经考虑到了这个问题,所以在set,remove,rehash方法中会扫描key为null的Entry,并把对应的value置为null,这样value对象就可以被回收。但是如果一个ThreadLocal不被使用,那么实际上这些方法也不会被调用,就依然会有内存泄漏的隐患
- 如何避免内存泄漏(阿里规约)
调用remove方法,就会删除对应的Extry对象,可以避免内存泄漏,所以使用完ThreadLocal之后,应该调用remove方法。
-
ThreadLocal空指针异常问题
- 若未初始化,get会返回一个null,若方法返回值涉及到装箱拆箱就可能导致空指针异常,并不是ThreadLocal的问题。
-
共享对象
- 如果在每个线程中ThreadLocal.set()保存的东西本来就是多线程共享的同一个对象,比如static对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
-
如果可以不使用ThreadLocal就解决问题,那么就不要强行使用,比如任务数很少的时候,在局部变量中可以新建对象就可以解决问题,那么就不需要使用到ThreadLocal
-
优先使用框架支持,而不是自己创造。