Android高级进阶

2019-08-10-Handler机制之ThreadLocal

2019-08-10  本文已影响0人  王元

ThreadLocal顾名思义就是线程本地变量

1,ThreadLocal介绍

线程局部变量,就是每个线程都有一个自己独一的变量副本,举个例子,大家就能明白

ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("main");

Thread a = new Thread() {
    @Override
    public void run() {
        super.run();
        tl.set("A");
        System.out.println("a = [" + tl.get() + "]");
    }
};

Thread b = new Thread() {
    @Override
    public void run() {
        super.run();
        tl.set("B");
        System.out.println("b = [" + tl.get() + "]");
    }
};

a.start();
b.start();
System.out.println("main = [" + tl.get() + "]");

上面代码的输出结果是

a = [A]
main = [main]
b = [B]

如上可以看出,虽然不同的线程访问了同一个对象tl,但是它们通过tl获取到的对象却是不同的,也就是说ThreadLocal为每个线程保存了一个副本

2,ThreadLocal的原理

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

从上面的源码可以看出,ThreadLocal.set方法的本质是先通过当前线程获取到当前线程的ThreadLocalMap,然后调用ThreadLocalMap.set(key, value)方法,如果为空的情况下,使用当前线程来创建ThreadLocalMap,如下

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

getMap源码如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

如上可以看出,ThreadLocalMap是线程的属性之一,因此它是和当前线程相关的

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

如上可以看出,尝试从ThreadLocalMap中获取,如果没有获取的,则返回默认null

3,ThreadLocal.ThreadLocalMap原理

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;

    /**
     * The number of entries in the table.
     */
    private int size = 0;

    /**
     * The next size value at which to resize.
     */
    private int threshold; // Default to 0

    /**
     * Set the resize threshold to maintain at worst a 2/3 load factor.
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    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);
    }
}

从上面的源码可以看出,ThreadLocalMap内部仅仅维护了Entry[] table,数组。其中Entry实体中对应的key为弱引用(下文会将为什么会用弱引用),在第一次放入数据时,会初始化数组长度(为16),定义数组扩容阀值(当前默认数组长度的2/3)。

如果key使用强引用,那么当引用ThreadLocal的对象被回收了,但ThreadLocalMap中还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致内存泄漏。

弱引用的key也可能带来一些问题,就是ThreadLocalMap可能有很多null为key的数据,而此时的value依然是被强引用,因此

注意:不管是调用ThreadLocal的set()还是get()方法,都会去清除key==null的数据。

ThreadLocal详细介绍

上一篇 下一篇

猜你喜欢

热点阅读