我待ThreadLocal如初恋,ThreadLocal虐我千百遍。但这一次,要彻底搞懂ThreadLocal

回忆一下面试名局面:

面试官:Handler如何做到与线程绑定的?

我:每个Handler只需一个相关联的Looper,线程绑定要害点正是Looper中和其内部的ThreadLocal类型的变量sThreadLocal。经过ThreadLocal完成了Looper和线程的绑定。

问:ThreadLocal原理是什么?

我心里:如同有个map,有个泛型,死活想不起啊。。。

10S后

我心里:我嘞个擦,ThreadLocal究竟是什么鬼啊,为什么会有这么奇!怪!的!类!啊

万万没想到,又被问ThreadLocal了

翻开源码开干吧

一.ThreadLocal源码注释及使用办法

看看第一段注释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).

此类提供线程局部变量。这些变量与正常变量的不同之处在于,访问一个变量的每个线程(经过其 get 或 set 办法)都有自己的、独立初始化的变量副本。ThreadLocal 实例一般是类中的私有静态字段,并将其状况与线程相关联(例如,用户 ID 或业务 ID)。

大约意思就是:ThreadLocal这个变量,望文生义,就是用来管理(设置和获取)线程的局部变量的一个东西类。你要用这个ThreadLocal的时分,最好就是把这个界说成一个私有的静态变量。在不同的线程中,调用这个静态变量的set和get办法,都能设置或者获取属于自己的线程局部变量。当然这个变量的类型也是有限制的,就是ThreadLocal的泛型类型。

这儿有个要害点:ThreadLocal一般界说成静态变量。

官方怕你不会用,注释第二段就给了一个比如

  import java.util.concurrent.atomic.AtomicInteger;
  public class ThreadId {
      // 原子整型,表明下一个要分配给下一个线程的id(这个线程ID是咱们自己界说的,跟线程自身id不同)
      // Atomic integer containing the next thread ID to be assigned
      private static final AtomicInteger nextId = new AtomicInteger(0);
      // 重点:ThreadLocal变量,包括咱们给每个线程界说的id(这儿的ThreadLocal目标是个私有静态变量)
      // Thread local variable containing each thread's ID
      private static final ThreadLocal<Integer> threadId =
          new ThreadLocal<Integer>() {
              @Override protected Integer initialValue() {
                  // 初始化办法,当调用get办法第一次获取线程id的时分会初始化。
                  return nextId.getAndIncrement();
          }
      };
      // 回来当时线程的仅有id。假如没有的话就给他分配一个。
      // Returns the current thread's unique ID, assigning it if necessary
      public static int get() {
          return threadId.get();
      }
  }

上面比如里的ThreadId用起来是啥作用呢?我写个demo跑一下

val thread1 = Thread {
    Log.i("threadLocal", "thread1 :" + ThreadId.get())
    Log.i("threadLocal", "thread1 :" + ThreadId.get())
}
val thread2 = Thread {
    Log.i("threadLocal", "thread2 :" + ThreadId.get())
    Log.i("threadLocal", "thread2 :" + ThreadId.get())
}
thread1.start()
thread2.start()
//  打印成果如下
//  thread1 :0
//  thread2 :1
//  thread2 :1
//  thread1 :0

经过ThreadId这个东西类,可以很便利的去获取到自界说的线程id。

代码注释中会高频地呈现一下两个名词:

ThreadLocal实例(ThreadLocal instances)

线程局部变量副本(copy of a thread-local variable)

这个局部变量副本很简单让人利诱和误解。为了便利了解,我把ThreadLocal实例(也就是界说的那个静态变量)了解为 “ThreadLocal东西目标” ,把线程局部变量副本就叫做 “线程局部变量” 。线程中调用ThreadLocal东西目标可以便利地存取指定类型的变量。

第三段注释:

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只需线程处于活动状况并且 ThreadLocal 实例可访问,每个线程都对他的线程局部变量副本持有隐式引证。当线程销毁后,线程局部变量实例的一切副本都将或许被废物收回掉(除非存在对这些副本的其他引证)。

大约意思就是:

只需线程还活着,ThreadLocal目标还能访问,每个线程对他的线程局部变量就有引证。线程销毁之后,线程局部变量就会成为废物收回的目标。

二.要害办法get和set

ThreadLocal中有get和set办法,分别用来获取和设置当时线程的“线程局部变量”。

1.set

先看看set办法和其相关的办法,这三个办法都是界说在ThreadLocal中:

public void set(T value) {
    // 获取当时线程
    Thread t = Thread.currentThread();
    // 获取线程的成员变量threadLocals,类型为ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 假如map不为null,增加键值对 “ThreadLocal东西目标:value”
        map.set(this, value);
    } else {
        // 假如为null,给map初始化并增加键值对
        createMap(t, value);
    }
}
/**
* 获取线程的threadLocals成员变量,类型为ThreadLocal.ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
/**
* 为线程t创建ThreadLocalMap目标,构造函数参数为ThreadLocal东西目标,以及初始值
*/
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

阐明一下,这儿呈现了一个新的类ThreadLocalMap,它是界说在ThreadLocal中的静态内部类,内部是一个哈希表,保存键值对,只不过保存键值对的key有必要是ThreadLocal类型的,值就是上面说的“线程局部变量”。

ThreadLocalMap就是保存“ThreadLocal:Object”的哈希表。

万万没想到,又被问ThreadLocal了

那我又问了,Thread的ThreadLocalMap的成员变量是什么样的,就下面这样的,不只有还有两个,咱们用的是第一个。

public class Thread implements Runnable {
    // ... 省掉
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    // ... 省掉
}

到这儿要总结一下了,为了便利了解,用十分不专业的uml图,看看Thread、ThreadLocal、ThreadLocalMap他们三个的引证关系:

万万没想到,又被问ThreadLocal了

ThreadLocal以自身为key,从调用线程中获取value。

要害点来了:ThreadLocal乃真谋士,以身为饵(key),引天下入局。

2.get

继续看get办法

    public T get() {
        // 获取当时线程
        Thread t = Thread.currentThread();
        // 获取线程的成员变量threadLocals,类型为ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 获取当时ThreadLocal东西类为key对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // map为null则回来初始值,默认初始值为null
        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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
    protected T initialValue() {
        return null;
    }

set看懂的话,get就很简单了解,仅有要注意的是获取初始值initialValue这个办法或许在实际使用时或许被重写。

三.看看Looper怎么做的

Looper中用到ThreadLocal的地方全都在这儿了:

public final class Looper {
    // 此处省掉
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    // 此处省掉
    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));
    }
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    // 此处省掉
}

sThreadLocal为静态变量。

prepare办法中为当时线程设置Looper。

三.总结

这个ThreadLocal写法的确剑走偏锋,惯例思维简单记混乱,不过记住两个要害点就可以把整个逻辑回想起来:

1.ThreadLocal一般界说成静态变量

2.ThreadLocal以自身为key,从当时Thread的map中获取value

好了,至此共享结束。

此时我的心情是:面试官快来问我ThreadLocal的问题吧,我已经刻不容缓地想答复了。

万万没想到,又被问ThreadLocal了