ThreadLocal源码浅析

概述

这里是基于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> {
            /** 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的总结请看开头。


女票良心美代,只做正品的搬运工。如果你觉得这篇文章帮助到了你一点点,扫码支持一下吧^ ^


ThreadLocal源码浅析_第1张图片
二维码

你可能感兴趣的:(ThreadLocal源码浅析)