ThreadLocal

一、两大使用场景——ThreadLocal的用途

典型场景1:

  每个线程需要独享一个对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)
每个Thread内有自己的实例副本,不共享
  比喻:教材只有一本,一起做笔记有线程安全问题。复印后没问题

SimpleDateFormat的进化之路:

  1、2个线程分别用自己的SimpleDateFormat,这没问题
  2、后来延伸出10个
  3、但是当需求变成了1000个,那么必然要使用线程池
  4、所有的线程都共用一个静态的SimpleDateFormat对象,但是出现了线程安全问题,通过加锁解决了问题,但是加锁会影响任务的执行效率
  5、利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全

典型场景2:

  每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦
  用ThreadLocal保存一些业务内容(用户权限信息、从用户系统获取到用户名、user ID等)
  这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的
  在线程生命周期内,都通过这个静态ThreadLocal实例的get()方法取得自己set过的那个对象,避免了将这个对象(例如user对象)作为参数传递的麻烦
  强调的是同一个请求内(同一个线程内)不同方法间的共享
  不需重写initialValue()方法,但是必须手动调用set()方法
ThreadLocal的两个作用
  1、让某个需要用到的对象在线程间隔离(每个线程都有自己的独立对象)
  2、在任何方法中都可以轻松获取到该对象
根据共享对象的生成时机不同,选择initialValue或set来保存对象
  场景一:initialValue
    在ThreadLocal第一次get的时候把对象给初始化出来,对象的初始化时机可以由我们控制
  场景二:set
    如果需要保存到ThreadLocal里的对象的生成时机不由我们随意控制,例如拦截器生成的用户信息
    用ThreadLocal.set直接放到我们的ThreadLocal中去,一遍后续使用

二、使用ThreadLocal带来的好处

达到线程安全
不需要加锁,提高执行效率
更高效地利用内存、节省开销(相比于每个任务都新建一个SimpleDateFormat,显然用ThreadLocal可以节省内存和开销)
免去传参的繁琐(不需要每次都传同样的参数,ThreadLocal使得代码耦合度更低,更优雅)

三、主要方法介绍

T initialValue():初始化
  1、该方法会返回当前线程对应的"初始值",这是一个延迟加载的方法,只有在调用get的时候,才会触发
  2、当线程第一次使用get方法访问变量时,将调用此方法
  3、每个线程最多调用一次此方法,但如果已经调用了remove()后,再调用get(),则可以再次调用此方法
  4、如果不重写本方法,这个方法会返回null。一般使用匿名内部类的方法来重写initialValue()方法
void set(T t):为这个线程设置一个新值
T get():得到这个线程对应的value。如果是首次调用get(),则会调用initialValue来得到这个值
void remove():删除对应这个线程的值

四、注意点

1、内存泄漏
什么是内存泄漏?
  某个对象不再有用,但是占用的内存却不能被回收
如何避免内存泄漏?
  调用remove方法,就会删除对应的Entry对象,可以避免内存泄漏,所以使用完ThreadLocal之后,应该调用remove方法。
2、空指针异常
  空指针异常并不是由ThreadLocal导致,而是由装箱、拆箱导致的
3、共享对象
4、如果可以不使用ThreadLocal就解决问题,那么不要强行使用
5、优先使用框架的支持,而不是自己创造

 

posted @ 2020-12-16 09:36  michealyangblog  阅读(86)  评论(0编辑  收藏  举报