并发编程——ThreadLocal原理分析,Looper如何和线程关联

说个特别尴尬的事情,最近刚辞职,上上周去极星面试。由于没有什么准备,加之很多东西很久之前看过都忘了,面试官问道我Looper和Thread是如何关联在一起即Looper.myLooper()如何获取到当前线程的looper的时候,我一时想不起来,竟然说了句“ThreadLocal内部维护了一个table,存储了关于线程和变量的key-value,类似map”,当时就觉得不对劲,面试官也就没有再多问,尴尬不已。不仅仅是这个,关于多线程方面的具体原理,cas,锁的原理,死锁……近几个月忘得一干二净,所以趁现在辞职在家躺着期间,打算把并发编程相关的都总结一下。


之前在我另一篇帖子《再论Handler—Handler事件分发、Handler线程切换原理解析》 中1.3部分有简单介绍Looper内的sThreadLocal 存放了当前线程绑定的Looper,这句话深入探究其实是不正确的,正确的说法是,当前线程的Looper是存放在当前线程的ThreadLocalMap中的。

  • ThreadLocal简介:
/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 */

上面是它简介,ThreadLocal提供了线程本地变量,这些变量是每个线程私有的副本,线程之间互不干扰。

接下来我们通过Looper的两个方法来了解ThreadLocal原理:

  • Looper.myLooper()
  • Looper.preapare()

(Ⅰ) 获取Looper——Looper.myLooper()。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

我们都知道这个方法,可以获取当前线程的Looper,可以看到,调用了ThreadLocal的get方法。

接下来看ThreadLocal的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 getMap(Thread t) {
        return t.threadLocals;
    }

get方法主要逻辑如下:

  • 1.先获取当前执行的线程,然后获取当前线程内部的ThreadLocalMap

我们点进Thread中,看一下threadLocals是什么:

ThreadLocal.ThreadLocalMap threadLocals = null;

它是ThreadLocal的内部类:


 static class ThreadLocalMap {
 
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

}

我们先不要管这个类是哪个类的内部类,这个不是重点。

  • 其内部有一个Entry的内部类,结构和HashMap的内部类Entry很像,只不过这里Key类型已经被指定为ThreadLocal了;
  • ThreadLocalMap内部为Thread维护了一个名为table的Entry[] 数组

是不是很熟悉,这就是一个妥妥的Map结构。

  • 2.以当前ThreadLocal为参数,调用ThreadLocalMap的getEntry方法。

我们再看ThreadLocalMap.Entry e = map.getEntry(this) 这行代码的实现:

 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);
        }
 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

getEntry方法其实很简单,看过HashMap原理(可以参考我的另一篇帖子《HashMap(Java8)原理解析&&HashMap扩容机制》的同学都知道,首先通过key的hash值与table数组length-1位运算得到table下标,即数组中存储的位置下标;然后从table数组中获取到Entry e;再比较Entry中存放的key是否与当前key一致后返回Entry。getEntryAfterMiss方法原理简单,不再赘述。

看到这里,我们其实已经大概明了:

通过ThreadLocal存储的变量,实际上是以ThreadLocal为key在调用者线程内ThreadLocalMap中存储的,在哪个线程通过TreadLocal存储某个变量,就存储到了哪个线程中。**


有取就必须先存,说完myLooper获取looper的方法,我们再看一下Looper是如何存进ThreadLocalMap中的:

(Ⅱ) Looper.prapare()

我们都知道,在子线程创建Looper需要调用Looper.prapare()方法为子线程初始化Looper,我们看一下这个方法

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

这个方法作用很简单,为当前线程初始化一个Looper:

  • 1.如果当前线程(ThreadLocalMap中)已存在,就抛出异常,所以这个方法在同一线程不能连续调用;
  • 2.创建一个Looper实例调用set方法存入。

再看ThreadLocal的set方法:

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

前面看get方法时候,已经知道前两行作用,看后面的作用:

  • 后面如果map不为null,就以当前TreadLocal为key存储我们的Looper实例;
  • 但如果当前线程初次使用ThreadLocal,那么此处就会为null,就会执行createMap方法。

ThreadLocal的createMap方法:

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

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

看上去很简单,就是创建它的实例,并以默认容量初始化内部的table数组,然后以ThreadLocal-Looper的k-v形式存储。
我们再看ThreadLocalMap的set方法:

 	private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            //通过key的hash值与table的lenth-1位运算得到下标
            int i = key.threadLocalHashCode & (len-1);
			//
            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				//如果已经存在当前ThradLocal为key的Entry,就替换value。
                if (k == key) {
                    e.value = value;
                    return;
                }
		
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			// 如果没有存储过当前ThreaLocal为key的Entry,就创建一个新的存储。
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
	
	/**
	* 获取下一个下标
	*/
 	private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

上面关键注释很清楚,原理很简单:

  • 1.如果已存在以当前ThreadLocal的Entry就替换value;
  • 2.如果不存在就创建新的Entry存入。

(Ⅲ)总结

好了,ThreadLocal的几个关键方法已经介绍完,原理非常简单,其他我就不展开介绍。
最后,我们需要明确几个概念:

  • ThreadLocal并不直接存储各个线程变量副本,而是代理线程内部ThreadLocalMap,换言之,各个线程变量的副本由线程内部的ThreadLocalMap存储;

通过ThreadLocal存储的变量,实际上是以ThreadLocal为key在调用者线程内ThreadLocalMap中存储的,在哪个线程通过TreadLocal存储某个变量,就存储到了哪个线程中。**

  • 通过ThreadLocalMap.Entry结构可以知道:每个ThreadLocal只能帮每个线程保存一个变量副本

多个变量可能需要多个ThreadLocal实例帮助保存,或者创建包装对象(实体类)包含多个变量


好了,关于ThreadLocal的介绍到此为止,如有不足,烦请批评指正,非常感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值