ThreadLocal

作者: 有且仅有la | 来源:发表于2018-06-05 16:47 被阅读0次

    我们知道线程也是一个「对象」,当线程这种对象想为我们提供一个「可以存取我们自定义变量的功能时」,来看下它是怎么做的。

    一、Thread 中的成员变量ThreadLocalMap

    1. 这种功能通过成员变量java.lang.Thread#threadLocals来完成的,它是一个自定义Map类型:

      public class Thread implements Runnable {
          /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
          ThreadLocal.ThreadLocalMap threadLocals = null;
      }
      

      可以看到它的:

      • null : 代表懒加载,只在你需要存数据时才会实例化一个ThreadLocalMap对象。
      • default : 访问修饰符,代表只想让java.lang下被访问,就是给java.lang.ThreadLocal用。
    2. 为什么不在Thread里面给我们暴露方法?

      设计者觉得暴露一个类似get()/put()方法给你,让你自己用任意的Object类型K/V操作太Low。

      所以他把这个功能「封装」为一个类,称为线程本地变量java.lang.ThreadLocal,每一个你需要的变量就是一个此类的实例。

    二、ThreadLocal 实现

    它们的关系是,Thread has a ThreadLocalMap has a Entry has a ThreadLocal

    1. 既然用Map存,那KeyValue分别是什么?

      • Key : 线程变量对象ThreadLocal
      • Value : 你自定义操作的那个数据Object
    2. 当然,这个内部Map设计意图肯定要对使用者透明

      所以我们不能直接操作这个Map,而是使用的ThreadLocal的三个public方法:get()/set()/remove()

      set 方法

      public void set(T value) {
          // 获取当前线程
          Thread t = Thread.currentThread();
          // 简单的返回Thread 对象的成员变量threadLocals
          ThreadLocalMap map = getMap(t);
          if (map != null)
              map.set(this, value);
          else
              createMap(t, value);
      }
      

      get 方法

      public T get() {
          Thread t = Thread.currentThread();
          ThreadLocalMap map = getMap(t);
          if (map != null) {
              // 这个Map 没有get方法只有getEntry
              ThreadLocalMap.Entry e = map.getEntry(this);
              if (e != null)
                  // 你之前设置的值
                  return (T)e.value;
          }
          return setInitialValue();
      }
      

      remove 方法

       public void remove() {
           ThreadLocalMap m = getMap(Thread.currentThread());
           if (m != null)
               m.remove(this);
       }
      

    三、ThreaLocal 使用

    1. Example

      JavaDoc 中已经给出了,摘抄一下:

      import java.util.concurrent.atomic.AtomicInteger;
      
      public class ThreadId {
          // Atomic integer containing the next thread ID to be assigned
          private static final AtomicInteger nextId = new AtomicInteger(0);
      
          // Thread local variable containing each thread's ID
          private static final ThreadLocal<Integer> threadId =
              new ThreadLocal<Integer>() {
                  @Override protected Integer initialValue() {
                      return nextId.getAndIncrement();
              }
          };
      
          // Returns the current thread's unique ID, assigning it if necessary
          public static int get() {
              return threadId.get();
          }
      }
      
    2. 解释:初始化和set()方法

      首先,之所以要使用「匿名内部类」及其对象,是因为想让你在一行代码内就完成初始化。除此之外,initialValue()set()的实现代码区别不大。

      然后,一旦哪个Thread引用到这个ThreadLocal对象了,就会在自己的threadLocals中被设置一个值。

    3. 获取不必解释,就是从当前线程的Map中取即可。

    四、WeakReference 的应用

    1. 假设我们来设计那个Map和其中的Entry

    1. Map 和Entry

      static class ThreadLocalMap {
          
              static class Entry<K,V> {
              final K key;
              V value;
          }
      }    
      
    2. 我们的使用场景是这样的

      // 这里注意,没有final 了
      private static ThreadLocal<Integer> threadId =
          new ThreadLocal<Integer>() {
              @Override protected Integer initialValue() {
                  return nextId.getAndIncrement();
          }
      }
      

      当我们想不再使用threadId时,我们会把threadId设置为null

      public void method1(){
          // ... 一些操作
          threadId = null;
      }
      
    3. 此时,GC 会清除这个对象吗?

      分析之后我们发现其实大概率不会,因为那个ThreadLocal对象并非「不可达对象」,这是因为我们的Thread.threadLocals.Entry.key仍然引用着它。只有当Thread对象本身被销毁后它才会被回收,不过在Web 项目或RPC 项目中我们一般都使用线程池,所以一般来说线程很难被销毁。

      很明显,我们的设计有问题。那我们看看设计者是如何做的。

    2. 实际代码

    1. 来看看实际代码

      static class ThreadLocalMap {
      
          /**
           * The entries in this hash map extend WeakReference, using
           * its main ref field as the key (which is always a
           * ThreadLocal object).  Note that null keys (i.e. entry.get()
           * == null) mean that the key is no longer referenced, so the
           * entry can be expunged from table.  Such entries are referred to
           * as "stale entries" in the code that follows.
           */
          static class Entry extends WeakReference<ThreadLocal> {
              /** The value associated with this ThreadLocal. */
              Object value;
      
              Entry(ThreadLocal k, Object v) {
                  super(k);
                  value = v;
              }
          }
          // ...
      }
      

      Entry是个弱引用。这意味着:一旦GC 确定ThreadLocal对象是「弱可达对象」时,它将清除所有引用此对象的「弱引用」,也就是说此时Entry也将被清除。

    2. 最后再来复习一下对象的「可到达性」

      从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:

      1. 强可到达 对象:如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象。
      2. 软可到达 对象:如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象。
      3. 弱可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
      4. 虚可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。

      最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象。

    相关文章

      网友评论

        本文标题:ThreadLocal

        本文链接:https://www.haomeiwen.com/subject/sghmsftx.html