栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal的理解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal的理解

ThreadLocal 前言

ThreadLocal:线程局部变量

在并发操作的时候,如何保证数据的安全性? 加锁!加锁固然解决问题,同时也是要牺牲一定的性能。
ThreadLocal就冒出来了,它的作用就是数据隔离。在并发操作下,避免当前线程的变量被其他线程修改,保证了数据的安全性

如果说锁和ThreadLocal之间有什么区别的话,就引用下敖丙前辈在相关博文中的一句话:“锁的应用是解决可能出现的问题,ThreadLocal是避免这种问题的发生”

这篇文章主要还是帮助KK来记忆相关知识的,如果有什么表达或者理解不对的地方,欢迎大家指正和交流

ThreadLocal的使用

ThreadLocal的使用非常简单,如下:

//初始化一个ThreadLocal实例
ThreadLocal threadLocal = new ThreadLocal<>();
//为线程局部变量赋值
threadLocal.set(1);
//获取线程局部变量的值
threadLocal.get();
//清除值
threadLocal.remove();
源码 SET
public class ThreadLocal {
    //...
    public void set(T value) {
    	//获取当前线程对象
        Thread t = Thread.currentThread();
    	//获取当前线程的ThreadLcoalMap,每个线程都有自己的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //存在,赋值
            map.set(this, value);
        else
            //不存在,创建一个ThreadLcoalMap
            createMap(t, value);
    }
    
    //...
    ThreadLocalMap getMap(Thread t) {
        //因为ThreakLocalMap是当前线程的,所以需要当前线程实体
        //threadLocals定义在Thread中
        return t.threadLocals;
    }   
    
	//...
	void createMap(Thread t, T firstValue) {
    	//ThreadLocalMap的是KEY是ThreadLocal实体
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
}
GET
public class ThreadLocal {
	 //...
	 public T get() {
         //获取当前线程对象
        Thread t = Thread.currentThread();
         //获取当前线程的ThreadLocalMap
        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不存在,进行初始化
        //将当前ThreadLcoal对象作为KEY,null为VALUE,构建了ThreadLocalMap 
        return setInitialValue();
    }
}
REMOVE
public class ThreadLocal {
    //...
	public void remove() {
    //获取当前线程的ThreadLocalMap,进行map的remove操作    
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
	}
}

为什么ThreadLocal可以保证并发操作下数据的安全性?

​ 正如源码所见,每个线程都定义了属于自己的ThreadLocalMap(在Thread中声明了的threadLocals变量),在Set和Get操作的时候,都是先获取当前线程实例,再通过实例获取线程的ThreadLocalMap,然后对Map进行一些列操作,例如赋值,取值,初始化等等。

​ 当前线程无法获取到其他线程的ThreadLocalMap,自然也无法对其他线程的数据进行操作了,避免了冲突,保证了数据的安全性。

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

单独把这个方法拎出来,是因为这玩意真的花了不少时间,需要记录下,主要是也怕自己忘记了

如果说上面是单一线程的操作,如果需要对所有线程进行相同的初始化赋值就可以使用ThreadLcoal.withInitial()

ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 1);

public class ThreadLocal {
    //创建一个线程局部变量。变量的初始值是通过调用 Supplier 的 get 方法来确定的
    public static  ThreadLocal withInitial(Supplier supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    
    
    static final class SuppliedThreadLocal extends ThreadLocal {

        private final Supplier supplier;
		//判空操作,可能会抛NPE
        SuppliedThreadLocal(Supplier supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
		
    	//统一初始化赋值的时候,程序会调用当前的初始化方法。supplier.get()获取的就是你设置的那个值
        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }
}

public class ThreadLocal {
    //...
    //ThreadLcoal.get()中如果没有获取到TreadLocalMap,会调用当前方法,赋予默认值NULL
    private T setInitialValue() {
        //如果初始化调用了withInitial(),当前方法会被重写。如果未被重写,默认是NULL
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
    
    //上面这段注释比较重要的意思是后面,说的是如果需要NULL以外的初始值,需要重写当前方法,并且使用匿名内部类
    protected T initialValue() {
        return null;
    }
}

首先说下结论:上述初始化赋值时,只是进行构建ThrealLocal操作,赋值的过程是在调用get方法时进行的

​ 在我DEBUG调试的过程中,发现它是先构建ThreadLocal实体。只有当其他线程执行get方法之时会执行setInitialValue()方法进行初始化值。程序默认的初始值是NULL,但是SuppliedThreadLocal这个类重写了这个方法,它会通过supplier.get()方法去获取设置的值,然后进行初始化赋值操作。

​ 然后再浏览相关博文的时候发现,看到一种说法,经过withInitial()方法初始化的ThreadLocal,通过Set和Remove后,你再次Get会发现还是初始值,并不是NULL。我自己试了试还真是这样。

​ 因为Remove后,当前线程的ThreadLocalMap是没有值的,Get操作会执行设置初始值的操作setInitialValue(),自然就会调用被重写的initialValue()方法,初始值当然不会是NULL了,有种拨开云雾见青天的感觉。芜湖!

结尾

ThreadLocal还有关于强弱引用和内存溢出的问题,这点KK还没开始研究。给自己挖个坑先。
散会!

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号