ThreadLocal简析
首先先来看一段代码
1 | public class ThreadId { |
然后控制台输出如下:
1 | 1 |
不难发现,不同的线程获取到的threadId
是不一样的,那么为什么会出现这种情况呢,所以需要分析下ThreadLocal
这个类,看看threadId.get()
的逻辑
1 | public T get() { |
第一步获取当前线程实例,然后调用getMap(t)
获取到ThreadLocalMap
实例
1 | ThreadLocalMap getMap(Thread t) { |
getMap(Thread t)
方法直接返回了当前线程实例中的threadLocals
成员变量,继续
1 | ThreadLocal.ThreadLocalMap threadLocals = null; |
其实threadLocals
是ThreadLocal
中的静态内部类ThreadLocalMap
类型,那么此时返回为null
,接着调用return setInitialValue();
1 | private T setInitialValue() { |
此时调用了重写的initialValue()
方法进行nextId
的自增操作,接着调用createMap(t, value);
1 | void createMap(Thread t, T firstValue) { |
此时创建了一个ThreadLocalMap
实例并复制给当前线程的threadLocals
变量,再来看其创建过程
1 | ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { |
这里创建了一个初始长度为INITIAL_CAPACITY
即长度为 16 的Entry
类型数组,然后通过firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
获取到数组下标,创建new Entry(firstKey, firstValue)
赋值到table
的i
位置,size
记录的是数组中被赋值的个数,setThreshold(INITIAL_CAPACITY);
是计算threshold
的大小,即INITIAL_CAPACITY * 2 / 3
,当size
达到这个值时,table
数组将会被扩容。接着我们来看看Entry
1 | static class Entry extends WeakReference<ThreadLocal> { |
Entry
是ThreadLocal
的静态内部类,利用虚引用保存ThreadLocal
实例,另外还保存了value
值。get
方法的流程大致就是如此了,首先会获取当前类中的ThreadLocalMap
类型的threadLocals
成员变量,如果为空则调用初始化方法,并且创建一个ThreadLocalMap
实例赋值给当前线程,然后经过取余计算获取对应数组的位置创建Entry
实例并赋值。ThreadLocal
在Android
中也有应用,在Handler
机制中,当我们调用Looper.prepare();
创建Looper
的时候用到了ThreadLocal
存储。
1 | public static void prepare() { |
从代码中可以看出,一个线程最多只能创建一个Looper
实例,不然会抛出异常,那么我们接下里就来分析下ThreadLocal
的set
方法
1 | public void set(T value) { |
逻辑和get
方法一样先获取当前线程的threadLocals
,如果为空和上面的流程一样,这里就不再讲了,接下来看看map.set(this, value);
1 | private void set(ThreadLocal key, Object value) { |
大致就是先根据key
即ThreadLocal
实例的threadLocalHashCode
获取到需要赋值数组的位置,然后判断是否有实例存在,有则覆盖value
值,没有则创建一个Entry
实例赋值到对应位置,最后判断需不需要resize
…ThreadLocal
在我看来其实就是作用于线程范围的类,适用于不同的线程需要创建不同的数据副本情况,比如说Android
中的Looper
,暂时还未想到在Andriod
实际开发中需要用到ThreadLocal
的业务场景。