ThreadLocal部分源码解析

2019-02-15  本文已影响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里还有几个比较大的扩展数组,擦除元素等等方法也是很值得撩一撩的。

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

上一篇下一篇

猜你喜欢

热点阅读