ThreadLocal浅析

    xiaoxiao2021-03-25  108

    概述

    这里是基于java JDK1.8源码分析的。

    首先从整体上描述一下ThreadLocal:

    ThreadLocal中的ThreadLocalMap静态内部类使用的是线性探测表(散列表)作为数据结构。

    每一个Thread对象都持有一个ThreadLocalMap,该Map以ThreadLocal对象的引用作为Key来查询/保存/删除Value。

    一个线程中可以使用多个ThreadLocal对象来保存不同的数据,不同的线程可以使用相同的ThreadLocal对象作为Key,但是由于每个线程持有的ThreadLocalMap对象不同,所有相同Key对应的Value不同。做个类比,每个线程对应不同的学生,ThreadLocal是一个个页码,线程持有的ThreadLocalMap是不同学生持有不同版本的字典,当老师说了一个页码以后,不同学生在自己手上独一无二的字典中查找到的内容当然不同。

    源码分析

    构造器

    构造器中并没有做什么

    public ThreadLocal() { }

    get方法

    ThreadLocal的get方法回返回当前线程中该ThreadLocal实例对应保存的数据。

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

    方法中,首先获取当前线程t,然后得到t的ThreadLocalMap。ThreaLocalMap是ThreadLocal的内部类,我们看一下它的主要成员:

    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; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; ..... }

    内部有一个继承了弱引用的Entry静态类,我们可以看到Entry内部其实是Key为弱引用,关于会造成的内存泄露的分析,大家可以看一下这篇文章

    接着上面所说,如果该Map不为空,则以该ThreadLocal实例的引用作为Key在该Map中寻找Entry并返回Value;如果Map为空则调用setInitialValue方法返回数据。下面我们看一下getEntry方法:

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

    由于ThreadLocalMap内部是用线性探测的散列表实现的(底层维护一个Entry数组table[],区别于HashMap等容器的拉链表),这里很清楚地看到由key的hashcode&数组长度 得到下标之后,分为查找中和没有查找中两种情况,这里不再详细说明了。我们回到上面,如果Map为空,则调用setInitialValue方法返回数据,我们看一下该方法:

    private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }

    首先调用initialValue方法获得了一个Value

    protected T initialValue() { return null; }

    该方法为空,一般是子类继承ThreadLocal类重写这个方法返回一个自己想保存的数据的初始值。回到setInitialValue方法中,接下来也是获得当前线程,再获得ThreadLocalMap,如果Map不空,进行set;如果Map空,新建Map:

    void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

    在这个方法中我们为Thread的ThreadLocal.ThreadLocalMap引用新建一个对象:

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }

    这里我们把调用get方法的ThreadLocal对象和在initialValue方法中返回的值作为一个Entry存入线性探测表中。

    我们总结一下,当调用了某个ThreadLocal.get()后:

    1.查找当前线程是否具有ThreadLocalMap,有进入2,没有进入3.

    2.在Map中以该ThreadLocal对象为Key查找Value并返回(涉及到线性探测表的查找)。

    3.以该ThreadLocal对象为Key,initialValue方法中返回值作为Value作为一个Entry,为当前线程初始化一个ThreadLocalMap。

    set方法

    ThreadLocal的set方法为当前线程设置数据。

    public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }

    依旧是和Map相关的,我们看一下ThreadLocalMap的set操作,这里依旧涉及到了线性探测表这个数据结构的set操作,我们就不具体展开了。

    优势

    这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能,据说性能的提升不是一点两点(没有亲测)当Thread销毁之后,其持有的ThreadLocalMap也就随之销毁了,能减少内存使用量。

    Android中的ThreadLocal

    与jdk1.8中的略有不同,下面粗略地看一下

    静态内部类Values

    底层也是维护了一个数组

    static class Values { /** * Size must always be a power of 2. */ private static final int INITIAL_SIZE = 16; /** * Placeholder for deleted entries. */ private static final Object TOMBSTONE = new Object(); /** * Map entries. Contains alternating keys (ThreadLocal) and values. * The length is always a power of 2. */ private Object[] table; ..... }

    看一下它的put方法,没有具体细看,但是注意到key和value是前后放入table数组中的:

    void put(ThreadLocal<?> key, Object value) { cleanUp(); // Keep track of first tombstone. That's where we want to go back // and add an entry if necessary. int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }

    同样,在Thread类中,维护一个Values成员:

    ThreadLocal.Values localValues;

    set方法

    根据当前线程去获取其Values,如果values为空,为values初始化,否则以该ThreadLocal实例为key,传入参数为value,前后一起放入数组中。

    public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); } /** * Gets Values instance for this thread and variable type. */ Values values(Thread current) { return current.localValues; } /** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values(); }

    get方法

    也是获取到当前Thread的Values对象,如果以当前ThreadLocal对象为key找到了对应的value则返回,否则调用getAfterMiss方法,这个方法没有细看,应该也是使用了线性探测法去找key。

    @SuppressWarnings("unchecked") public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); }

    对于ThreadLocal的总结请看开头。

    转载请注明原文地址: https://ju.6miu.com/read-18983.html

    最新回复(0)