ThreadLocal的理解

ThreadLocal

前言

ThreadLocal:线程局部变量

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

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

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

ThreadLocal的使用

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

//初始化一个ThreadLocal实例
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
//为线程局部变量赋值
threadLocal.set(1);
//获取线程局部变量的值
threadLocal.get();
//清除值
threadLocal.remove();

源码

SET

public class ThreadLocal<T> {
    //...
    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<T> {
	 //...
	 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<T> {
    //...
	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 values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;    
}

withInitial()

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

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

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

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

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

public class ThreadLocal<T> {
    //...
    //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;
    }
    
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    //上面这段注释比较重要的意思是后面,说的是如果需要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还没开始研究。给自己挖个坑先。
散会!
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值