线程基础知识08- ThreadLocal基础总结

     ThreadLocal在平时开发中是用的比较多的一个类,主要用于存储线程的数据。下面我对ThreadLocal进行一下总结;

     dreamcatcher-cx大佬对ThreadLocal的设计总结,写的比较深刻,很有帮助。

主要使用场景:

  • 多数据源切换,记录当前线程访问的数据源

  • spring框架事务管理,用于存放事务数据;

  • springsecurity安全框架,用于存储用户登录信息;

除了以上还有很多,不限于以上。

解读源码

数据存储ThreadLockMap

  • 通过Entry数组进行存储的;

  • Entry继承Reference类,是弱关联的类,当ThreadLocal的实例为空时,GC会快速收回;一般用于处理数据量较大且维持时间较短的业务。

  • Entry节点存储的是ThreadLocal对象和对象值

//是通过Entry数组进行存储的 
private Entry[] table;

static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

set 方法

  • 存放ThreadLocal<?>的是弱关联的容器,GC会等存储满的时候处理;

  • 存放的过程中,会对key判断,调整数组位置;

 private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);//获取存放的位置

            /**
             * 当i位置刚好已经存储了数据
             */
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {//向i+1<len遍历
                ThreadLocal<?> k = e.get();//获取存入的ThreadLock
                if (k == key) {//判断是否一致,如果一致,替换值并返回
                    e.value = value;
                    return;
                }

                if (k == null) {//如果i节点获取到的ThreadLocal为空的话               
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //没有存储数据的情况
            tab[i] = new Entry(key, value);//新增信的节点
            int sz = ++size;
            /**
             * 判断是不是要进行扩容
             * Entry数组的扩容是2倍扩容的;            
             */
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }



     private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // 判断i之前的非空节点的位置
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            //从staleSlot又向后循环,找到非空节点,查看是否又相同key
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                /**
                 * 判断和查询的实例是否同一个,如果key相同,则和staleSlot节点进行替换位置   
                 */ 
                  if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);//这里进行rehash
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // 如果key没找到那就在staleSlot位置插入一个新节点
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // 判断是不是要重新尽心hash
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

get方法

  • 获取存储的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
                /**
                 * 从i位置往后逐个遍历查找如果找到了就返回,没找到返回null;
                 */   
                return getEntryAfterMiss(key, i, e);
        }

remove方法

  • 查找对应数据节点进行删除就行
  private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

ThreadLocal类的方法

set方法数据存储

  • 首次存储,新建一个ThreadLockMap,以当前ThreaLock为key;

  • 如果线程的存储区域已经初始化过,则更新存储区域中的数据;

public void set(T value) {
        Thread t = Thread.currentThread();// 获取当前线程
        ThreadLocalMap map = getMap(t);//获取存储数据的MAP
        if (map != null)//判断存储数据的map是否为空,如果为空则创建map,如果不为空,则存入数据
            map.set(this, value);
        else
            createMap(t, value);//如果没有就创建一个,当前Thread
    }
//获取当前线程的值存储区域
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; //线程的成员变量
    }
//给当前线程创建一个新的ThreadLocalMap,并存储以当前ThreaLocal实例为key的键值对。
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

get方法

  • 如果在访问之前没有存储任何数据,则对ThreadLockMap进行初始化,绑定当前线程。并进行初始值的创建,和当前ThreadLock实例进行绑定,kv存储

  • 如果获取到之前存储的值,则进行返回存入的值;

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取当前线程的存储map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//获取当前的ThreadLock的节点,具体上面有介绍
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//如果不存在则进行初始化
    }


//进行一些初始化操作
private T setInitialValue() {
        T value = initialValue();//ThreaLock提供的一个可继承的方法,进行初始化操作
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取当前线程的
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

remove 方法

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)//在存储的map不为空的情况下,移除当前ThreadLock实例
             m.remove(this);
     }

总结

注意点:

1. ThreadLocalMap存储的key值为当前的ThreadLocal实例,所以一般要求ThreadLocal是单例的

2. 单个线程可以有多个ThreadLocal实例,存储数据。

总结分析:

1.ThreadLocal的设计思路:

  • 原理:相当给当前线程的创建独有数据域,方便当前线程访问。这样避免了多线程环境中,访问当前线程锁的相关问题

  • 之所以使用ThreadLocalMap进行存储,而不用HashMap,上面源码分析说了,源于Reference类的特性。

    • 弱引用,当对象无用的时候,会快速被GC回收。符合线程的特点,一般一个线程时间不会很长,当伴随大量数据时能快速回收

    • 当前线程结束的时候,要通过调用ThreadLocal的remove方法,及时将ThreadLocalMap中的对应实例key设置为空,方便GC快速回收

  • ThreadLocal操作更有优势,下面我具体分析以下

为什么用ThreadLocal类直接操作存储数据 ?

1.简化代码:

试想一下上面的源码,如果直接从Thread中存放数据,
首先,要用Thread.currentThread()获取当前线程,
然后,再通过当前线程thread获取存储的Map
再根据key获取对那个的操作

从上面的源码可以看出,这些操作细节,都已经在ThreadLocal类中进行了隐藏;
1.避免每次使用时的重复代码;
2.而使用ThreadLocal实例获取数据,相当于拿key直接获取数据,隐藏了获取细节

2.限制和统一管理存储值

1. 内部的Entry数组,进行了泛型约束,避免了直接在Thread类中Map操作key的不规范性;

2. 避免了开发人员直接操作当前线程,而使用“变量副本”进行操作

原文地址:https://www.cnblogs.com/perferect/p/13646131.html