ThreadLocal类详解

253 阅读4分钟

Android中Handler中有使用到ThreadLocal相关的知识,本文对ThreadLocal源码做一次解读

ThreadLocal的特性

  1. ThreadLocal是线程本地保存数据的变量,这也是“ThreadLocal”的字面意思。
  2. 同一个ThreadLocal对象,在不同线程中可以保存不同的数据,每个线程都有自己独一份的ThreadLocalMap来保存数据,ThreadLocal对象只不过起到key的作用
  3. 一个线程中可以有多个ThreadLocal对象,但是同一个ThreadLocal只能保存一份值,多次保存会替换之前保存的值
  4. 理论上数据有所在线程独享,但是如果存入的数据被多线程访问,那还是会有并发访问的问题。所以ThreadLocalMap保存的数据并不建议保存共享数据。
  5. 线程销毁后自动释放线程保存的数据。
  6. 线程本地保存数据,提高数据访问的速度。

ThreadLocal内部实现

  • 变量
//相当于ThreadLocal的hash,保存数据到ThreadLocalMap时用于确定存储的位置
private final int threadLocalHashCode = nextHashCode();
//自增长的int值,静态变量,保证不重复
private static AtomicInteger nextHashCode = new AtomicInteger();
//每次threadLocalHashCode增长都要加上这个值,注释写的,为了让哈希码能均匀的分布在2的N次方的数组里
/**
 * The difference between successively generated hash codes - turns
 * implicit sequential thread-local IDs into near-optimally spread
 * multiplicative hash values for power-of-two-sized tables.
 */
private static final int HASH_INCREMENT = 0x61c88647;
/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
  • 方法
//提供默认值,子类继承时可以复写
protected T initialValue() {
    return null;
}
//返回保存在当前线程ThreadLocalMap中的T值,若map为null则返回默认值
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
        }
    return setInitialValue();
}
//保存T到当前线程的ThreadLocalMap中
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
//清除保存的数据
//因为数据本来就是保存在线程中,清除数据也不是必须操作, 但可以加快内存回收
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

上面是ThreadLocal内部基本的几个方法,我们接着来看ThreadLocal怎么好Thread联系起来。

set()方法会调用到createMap(),看一下源码

/**                                                                
 * Create the map associated with a ThreadLocal. Overridden in     
 * InheritableThreadLocal.                                         
 *                                                                 
 * @param t the current thread                                     
 * @param firstValue value for the initial entry of the map        
 * @param map the map to store.                                    
 */ 
 //这里可以很清除的看到,其实是new了ThreadLocalMap,并保存到了Thread中
 //查看Thread的源码也可以看到,内部保存了一个ThreadLocalMap变量                                                
void createMap(Thread t, T firstValue) {                           
    t.threadLocals = new ThreadLocalMap(this, firstValue);         
}                                                                  
  • 内部类

ThreadLocal内部定义了ThreadLocalMap类,并不是一个map类,内部使用WeakReference来操作数据,保存、获取等数据操作的实现都在这里。

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;
        }
    }
    
 	...                                                                   
 	
 	/**                                                              
     * Get the entry associated with key.  This method               
     * itself handles only the fast path: a direct hit of existing   
     * key. It otherwise relays to getEntryAfterMiss.  This is       
     * designed to maximize performance for direct hits, in part     
     * by making this method readily inlinable.                      
     *                                                               
     * @param  key the thread local object                           
     * @return the entry associated with key, or null if no such     
     */                                                              
    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);                     
    }                                                                   
 
}

看过HashMap源码实现的同学看到这里肯定不会陌生,这就相当于是HashMap的实现,只不过这里取出来的数据不是链接,是Entry对象。

其他方法不再赘述,有兴趣的同学可以自行查看HashMap与ThreadLocalMap源码做比较。

没有看过HashMap源码的同学也可以看这篇HashMap源码解析

代码分析

  1. 通过上面的代码可以看到,每次set()以及get()都会获取到当前线程再做后续的操作,保证所有的数据跟当前的线程相关联,不干扰其他线程。

  2. 在 set()和 get() 我们可以看到,保存数据的ThreadLocalMap对象,其实是保存在当前线程中,并不是保存在ThreadLocal中,也就解释了,为什么不同的线程可以取到自己线程中保存的值,ThreadLocal只是起到了key的左右。

  3. 数据真正保存在ThreadLocalMap中,再看一下getEntry(),通过threadLocal的threadLocalHashCode来确定返回值,这个就相当于threadLocal为key获取到之前保存的数据。同时每次new ThreadLocal对象时,threadLocalHashCode做了相对性的自增,这样可以保证多个threadLocal对象能被正确区分。

  4. ThreadLocal保存的数据对象是T类型,意味着我们要存多个数据时可以保存Map或者List等类型,不用没保存一个数据就new一个对象。