使用场景-用途

每个线程需要一个独享的对象

(通常是工具类,典型需要使用的类有SimpleDataFormat和Random)

  1. ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<SimpleDateFormat>(){
  2. @Override
  3. protected SimpleDateFormat initialValue() {
  4. return new SimpleDateFormat("HH:mm:ss");
  5. }
  6. };

每个线程内需要保存全局变量

(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦

2种用法

initialValue:重写赋值
set:调用赋值

好处

达到线程安全
不需要加锁,提高执行效率
更高效的利用内存、节省开销:相比每个任务创建一个对象变成每个线程创建一个对象
免去传参的麻烦

原理

截屏2020-02-04上午11.06.33.png

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }
  9. public T get() {
  10. Thread t = Thread.currentThread();
  11. ThreadLocalMap map = getMap(t);
  12. if (map != null) {
  13. ThreadLocalMap.Entry e = map.getEntry(this);
  14. if (e != null) {
  15. @SuppressWarnings("unchecked")
  16. T result = (T)e.value;
  17. return result;
  18. }
  19. }
  20. return setInitialValue();
  21. }

注意点

内存泄漏

  • ThreadLocal的每个Entry都是一个对key的弱引用,同时,每个Entry都包含了一个对Value的强引用
  • 正常情况下,当线程终止时,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了
  • 但是线程不终止(比如使用线程池时),那么key对应的value就不会被回收,就可能出现OOM
  • JDK已经考虑到这个问题,所以在set,remove,rehash方法中会扫描key为null的Entry,并把对应的value设为null,这样value对象就可以被回收
  • 但是一个ThreadLocal不被使用时,实际上不会调用set,remove,rehash方法,那么value就不会被回收

    如何避免

    调用remove方法,就会删除对应的Entry对象,就可以避免内存泄漏,所以在使用完ThreadLocal对象后,应该调用remove方法

空指针异常

当ThreadLocal里没有存储value时,会返回Null,但需要注意一些场景,(比如Long对象自动装箱拆箱转成基本类型long时,出现的空指针异常)

共享对象

如果每个线程中ThreadLocal.set()进去的对象本来就是多线程共享的对象(比如static对象),还是会出现线程安全问题