ThreadLocal的总结和使用

ThreadLocal的set,get,remove

​ 在学习数据源切换相关代码的时候,看到了关于ThreadLocal部分的东西,刚开始认为只是个简单的map,没有在意,后来在交流的时候发现颇为重要,专门学习,并记录一下ThreadLocal方面的知识。

​ 首先要明白ThreadLocal是什么,光看名字可以知道是和线程有关的,本地线程?然也不尽然。

​ ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

​ ThreadLocal的确是一个map模型,之所以称之为模型,因为他并不是一个简单的map,本次学习涉及到的是其中最常使用的三个方法:set、get、remove

public class DataSourceHolder {
    // 创建一个threadlocal
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    // set方法
    public static void setDataSource(String dataSourceName) {
        threadLocal.set(dataSourceName);
    }
    // get方法
    public static String getDataSource() {
        return threadLocal.get();
    }
    // remove方法
    public static void removeDataSource() {
        threadLocal.remove();
    }
}

​ 上面是简单创建一个ThreadLocal及其方法的使用,ThreadLocal提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离〜 。

​ 首先我们跟着ThreadLocal的set方法的源码,看一下怎么对这个局部变量进行操作的:

    public void set(T value) {
        // 既然是ThreadLocal,肯定要有thread,首先获取当前线程对象
        Thread t = Thread.currentThread();
        // 创建ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // 如果不为空则set一个this!,否则,这个线程就是他的key
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

​ 在创建map的时候有一个getMap(t)的方法,让我们看下这个方法的源码

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    // 点进去查看t.threadLocals
     ThreadLocal.ThreadLocalMap threadLocals = null;

​ 此方法就是简单的获取这个key,是否有对应的value如果有,则覆盖,如果没有则使用createMap创建一个,如果有,则用现在要存储的对象将之前的覆盖

​ 那么让我们看一看这个神奇的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;
            }
        }

​ 我们可以发现ThreadLocalMap是ThreadLocal的一个内部类,用entry类来进行存储,而这个map的key就是当前的ThreadLocal对象

​ 所以我们可以看出,Thread利用了每一个线程作为key,维护这个变量,保证其线程的数据隔离,了解了set方法之后,get方法就非常好理解了,让我们看一下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();
    }

​ 看似很多地方和set差不多,也是先获取t,然后获取自身的ThreadLocalMap,获取之后通过map.getEntry(this)方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。该方法中的 this 即当前访问的 ThreadLocal 对象; 如果获取到的 Entry 不为 null,从 Entry 中取出值即为所需访问的本线程对应的实例。如果获取到的 Entry 为 null,则通过setInitialValue()方法设置该 ThreadLocal 变量在该线程中对应的具体实例的初始值。

​ 而setInitialValue()方法和set差别不大,主要是多了一个初始化和返回

private T setInitialValue() {
		// 初始化value
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

​ remove方法也较为简单啦,获取当前key,然后直接使用remove方法删除,就完事了

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

​ 其实看源码分析ThreadLocal还是比较简单的,但是ThreadLocal的原理主要是什么呢?下面进行一个简单的总结:

​ 1、每个线程维护着一个ThreadLocalMap的引用

​ 2、ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

​ 3、调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象

​ 4、调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象

​ 5、ThreadLocal本身并不存储值,它只是一个键来让线程从ThreadLocalMap获取值。

​ 那么ThreadLocal是否会引起内存不足的影响呢,当然不会,因为ThreadLocal并不会产生内存开销,在选择键的时候,并非是选择了一个ThreadLocal的实例,实际上是对ThreadLocal实例的弱引用,我们可以看到在ThreadLocalMap中,在用entry存储键值的时候,其内部类Entry继承了WeakReference<ThreadLocal<?>>,而 WeakReference是为了区别直接的对象引用,定义的另外一种引用关系,其标志性特点就是:reference实例不会影响到被应用对象的GC回收行为,只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。所以当threadLocal实例可以被GC回收时,系统可以检测到该threadLocal对应的Entry是否已经过期(根据reference.get() == null来判断,如果为true则表示过期)来自动做一些清除工作,从而防止内存泄露的问题,也不会造成内存不足。

​ 还有一个小坑,是在使用的时候遇到的,在使用动态切换数据源时,在使用完毕之后,一定要调用其remove方法,手动remove,避免造成内存浪费,甚至有可能导致,下一次数据操作的时候,发生找不到数据库,活数据写错地方的情况

​ 以上就是对Threadlocal知识点的总结,和个人观点,欢迎大家评论指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值