美文网首页
ThreadLocal部分源码解析

ThreadLocal部分源码解析

作者: 忧郁的小码仔 | 来源:发表于2019-02-15 14:23 被阅读3次

    最近在整理Handler相关的东西,看到ThreadLocal,感觉源码不是太多,就拿过来啃啃,其实基本上ThreadLocal的原理是了解的,只是看了源码后就感觉更明了了一些。

    先贴张图吧:


    ThreadLocal图解

    撩源码之前,先来看下ThreadLocal的原理:
    一个ThreadLocal实例可以很方便的在任何地方存储或这获取当前线程的值,之前看到有人提到共享对象,其实这里和共享没有一分钱关系。如图所示,每个线程内部都持有一个ThreadLocalMap实例,而这个ThreadLocalMap实例用来干什么呢?就是用来存储ThreadLocal实例set(...)的值。可以看到ThreadLocalMap中持有一个数组,这个数组对应着多个ThreadLocal实例设置的值。比如说我有threadLocal1和threadLocal2,他俩都在线程1上set了一下。那么,ThreadLocalMap中的这个数组里就会出现两条记录。一条记录的key是threadLocal1,另一条记录的key是threadLocal2。 ThreadLocal取值的时候也很简单,只要去遍历下上图中的数组,对比数组中每一项的key和ThreadLocal实例就可以了。

    先看个简单的小例子:

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
        private ThreadLocal<String> mDemoThreadLocal = new ThreadLocal();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mDemoThreadLocal.set("main");
            Log.d(TAG, "onCreate: " + mDemoThreadLocal.get()); // 注释1
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mDemoThreadLocal.set("Thread 1");
                    Log.d(TAG, "onCreate: " + mDemoThreadLocal.get()); // 注释2
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mDemoThreadLocal.set("Thread 2");
                    Log.d(TAG, "onCreate: " + mDemoThreadLocal.get()); // 注释3
                }
            }).start();
        }
    }
    
    

    用过ThreadLocal的同学都知道,执行一下的话,//注释1标记的地方会打印出main//注释2标记的地方会打印出Thread 1//注释3标记的地方会打印出Thread 2。这就是ThreadLocal的简单用法。

    那么我们调用ThreadLocal的set(...)方法的时候,它背后到底干了什么呢?

        public void set(T value) {
            // 1 先获取当前线程
            Thread t = Thread.currentThread();
            // 2 根据当前线程,获取到一个ThreadLocalMap实例,
            //    ThreadLocalMap是定义在ThreadLocal里的一个内部类,
            //     具体的存储就是它来实现的。
            ThreadLocalMap map = getMap(t);
    
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
        ThreadLocalMap getMap(Thread t) {
            // 4 获取之前该线程上保存的ThreadLocalMap的引用。
            return t.threadLocals;
        }
        void createMap(Thread t, T firstValue) {
           // 3. 创建一个ThreadLocalMap,把当前ThreadLocal实例作为key,
           //     把值设置进去。并把这个ThreadLocalMap的引用丢给线程t。
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    上面的源码比较简单,当线程中取到的ThreadLocalMap为null的时候:

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
               // 这里的table就是一开始图中所说的数组。Entry用来存储具体的值
                table = new Entry[INITIAL_CAPACITY];
               // 通过ThreadLocal实例的threadLocalHashCode变换得到具体的索引。
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
    

    就去实例化一个ThreadLocalMap并将引用赋给当前线程。ThreadLocalMap初始化的时候也会创建一个数组,具体的key和value设置给Entry之后就保存到这个数组中去了。

    看下当线程中的ThreadLocalMap已经存在的时候的处理:

    private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                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;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    

    这里就是根据ThreadLocal的hashCode变换后得到一个索引。这里并不是直接把key和value作为新元素直接添加到table里去的。因为table初始化的时候有个默认空间,它会先去遍历table中的元素,如果key之前已经存在了,则直接覆盖掉,如果遍历到一个key为null,则直接用新的key和value把这一项替换掉,这也是为了不浪费空间,因为有些key,value可能已经失效了。

    下面再看下ThreadLocal的get()的处理逻辑

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    有了前面的讲解后,get()就简单多了。

    1. 获取到当前线程
      2.获取当前线程上的ThreadLocalMap实例
      3.根据TreadLocal实例获取到具体的key,value所在的Entry
      4.取到Entry中的value

    下面看下map.getEntry(this):

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

    这里有两部分处理,先根据ThreadLocal实例的hasCode变换的到索引。如果该索引对应的Entry有值,并且key也一致,就直接返回Entry。否则的就执行项目擦除,也就是getEntryAfterMiss(...)中要做的工作了。

    这一小部分源码基本上就能看出ThreadLocal的工作原理了,当然ThreadLocalMap里还有几个比较大的扩展数组,擦除元素等等方法也是很值得撩一撩的。

    如文中理解有错误,请大家不吝指出,一起探讨。

    相关文章

      网友评论

          本文标题:ThreadLocal部分源码解析

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