ThreadLocal解析

1、ThreadLocal起到数据隔离的作用,相当于为每个线程提供一个变量副本,变量数据对别的线程而言是相对隔离的。在保存多线程环境下,防止自己的变量被其他线程纂改。

使用方式:线程中初始化一个ThreadLocal对象,后续只要改线程在remove之前调用get()方法取值即可。

ThreadLocal<String> localName = new ThreadLocal();
localName.set("Alice");
String name = localName.get();
localName.remove();

从以上的使用可以看出,ThreadLocal的使用主要涉及的接口:

void set(Object value)设置当前线程的局部变量值;

Object get() 返回当前线程所对应的局部变量值;

void remove() 删除当前线程局部变量的值,为了减少内存的占用。注意:当线程结束后,该线程的布局变量将自动被垃圾回收,所以显示调用该方法清楚并不是必须的,只是可以加快内存的回收速度。

2、常见的场景:
其中Spring中的事务就是采用ThreadLocal方式实现。Spring采用ThreadLocal的方式,来保存单个线程中的数据库操作使用的是同一个数据库连接。同时采用这种方式可以使业务层使用事务时不需要感知并管理Connection对象,通过传播级别巧妙的管理多个事务配置之间的切换、挂起和恢复。

在Android中,Looper类就是利用了ThreadLocal的特性,保证每个线程只存在一个Looper对象。

Looper的源码如下:

// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

3、接下来看下 ThreadLocal的源码解析

Thread类中有一个ThreadLocal.ThreadLocalMap 类型的属性变量 threadLocals

ThreadLocal.ThreadLocalMap threadLocals = null; 而ThreadLocal的静态内部类ThreadLocalMap类 中是由 Entry 类型数组table保存每一个数值,

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

其中, Entry 是包装成ThreadLocal<?>的弱引用static class Entry extends WeakReference<ThreadLocal<?>>

下面我们看下ThreadLocal类的 set(T value)方法源码

 public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取ThreadLocalMap对象
        if (map != null)
            map.set(this, value);//设置值
        else
            createMap(t, value);//创建Map对象
    }

以上set源码很简单明了,就是先获取当前现在的ThreadLocalMap对象,若存在直接设置,若不存在先创建Map对象,再设置。

然后我们先看下新建ThreadLocal类的 createMap(t, value);方法创建Map对象的源码,其中,this是当前的ThreadLocal对象

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

重点是ThreadLocalMap的构造方法源码:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY]; //新建Entry数组,初始大小是INITIAL_CAPACITY = 16
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);// 根据斐波拉契散列获取下标值
            table[i] = new Entry(firstKey, firstValue); //构建Entry对象,放置数组中
            size = 1;
            setThreshold(INITIAL_CAPACITY); //设置扩容因子
        }

其中
// 斐波拉契散列魔数
private final int threadLocalHashCode = nextHashCode();

/**
 * The next hash code to be given out. Updated atomically. Starts at
 * zero.
 */
private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;// 魔数

/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
   return nextHashCode.getAndAdd(HASH_INCREMENT); // 通过 AtomicInteger 类实现原子递增
}

再来看下 ThreadLocalMap 对象的set方法

 /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be 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;
            int i = key.threadLocalHashCode & (len-1); // 通过斐波拉契散列获取线程应该所在的索引下标



            // 获取数组中该下标的值,判断是否已经存在元素
            // nextIndex:获取下一个下标,通过三元表达式实现首尾相连

            // 如果Entry为空,说明当前 TheadLocal 实例不存在,同时也不可能存在在其他索引位置,分析如下:
            // 首先GC回收失效只对ThreadLocal进行清理,不会对Entry进行清理,所以Entry只会为空,不会为null
            // 此时如果当前 ThreadLocal 在此Entry有效时插入,则会顺序寻找下一个槽位。
            // 此时需要做的就是从其他槽位找到当前ThreadLocal,并把当前 ThreadLocal 拉回到正确槽位
    
            // 其次,如果当前线程在插入 ThreadLocal 时所在槽位已经被占用,此时该 ThreadLocal 占用其他槽位,
            // 之后如果占用该槽位的 ThreadLocal 被程序清理,注意程序清理会直接将Entry置null
            // 清理完成后,程序会重新向下整理一次元素的下标位置,尽量将元素放在hash算出的hash位置

            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
            
                ThreadLocal<?> k = e.get();
                
                //如果位置i不为空,这个Entry对象的key正好是即将设置的key,那么就刷新Entry中的value;
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果当前位置是空的,就初始化一个Entry对象放在位置i上
                if (k == null) {
                    replaceStaleEntry(key, value, i);// 清理并存储当前节点
                    return;
                }
                //如果位置i的不为空,而且key不等于entry,那就找下一个空位置,直到为空为止。
            }
            
            // 如果当前下标未被占用,则直接初始化为当前 ThreadLocal
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

再看下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();// map为空或者entry为空,构建并填充初值
    }
    
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;
    }

ThreadLocalMap的getEntry(ThreadLocal<?> key)源码:从ThreadLocalMap中获取Entry对象

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); //key不为当前ThreadLocal,则说明已经被占用了,向后继续寻找
        }
        
        
  private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            
            // e不为空,说明槽点被占用,继续向后寻找
            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;
        }

ThreadLocal的remove源码

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);//map存在移除当前的ThreadLocal
     }

再看下ThreadLocalMap的remove

private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            // 从下标位置开始,向下寻找,获取指定的 ThreadLocal后直接移除
            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

类之间的关系


ThreadLocal的类图.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容