并发问题的新思路——ThreadLocal

  • 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 sThreadLocal = new ThreadLocal();
        
        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 {
        
      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 {
            //要存储的变量
            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

你可能感兴趣的:(并发问题的新思路——ThreadLocal)