美文网首页
ThreadLocal解析

ThreadLocal解析

作者: izheer | 来源:发表于2021-03-25 10:31 被阅读0次

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

相关文章

  • 线程中的ThreadLocal

    ThreadLocal解析

  • JDK ThreadLocal解析

    Java ThreadLocal解析 ThreadLocal 线程本地变量, 线程私有, 在 Thread 类中用...

  • ThreadLocal 深度解析

    一.对ThreadLocal的理解二.深入解析ThreadLocal类三.ThreadLocal的应用场景 对Th...

  • ThreadLocal解析

    前言 刚看过EventBus和AndroidEventBus的源码, 发现里面都有用到ThreadLocal, 那...

  • ThreadLocal解析

    前言 在各大公司招聘笔试和面试题题中,都遇到了很多ThreadLocal的问题,最近博主在面试的时候也被两次问到过...

  • ThreadLocal解析

    一、简介 首先我们需要知道Thread.currentThread()获取当前线程对象,同一个线程每次获取的都是同...

  • ThreadLocal解析

    前言 我们都知道ThreadLocal用于为每个线程存储自己的变量值,起到线程间隔离的作用,那么它到底是怎么运行的...

  • ThreadLocal解析

    ThreadLocal,顾名思义,肯定是与Thread类有关系的,所以先从Thread类入手。 Thread和Th...

  • ThreadLocal解析

    原理 产生线程安全问题的根源在于多线程之间的数据共享。如果没有数据共享,就没有多线程并发安全问题。ThreadLo...

  • ThreadLocal解析

    我们都知道,ThreadLocal在多线程中使用,即多线程共享一个变量,正常情况下线程安全。其主要原理是内部持有的...

网友评论

      本文标题:ThreadLocal解析

      本文链接:https://www.haomeiwen.com/subject/zgdlhltx.html