Android:ThreadLocal源码解析

1、前言

  • 最初看到ThreadLocal这个东西是在Handler消息机制的Looper实例化的时候, 系统把Looper的实例对象保存在ThreadLocal里,当有需要的时候就直接拿出来用,以此保证一个线程只有一个Looper对象;
  • 这篇文章就是对ThreadLocal源码进行解析,记录其工作过程与原理;

2、定义

先来看看系统源码对其的一个简单描述:

/**
 * Implements a thread-local storage, that is, a variable for which each thread
 * has its own value. All threads share the same {@code ThreadLocal} object,
 * but each sees a different value when accessing it, and changes made by one
 * thread do not affect the other threads. The implementation supports
 * {@code null} values.
 *
 * @see java.lang.Thread
 * @author Bob Lee
 */
public class ThreadLocal {
    ···
    ···
}

观察最上面的注释,大概的意思是ThreadLocal是每个线程的成员变量,实现线程本地的存储,所有线程都共享同一个对象,但是访问的时候却具有不同的值,实际就是在不同的线程中提供一份各自的副本,这样线程与线程之间的数据就不会相互影响,是独立存在的;
这句话是什么意思呢,举个栗子:

public class MainActivity extends AppCompatActivity {
    private ThreadLocal mThreadLocal = new ThreadLocal<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mThreadLocal.set("A");
        Log.i("测试","result->"+mThreadLocal.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                mThreadLocal.set("B");
                Log.i("测试","result->"+mThreadLocal.get());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("测试","result->"+mThreadLocal.get());
            }
        }).start();

    }

首先我们定义了一个全局的ThreadLocal对象,然后分别在三个不同的线程中存入不同的值,然后得出的结果是:

07-08 14:01:38.149 19251-19251/com.qinkl.demo I/测试: result->A
07-08 14:01:38.151 19251-19294/com.qinkl.demo I/测试: result->B
07-08 14:01:38.168 19251-19295/com.qinkl.demo I/测试: result->null

在不同的线程中全局变量ThreadLocal的值是都是不同的,通过这个例子很好的证明了系统源码的描述:多个线程共享同一个变量,但是访问的值是不同的,只有一个解释,就是在不同的线程中产生了各自的副本,在进行数据操作的时候,是互相独立的,线程与线程之间不会有影响;
要知道ThreadLocal为什么会产生这样的效果,就要从源码入手,下面我们就开始探究其原理是怎样的。


3、源码解析

首先我们从ThreadLocalset方法开始:

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

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

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

首先是获得当前所在的Thread对象,然后作为参数传入getMap方法,在getMap方法中,返回的是当前线程的以ThreadLocal.ThreadLocalMap声明的threadLocals变量,判断如果这个值不为空的话就直接存入ThreadLocalMap中,如果为空的话就调用createMap方法,该方法实例化了一个ThreadLocalMap对象并且赋值给线程的threadLocals变量,在构造函数里面保存这个值;
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> {
            /** 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对象是ThreadLocal的静态内部类,内部通过一个Entry[]数组存储数据,并且该数组的keyThreadLocal的弱引用,设计的目的是防止Entry[]持有外部类ThreadLocal引用导致不能回收的内存泄露;
每个线程都持有一个自己的ThreadLocalMap,也就是持有自己的独立副本,数据的操作都是在
ThreadLocalMap的数组完成的,所以保证了线程与线程之间不会有影响;
然后再看看ThreadLocalget方法:

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

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

get方法中,主要还是判断当前线程是否存在ThreadLocalMap对象,然后从Entry[]数组中取出该ThreadLocal对应的value值,如果没有的话,在setInitialValue方法中,设置为null的初始值;


4、总结

  • 虽然每个线程都共享同一对象,但是会在线程各自创建自己的副本,该副本是ThreadLocalMap对象;
  • ThreadLocal本质是操作线程中ThreadLocalMap对象,而不是其本身;
  • ThreadLocalMap对象内部维护了Entry[]数组存储数据,并且key为弱引用,目的是防止内存泄漏;

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