并发问题的新思路——ThreadLocal

作者: 红Bean | 来源:发表于2019-03-22 16:13 被阅读97次
    • ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。

    • Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

    从Handler中进入ThreadLocal的世界

    • Android中一个线程只能有一个Looper,如果一个线程已经有Looper,我们再在线程中调用Looper.prepare()方法会抛出RuntimeException("Only one Looper may be created per thread")。那如何确保一个线程中最多只能有一个Looper呢?我们从Looper中寻找答案。

      public final class Looper {
          
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
          
          public static void prepare() {
              prepare(true);
          }
      
          private static void prepare(boolean quitAllowed) {
              //如果该线程已经有Looper
              if (sThreadLocal.get() != null) {
                  throw new RuntimeException("Only one Looper may be created per thread");
              }
              //该线程中没有Looper
              sThreadLocal.set(new Looper(quitAllowed));
          }
          ...
      }
      

      从上面的源码我们能知道如果一个线程已经有Looper了sThreadLocal.get()便不为空就抛出异常。否则就会新建一个Looper然后set进入sThreadLocal。

    ThreadLocal

    • sThreadLocal是一个静态的成员变量,所有线程共享它。那它是如何实现同一个静态变量在不同的线程中调用get()方法却能返回不同值的骚操作的呢?让我们来看看ThreadLocal的get()set()

      public class ThreadLocal<T> {
          
        public T get() {
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null) {
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null)
                      return (T)e.value;
              }
              return setInitialValue();
          }
          
          public void set(T value) {
              Thread t = Thread.currentThread();
              //获取Thread中的成员变量ThreadLocalMap
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
          }
          
          ThreadLocalMap getMap(Thread t) {
              return t.threadLocals;
          }
          
          void createMap(Thread t, T firstValue) {
              t.threadLocals = new ThreadLocalMap(this, firstValue);
          }
          
          private T setInitialValue() {
              //initialValue()返回null
              T value = initialValue();
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
              return value;
          }
          ...
      }
      
      public class Thread implements Runnable {
        ...
        ThreadLocal.ThreadLocalMap threadLocals = null;
        ...   
      }
      

      在每个Thread中都有一个ThreadLocalMap成员变量,ThreadLocal中的get()set()方法都是通过获取到当前线程的引用后直接再通过getMap()方法拿到ThreadMap的引用。ThreadLocal就是通过能获取到每个线程中的ThreadLocalMap,从而实现线程间的数据隔离。ThreadLocalMap只能通过ThreadLocal的createMap()方法初始化。就让我们来看看ThreadLocalMap。

    ThreadLocalMap

    • ThreadLocalMap是ThreadLocal的内部类。它用来存储数据,采用类似hashmap机制,存储了以ThreadLocal为key,需要隔离的数据为value的Entry键值对数组结构。里面有一些具体关于如何清理过期的数据、扩容等机制,思路基本和hashmap差不多,有兴趣的可以自行阅读了解。

      static class ThreadLocalMap {
          ...
          //存储放入的数据
          private Entry[] table;
          
          //Entry继承ThreadLocal的弱引用
          static class Entry extends WeakReference<ThreadLocal> {
              //要存储的变量
              Object value;
      
              Entry(ThreadLocal k, Object v) {
                  super(k);
                  value = v;
              }
          }
          
          private Entry getEntry(ThreadLocal key) {
              //获取存储数据的位置
              int i = key.threadLocalHashCode & (table.length - 1);
              Entry e = table[i];
              if (e != null && e.get() == key)
                  return e;
              else
                  return getEntryAfterMiss(key, i, e);
          }
          
          private void set(ThreadLocal key, Object value) {
              Entry[] tab = table;
              int len = tab.length;
              int i = key.threadLocalHashCode & (len-1);
              //遍历整个Entry数组
              for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                   ThreadLocal k = e.get();
                   if (k == key) {
                       //覆盖数据
                       e.value = value;
                       return;
                   }
                   if (k == null) {
                       replaceStaleEntry(key, value, i);
                       return;
                   }
              }
              //如果位置为空,就新建有要存储的Entry后放入数组
              tab[i] = new Entry(key, value);
              int sz = ++size;
              if (!cleanSomeSlots(i, sz) && sz >= threshold)
                  rehash();
          }
          ...
      }
      
    • ThreadLocal实例被线程的ThreadLocalMap实例持有,也可以看成被线程持有。但是ThreadLocalMap的key是ThreadLocal实例的弱引用。从而避免了内存泄漏。

    总结

    • 每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。每个Thread只访问自己的 Map,那就不存在多线程写的问题,也就不需要锁。
    • 与同步机制比较:对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
    • 在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

    参考

    相关文章

      网友评论

        本文标题:并发问题的新思路——ThreadLocal

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