ThreadLocal源码解析(执行流程图)

ThreadLocal 源码解析与说明

​ 这里先用图表示一下ThreadLocal在thread中的主要数据结构
在这里插入图片描述

Public T 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();
    }
  1. 通过Thread.currentThread().threadLocals来获取threadLocalMap

  2. 当map 不为空的时候,通过map的getEntry(ThreadLocal threadLocal)方法获得Entry,然后返回entry.value所保存的泛型变量T;

  3. 当map为空的时候则执行setInitialValue()方法,这个方法初始化map的同时在map中创建Entry保存泛型变量T, 然后返回值。

上述过程中存在两个问题:

  1. threadLocalMap的getEntry方法的实现过程
  2. setInitialValue() 的实现过程,该过程比较简单,上述流程图可以解释

于是下面谈论threadLocalMap的getEntry方法:

在这里插入图片描述

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);
        }
  1. 通过threadLocal变量的唯一的hashcode来计算entry在map的Entry[]中的位置。
  2. 若数组的该位置已存在entry,并且entry所包含的key等于threadLocal,那么返回该entry。
  3. 否则执行getEntryAfterMiss(key, i, e)方法,在该方法内,依次遍历该位置之后的元素,key等于threadLocal则返回value,直到entry为空,返回空。

在上述的过程中,threadLocal调用的都是threadLocalMap中的方法,下面则说明map的set()方法:

set()过程:

  1. 根据threadLocalHashCode 计算entry的位置
  2. 从该位置开始向后遍历:找到第一个空位置,创建新的entry存入,如果有key等于threadLocal的entry,则替换其value,如果存在key为null,则用新的key与value覆盖之,并清除旧的数据。
  3. 检查entry数量是否超过阈值,超过则进行再哈希:
    1. 清理旧数据
    2. 如果此时的entry数目任然超过0.75*length, 就将数组扩大两倍,类似于hashmap的扩容方式
    3. 将旧entry重新哈希散列放入数组(threadHashCode & (length-1))
/**
         * 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;
            //根据唯一的哈希值计算entry位置
            int i = key.threadLocalHashCode & (len-1);

            //在该位置及后续位置中遍历,找到一个空位,或者有相等key的entry,替换该entry的value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
               
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			
            //在找到的空位中填入新的entry
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //如果超过阀值,就需要再哈希了(再哈希中实现了扩容)
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

cleanSomeSlots(i, sz) 方法,启发式地扫描一些单元格,寻找陈旧的条目,并将这些垃圾删除掉。

下面是rehash()方法

		/**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() {
            // 清理一次陈旧数据
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            //清理完陈旧数据,如果>= 3/4阀值,就执行扩容,避免迟滞
            if (size >= threshold - threshold / 4)
                resize();
        }

		/**
         * Double the capacity of the table.
         * 把table扩容2倍,并把老数据重新哈希散列进新table
         */

        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC  把value也置null,有助于GC回收对象
                    } else {// 如果key!=null
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null) // 如果这个位置已使用
                            h = nextIndex(h, newLen);// 线性往后查询,直到找到一个没有使用的位置,h递增
                        newTab[h] = e;//在第一个空节点上塞入Entry e
                        count++;
                    }
                }
            }

            setThreshold(newLen);// 设置新的阈值(实际set方法用了2/3的newLen作为阈值)
            size = count;// 设置ThreadLocalMap的元素个数
            table = newTab;// 把新table赋值给ThreadLocalMap的Entry[] table
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }


		/**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
		//删除陈旧entry的核心方法
         private int expungeStaleEntry(int staleSlot) {
             Entry[] tab = table;
             int len = tab.length;
 
             
             tab[staleSlot].value = null;//删除value
             tab[staleSlot] = null;//删除entry
             size--;//map的size自减
 
             // 遍历指定删除节点,所有后续节点
             Entry e;
             int i;
             for (i = nextIndex(staleSlot, len);
                  (e = tab[i]) != null;
                  i = nextIndex(i, len)) {
                 ThreadLocal<?> k = e.get();
                 if (k == null) {//key为null,执行删除操作
                     e.value = null;
                     tab[i] = null;
                     size--;
                 } else {//key不为null,重新计算下标
                     int h = k.threadLocalHashCode & (len - 1);
                     if (h != i) {//如果不在同一个位置
                         tab[i] = null;//把老位置的entry置null(删除)
 
                         // 从h开始往后遍历,一直到找到空为止,插入
                         while (tab[h] != null)
                             h = nextIndex(h, len);
                         tab[h] = e;
                     }
                 }
             }
             return i;
         }

最后是threadLoalMap的remove()方法:

/**
 * Remove the entry for key.
 */
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    //计算entry位置
    int i = key.threadLocalHashCode & (len-1);
    //遍历方式与set方法一样
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
        	//clear方法很简单
            e.clear();//public void clear() {this.referent = null;}
            expungeStaleEntry(i);
            return;
        }
    }
}

使用场景(有待补充)

ThreadLocal更像是一个线程级别的HashMap

在上层中(例如controller)我们把需要传送的对象(session)放在ThreadLocal中,底层(例如Dao)需要调用时候,就从ThreadLocal中拿,这样就保证了每个线程中使用到的对象都是和当前线程相关的。

  • 数据库连接池中,将Connection对象方法Threadlocal中,保证线程安全与事务;
  • 同理,在实现redis事务中,也可以用Threadlocal保存Multi对象;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal源码Java中一个关键的类,它提供了一种在多线程环境下实现线程本地变量的机制。在JDK 8之前和之后,ThreadLocal的内部结构有所变化。ThreadLocal源码分为两部分:ThreadLocal类和ThreadLocalMap类。 ThreadLocal类是一个泛型类,它包含了两个核心方法:set()和get()。set()方法用于将一个值与当前线程关联起来,get()方法用于获取当前线程关联的值。 ThreadLocalMap类是ThreadLocal的内部类,它用于存储每个线程的本地变量。在JDK 8之前,ThreadLocalMap是通过线性探测法解决哈希冲突的,每个ThreadLocal对象都对应一个Entry对象,Entry对象包含了ThreadLocal对象和与之关联的值[2]。 在JDK 8之后,ThreadLocalMap的实现方式发生了改变。使用了类似于HashMap的方式,采用了分段锁的机制来提高并发性能。每个线程维护一个ThreadLocalMap对象,其中的Entry对象也是采用链表的形式来解决哈希冲突。 总结起来,ThreadLocal源码主要由ThreadLocal类和ThreadLocalMap类组成。ThreadLocal类提供了set()和get()方法来管理线程本地变量,而ThreadLocalMap类则负责存储每个线程的本地变量,并解决哈希冲突的问题。 史上最全ThreadLocal 详解 ThreadLocal源码分析_02 内核(ThreadLocalMap) 【JDK源码】线程系列之ThreadLocal 深挖ThreadLocal ThreadLocal原理及内存泄露预防 ThreadLocal原理详解——终于弄明白了ThreadLocal ThreadLocal使用与原理 史上最全ThreadLocal 详解。 ThreadLocal源码分析,主要有ThreadLocal源码以及ThreadLocal的内部结构在jdk8前后的变化。 使用方式非常简单,核心就两个方法set/get public class TestThreadLocal { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { threadLocal.set("aaa"); Thread.sleep(500); System.out.println("threadA:" threadLocal.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { threadLocal.set("bbb"); System.out.println("threadB:" threadLocal.get()); } }).start(); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值