ThreadLocal 简析

ThreadLocal 是什么?

ThreadLocal,即线程局部变量,是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获取到存储的数据。首先,我们来看下简单使用吧。

  public class ThreadLocalTest {    
    //线程局部变量
    private static ThreadLocal localNum = new ThreadLocal<>();
    //正常变量
    private int shareNum;

    public int getShareNum() {
        return shareNum;
    }

    public void setShareNum(int shareNum) {
        this.shareNum = shareNum;
    }

    public static void main(String[] args) {

        ThreadLocalTest threadLocal = new ThreadLocalTest();

        new Thread("#1"){
            @Override
            public void run() {
                //获取当前线程 "#1" 线程局部变量
                Integer integer = localNum.get(); //默认值为 null
                System.out.println("#1线程局部变量默认值 localNum:" + integer  + " #1正常变量 shareNum:" + threadLocal.getShareNum());
                //在当前线程修改线程局部变量的值
                localNum.set(5);
                //修改正常变量的值
                threadLocal.setShareNum(3);

                System.out.println("#1 修改后\n" +"线程局部变量的值  localNum:" + localNum.get() +  " 正常变量 shareNum:" + threadLocal.getShareNum());
            }
        }.start();



        new Thread("#2"){
            @Override
            public void run() {
                System.out.println("\n#2 线程局部变量的值 localNum:" + localNum.get() + " 正常变量的值 shareNum"  + threadLocal.getShareNum());
            }
        }.start();
    }

}

输出结果:


ThreadLocal 简析_第1张图片
ThreadLocal演示.png

可以看到,在线程 #1 中一开始 localNum 的值没有赋值,所以为null,shareNum 理所当然为0,而且修改后也输出修改后的值,localNum 的值为5,shareNum 为3。很正常。但是当我们看线程 #2 的时候可以发现,shareNum 为3,但是 localNum的值依然为 null,也就是说没有赋值。我们可以简单地理解为ThreadLocal 是一个 HashMap(事实上并不是,后面我们会讲到),get()方法和 set()方法都是以当前线程为key进行取值和赋值。

源码看一看

首先,先上 ThreadLocal 类的结构图


ThreadLocal 简析_第2张图片
ThreadLocal 类结构图.png

我们挑比较重要的几个方法进去看看。

  • public T 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()方法首先是获得当前的线程,然后通过 getMap(t) 获取到 ThreadLocalMap 的实例,这里我们需要知道,如果在 get()之前没有调用 set()方法的时候,map 是等于 null的,然后返回 setInitialValue,如果之前调用了 set() 方法,则会返回其设置的值。

  • private T 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;
}

protected T initialValue() {
    return null;
}

可以看到 setIniitalValue 首先调用 initialValue 获取初始值,而 initialValue()就更简单了,直接返回了 null,这也解释了我们的实例代码在没有调用 set() 时直接调用 get()获取到的值是 null。当然,如果我们有需要可以派生出 ThreadLocal 的子类然后重写 inititalValue()方法设置我们需要的默认值。

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

set()方法也很简单,就是获取当前线程,然后通过当前线程获取ThreadLocalMap,然后设置 value,如果ThreadLocalMap 为空就创建 ThreadLocalMap 并设置 value。

相信聪明的你也看到了上面这几种方法都是围绕着 TheadLocalMap 来操作的,那么ThreadLocalMap 到底是何方神圣?其实 ThreadLocalMap 是 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;

   //省略部分代码
 }

这里简要说下ThreadLocalMap 的机制,首先是 ThreadLocalMap 的变量是属于 Thread 的内部属性,不同的 Thread 拥有完全不同的 ThreadLocalMap 变量,然后ThreadLocalMap内部又维持了一个 Entry[ ] table 数组,数据的读取都是根据当前 ThreadLocal 的索引进行操作的。

总结

  • ThreadLocal 主要操作方法是 set 方法和 get 方法,它们最后都会转移到ThreadLocalMap 中。
  • 我们可以通过重写 initialValue 方法来定义我们ThreadLocal 返回的默认值,如果不这么做的话默认返回 null。
  • 因为每个 Thread 在进行 ThreadLocal 对象访问的时候,访问的都是各自线程自己的 ThreadLocalMap,所以保证了 Thead 和 Thead 之间的数据访问隔离。
  • ThredLocalMap 内部维护了一个 Entry[ ] 数组,不同的 ThreadLocal 对象操作同一 Thread 时,ThreadLocalMap 在存储时采用当前 ThreadLocal 的实例作为 key 来保持数据访问隔离。

多说两句

在 Android 中,如果我们子线程中直接创建 Handler 是会报错的,这是因为 Handler 的创建时需要获取当前线程的 Looper 的,主线程默置了 Looper。那么 Handler 是如何获取每个线程的 Looper 的呢?答案就是通过 ThreadLocal,有兴趣的同学可以自己去查阅源码。

参考资料

[1] 任玉刚. Android 开发艺术探索[M]. 北京 : 电子工业出版社 , 2015.9.
[2] Mr-YangCheng ThreadLocal的使用规则和源码分析.md

你可能感兴趣的:(ThreadLocal 简析)