加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

正确理解Thread Local的原理与适用场景

发布时间:2018-08-17 01:49:25 所属栏目:教程 来源:郭俊
导读:技术沙龙 | 邀您于8月25日与国美/AWS/转转三位专家共同探讨小程序电商实战 一、ThreadLocal解决什么问题 由于 ThreadLocal 支持范型,如 ThreadLocal StringBuilder ,为表述方便,后文用 变量 代表 ThreadLocal 本身,而用 实例 代表具体类型(如 StringBui

这里并不需要考虑 ThreadLocalMap 的线程安全问题。因为每个线程有且只有一个 ThreadLocalMap 对象,并且只有该线程自己可以访问它,其它线程不会访问该 ThreadLocalMap,也即该对象不会在多个线程中共享,也就不存在线程安全的问题。

4. 设置实例

除了通过initialValue()方法设置实例的初始值,还可通过 set 方法设置线程内实例的值,如下所示。

  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); 

该方法先获取该线程的 ThreadLocalMap 对象,然后直接将 ThreadLocal 对象(即代码中的 this)与目标实例的映射添加进 ThreadLocalMap 中。当然,如果映射已经存在,就直接覆盖。另外,如果获取到的 ThreadLocalMap 为 null,则先创建该 ThreadLocalMap 对象。

5. 防止内存泄漏

对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏。

针对该问题,ThreadLocalMap 的 set 方法中,通过 replaceStaleEntry 方法将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。

  1. private void set(ThreadLocal<?> key, Object value) { 
  2.   Entry[] tab = table; 
  3.   int len = tab.length; 
  4.   int i = key.threadLocalHashCode & (len-1); 
  5.   for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 
  6.     ThreadLocal<?> k = e.get(); 
  7.     if (k == key) { 
  8.       e.value = value; 
  9.       return; 
  10.     } 
  11.     if (k == null) { 
  12.       replaceStaleEntry(key, value, i); 
  13.       return; 
  14.     } 
  15.   } 
  16.   tab[i] = new Entry(key, value); 
  17.   int sz = ++size; 
  18.   if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  19.     rehash(); 

五、适用场景

如上文所述,ThreadLocal 适用于如下两种场景

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。

对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。

六、案例

对于 Java Web 应用而言,Session 保存了很多信息。很多时候需要通过 Session 获取信息,有些时候又需要修改 Session 的信息。一方面,需要保证每个线程有自己单独的 Session 实例。另一方面,由于很多地方都需要操作 Session,存在多方法共享 Session 的需求。如果不使用 ThreadLocal,可以在每个线程内构建一个 Session实例,并将该实例在多个方法间传递,如下所示。

  1. public class SessionHandler { 
  2.   @Data 
  3.   public static class Session { 
  4.     private String id; 
  5.     private String user; 
  6.     private String status; 
  7.   } 
  8.   public Session createSession() { 
  9.     return new Session(); 
  10.   } 
  11.   public String getUser(Session session) { 
  12.     return session.getUser(); 
  13.   } 
  14.   public String getStatus(Session session) { 
  15.     return session.getStatus(); 
  16.   } 
  17.   public void setStatus(Session session, String status) { 
  18.     session.setStatus(status); 
  19.   } 
  20.   public static void main(String[] args) { 
  21.     new Thread(() -> { 
  22.       SessionHandler handler = new SessionHandler(); 
  23.       Session session = handler.createSession(); 
  24.       handler.getStatus(session); 
  25.       handler.getUser(session); 
  26.       handler.setStatus(session, "close"); 
  27.       handler.getStatus(session); 
  28.     }).start(); 
  29.   } 

该方法是可以实现需求的。但是每个需要使用 Session 的地方,都需要显式传递 Session 对象,方法间耦合度较高。

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读