ThreadLocal原理详解

        ThreadLocal是多线程情况下常用的一种线程隔离的变量,今天我们就来了解一下它的底层是如何实现的

 

目录

一. 简介

二. 基本使用方式

三. 原理解析

        3.1 ThreadLocalMap

        3.2 get()方法

        3.3 set()方法

         3.4 架构图


一. 简介

        ThreadLocal,也称线程变量,每个ThreadLocal对象存储的变量仅仅属于当前线程,对于其他线程而言,无法获取到该变量,是一种线程隔离的变量,适用于一些在每个线程的上下文中,需要频繁地获取该变量,但处于需求或隐私考虑,需要独属于该线程的情况。

        作用:

  • 提供线程独立性:每个线程可以拥有自己的变量副本,不会受到其他线程的影响,确保数据独立和安全。

  • 避免线程同步:通过 ThreadLocal,可以避免使用锁机制来处理线程间的数据共享问题,从而简化并发编程,提升程序的性能。

  • 简化线程上下文传递:在多线程环境中,ThreadLocal 可以隐式地存储线程特有的数据,避免在方法调用中显式传递这些数据,尤其在复杂的调用链中非常实用。

  • 维护线程状态ThreadLocal 可以用于保存与线程生命周期相关的状态信息,例如当前线程处理的事务、数据库连接、用户信息等

        

二. 基本使用方式

        ThreadLocal支持传入泛型

public class Main {
    static ThreadLocal threadLocal = new ThreadLocal();
    static ThreadLocal<String> threadLocal2 = new ThreadLocal();
    public static void main(String[] args) {
        threadLocal.set(1);
        System.out.println(threadLocal.get());
        threadLocal2.set("B");
        System.out.println(threadLocal2.get());
        //演示线程隔离
        new Thread(()->{
            System.out.println(threadLocal.get());
        }).start();
    }
}

        运行结果:

三. 原理解析

        3.1 ThreadLocalMap

        ThreadLocal的线程独立性底层是基于ThreadLocal的一个静态内部类ThreadLocalMap实现的,首先让我们粗略了解一下ThreadLocalMap:

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

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

        /**
         * .......
         */

}  

        可以看到的是ThreadLocalMap的每个节点的key值,即ThreadLocal对象是弱引用,也就是说会在每次gc的时候进行回收,这可能是设计者想要避免OOM问题所使用的特殊设计。

        但是大家有没有发现一个问题;value值仍然是强引用,gc的时候不会回收

        也就是说,如果在程序运行情况下,程序内存中会累积一个又一个线程的ThreadLocalMap的value值的垃圾信息,那么总有一天也会出现OOM问题!

        所以虽然设计者考虑到了使用ThreadLocal时尽量避免OOM问题,但没有完全解决,仍然需要我们每次在使用完成后,调用threadLocal.remove()方法:

        

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
    static class ThreadLocalMap {
        //...

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

        //...
    }

        remove()方法会将ThreadLocalMap所有节点设置为null,这样就能在gc的时候被回收了。

        另外我们可以看到ThreadLocalMap中有一个Entry数组,但是与HashMap相似相非

        

    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;
            //重新散列与删除key值为null的结点
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
    }
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

         相同点:ThreadLocalMap和HashMap一样,会先对value值进行哈希,计算出value值将要落在数组内的坐标。

        不同点:HashMap采用拉链法/红黑树解决哈希冲突,而ThreadLocalMap采用向后寻找空余位的方式解决哈希冲突。

        总结一下,ThreadLocalMap是一个ThreadLocalMap的静态内部类,可以看作一个key值限定为ThreadLocal,做了些特殊处理的哈希表。

        3.2 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;
            }
        }
        //初始化ThreadLocalMap
        return setInitialValue();
    }
    static class ThreadLocalMap {
        //......

        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
        
        //......
    }

         可以看到ThreadLocal的get()方法,首先从当前线程Thread获取一个ThreadLocalMap对象引用,然后在该ThreadLocalMap哈希表中搜寻key值对应的value值返回。        

public class Thread implements Runnable {
        //...
        ThreadLocal.ThreadLocalMap threadLocals = null;
        //...
}

        threadLocals是Thread中的一个非静态属性,也就是说它是每个Thread独有的一个引用,每次初始化threadLocals会在堆区开辟一个新的ThreadLocalMap对象

        3.3 set()方法

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
    static class 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)]) {
                if (e.refersTo(key)) {
                    e.value = value;
                    return;
                }

                if (e.refersTo(null)) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            //重新散列与删除key值为null的结点
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        //......

    }

         与get()方法一致,threadLocal会通过调用Thread.currentThread()获取当前Thread对象,再通过getMap(t)获取当前线程指向的ThreadLocalMap,并尝试像哈希表一样插入key和value

         

         3.4 架构图

        厘清一下以上分析的脉络,我们可以得到下图:

        threadLocal:堆区开辟的ThreadLocal对象的一个引用

        ThreadLocal:堆区开辟的对象

        Thread.currentThread():native修饰的本地方法,返回当前线程Thread对象

        Thread对象:threadLocals属性默认为null,第一次使用threadLocal.set()方法后在堆区新开辟一个ThreadLocalMap对象

        ThreadLocalMap:使用ThreadLocal作为key值,传入值作为value值

        

        也就是说在多线程情况下如下图所示:

        尽管threadLocal引用的是堆区中的同一个ThreadLocal对象,但是由于ThreadLocalMap不同,那么存储与取出的value值互不影响,实现线程独立性。 

        本篇文章到此结束,希望能对大家有所帮助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值