ThreadLocal源码解读

ThreadLocal的三个理论基础

1. 每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象,ThreadLocal类中定义了静态类ThreadLocalMap,

静态类ThreadLocalMap中定义了Entry结构存储

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

2. 每个ThreadLocal对象都有一个循环计数器

3. ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据根据第二点中循环计数器取得一个特定value值

两个数学问题

1. ThreadLocal.ThreadLocalMap规定了table的大小必须是2的N次幂

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

计算机处理位运算的效率比数学运算要高,例如ThreadLocalMap中获取Entry对象的方法

 private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);//1
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

上面第一行代码,通过与运算获取到Entry在数组中的索引

如果使用%的话,table.length=16,存在一个数字23,23%16=7,如果转为上面的二进制运算的话:

23                 -> 00010111 

&

table-1 = 15 -> 00001111

result: 00000111 就是十进制的 7 ,效率更高

2. 对于上面取模获取在table中索引位置时候,threadLocalhashCode源码如下:

private final int threadLocalHashCode = nextHashCode();
 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 private static final int HASH_INCREMENT = 0x61c88647;

length 为 16 和 32 时候,取模生成的值


通过取模方式获取索引的时候,每次都会在原来的threadLocalHashCode的基础上加上0x61c88647,这样的结果是生成的hash值分散,而且在length扩容为,2的n次幂,之后,生成的hash值会和前面扩容前的值一致,这就保证了threadLocalHashCode可以从任何地方开始。

set(T value)

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

1. 获取当前线程

2. 获取当前线程的ThreadLocalMap,不为null,就往里面设值

3. 否则,就去创建ThreadLocalMap,并设值

看下,第三步的源码:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

创建一个Entry数组,将初始值放入,取模运算后的索引位置,并且置entry数量为1,而且从ThreadLocalMap中看出并没有next节点,也就是ThreadLocalMap不是类似于hashmap的链表结构,而是开地址法,每次递增一个值,取模运算计算索引存放元素。这样的结果就是设置同一个value放到table中的位置会不一样的

接着,看下,ThreadLocalMap的设值源码:

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

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

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

1. 取模运算获取到table数组的索引位置,从这个索引位置开始遍历到数组最后

2. 根据获取每个Entry的ThreadLocal引用,对于Entry它是个弱引用

static class Entry extends WeakReference<ThreadLocal> {

,获取到ThreadLocal

3. 如果key和k是指向同一个ThreadLocal,那么就将值设置到这个Entry上,返回

4. 不是同一个ThreadLocal,在判断下位置上的ThreadLocal是不是空的,因为Entry是弱引用,有可能这个ThreadLocal已经被垃圾回收了,如果ThreadLocal是空的,会去轮询找到下一个不为null的entry,将值放在这个entry,并且会去将过期的entry删除

5. 如果上面都没有返回的话,将entry数量加1 ,在索引位置设置一个新的Entry

get()

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();
    }
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);
        }
 private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

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

1. 获取到当前线程绑定的ThreadLocalMap

2. 如果ThreadLocalMap不为空

  * 如果根据index能直接找到Entry,并且不为null,直接返回这个Entry

  * 否则,继续向下遍历,找到下一个不为null的entry,返回这个entry,并在轮询过程中将过期的entry删除

3. 如果ThreadLocalMap为空

给当前线程创建一个ThreadLocalMap,并设置初值

remove()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
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;
                }
            }
        }

1 . 找到线程绑定的ThreadLocalMap,并且找到对应的entry,删除掉这个entry

总结

1. ThreadLocal不需要键值,因为ThreadLocalMap不是通过链表实现,而是通过开地址法实现的

2. 设置值的时候,如果index位置找到entry,并且key(这个key指的是ThreadLocal引用)是同一个,进行覆盖,否则向下找到一个不为null的entry,并设值

3. 查找数据的时候,如果能够从index找到entry,直接就返回了,否则就向下继续找到下一个不为null的entry,返回

4. 如果需要向ThreadLocal中存放不同类型的数据,需要定义多个ThreadLocal

参考博客:

http://www.cnblogs.com/xrq730/p/4854813.html












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
ThreadLocal是Java中的一个线程本地变量,它提供了一种线程安全的方式来存储每个线程的局部变量。ThreadLocal的实现原理是在每个线程中创建一个独立的副本,每个线程都可以访问自己的副本,从而避免了线程安全问题。 ThreadLocal的内部结构主要包括ThreadLocal类和ThreadLocalMap类。ThreadLocal类是一个泛型类,用于存储线程本地变量的值。ThreadLocalMap类是一个自定义的哈希表,用于存储每个线程的ThreadLocal变量副本。 在jdk8之前,ThreadLocalMap是通过自定义的Entry数组来实现的,每个Entry包含一个ThreadLocal对象和对应的值。在jdk8之后,ThreadLocalMap的实现方式发生了变化,它使用了类似于HashMap的Node数组来存储Entry,从而提高了性能。 ThreadLocal内存泄漏问题是指在使用ThreadLocal时,由于没有及时清理ThreadLocal变量,导致线程结束后ThreadLocal变量没有被回收,从而导致内存泄漏。解决ThreadLocal内存泄漏问题的方法是在使用完ThreadLocal变量后,调用remove()方法将其从ThreadLocalMap中删除。 下面是一个简单的示例,演示了如何使用ThreadLocal来存储线程本地变量: ```java public class ThreadLocalDemo { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread t1 = new Thread(() -> { threadLocal.set("Hello from thread 1"); System.out.println(threadLocal.get()); }); Thread t2 = new Thread(() -> { threadLocal.set("Hello from thread 2"); System.out.println(threadLocal.get()); }); t1.start(); t2.start(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值