【高并发】多线程环境下如何保证共享变量的安全性?冰河建议你使用ThreadLocal,一文带你彻底搞懂ThreadLocal

}

运行程序,打印的结果信息如下所示。

线程A本地变量中的值为:ThreadA:Thread-0

线程B本地变量中的值为:ThreadB:Thread-1

此时,我们为线程A增加删除ThreadLocal中的变量的操作,如下所示。

public class ThreadLocalTest {

private static ThreadLocal threadLocal = new ThreadLocal();

public static void main(String[] args){

//创建第一个线程

Thread threadA = new Thread(()->{

threadLocal.set(“ThreadA:” + Thread.currentThread().getName());

System.out.println(“线程A本地变量中的值为:” + threadLocal.get());

threadLocal.remove();

System.out.println(“线程A删除本地变量后ThreadLocal中的值为:” + threadLocal.get());

});

//创建第二个线程

Thread threadB = new Thread(()->{

threadLocal.set(“ThreadB:” + Thread.currentThread().getName());

System.out.println(“线程B本地变量中的值为:” + threadLocal.get());

System.out.println(“线程B没有删除本地变量:” + threadLocal.get());

});

//启动线程A和线程B

threadA.start();

threadB.start();

}

}

此时的运行结果如下所示。

线程A本地变量中的值为:ThreadA:Thread-0

线程B本地变量中的值为:ThreadB:Thread-1

线程B没有删除本地变量:ThreadB:Thread-1

线程A删除本地变量后ThreadLocal中的值为:null

通过上述程序我们可以看出,线程A和线程B存储在ThreadLocal中的变量互不干扰,线程A存储的变量只能由线程A访问,线程B存储的变量只能由线程B访问。

ThreadLocal原理


首先,我们看下Thread类的源码,如下所示。

public class Thread implements Runnable {

/省略N行代码**/

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

/省略N行代码**/

}

由Thread类的源码可以看出,在ThreadLocal类中存在成员变量threadLocals和inheritableThreadLocals,这两个成员变量都是ThreadLocalMap类型的变量,而且二者的初始值都为null。只有当前线程第一次调用ThreadLocal的set()方法或者get()方法时才会实例化变量。

这里需要注意的是:每个线程的本地变量不是存放在ThreadLocal实例里面的,而是存放在调用线程的threadLocals变量里面的。也就是说,调用ThreadLocal的set()方法存储的本地变量是存放在具体线程的内存空间中的,而ThreadLocal类只是提供了set()和get()方法来存储和读取本地变量的值,当调用ThreadLocal类的set()方法时,把要存储的值放入调用线程的threadLocals中存储起来,当调用ThreadLocal类的get()方法时,从当前线程的threadLocals变量中将存储的值取出来。

接下来,我们分析下ThreadLocal类的set()、get()和remove()方法的实现逻辑。

set()方法

set()方法的源代码如下所示。

public void set(T value) {

//获取当前线程

Thread t = Thread.currentThread();

//以当前线程为Key,获取ThreadLocalMap对象

ThreadLocalMap map = getMap(t);

//获取的ThreadLocalMap对象不为空

if (map != null)

//设置value的值

map.set(this, value);

else

//获取的ThreadLocalMap对象为空,创建Thread类中的threadLocals变量

createMap(t, value);

}

在set()方法中,首先获取调用set()方法的线程,接下来,使用当前线程作为Key调用getMap(t)方法来获取ThreadLocalMap对象,getMap(Thread t)的方法源码如下所示。

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

可以看到,getMap(Thread t)方法获取的是线程变量自身的threadLocals成员变量。

在set()方法中,如果调用getMap(t)方法返回的对象不为空,则把value值设置到Thread类的threadLocals成员变量中,而传递的key为当前ThreadLocal的this对象,value就是通过set()方法传递的值。

如果调用getMap(t)方法返回的对象为空,则程序调用createMap(t, value)方法来实例化Thread类的threadLocals成员变量。

void createMap(Thread t, T firstValue) {

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

也就是创建当前线程的threadLocals变量。

get()方法

get()方法的源代码如下所示。

public T get() {

//获取当前线程

Thread t = Thread.currentThread();

//获取当前线程的threadLocals成员变量

ThreadLocalMap map = getMap(t);

//获取的threadLocals变量不为空

if (map != null) {

//返回本地变量对应的值

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings(“unchecked”)

T result = (T)e.value;

return result;

}

}

//初始化threadLocals成员变量的值

return setInitialValue();

}

通过当前线程来获取threadLocals成员变量,如果threadLocals成员变量不为空,则直接返回当前线程绑定的本地变量,否则调用setInitialValue()方法初始化threadLocals成员变量的值。

private T setInitialValue() {

//调用初始化Value的方法

T value = initialValue();

Thread t = Thread.currentThread();

//根据当前线程获取threadLocals成员变量

ThreadLocalMap map = getMap(t);

if (map != null)

//threadLocals不为空,则设置value值

map.set(this, value);

else

//threadLocals为空,创建threadLocals变量

createMap(t, value);

return value;

}

其中,initialValue()方法的源码如下所示。

protected T initialValue() {

return null;

}

通过initialValue()方法的源码可以看出,这个方法可以由子类覆写,在ThreadLocal类中,这个方法直接返回null。

remove()方法

remove()方法的源代码如下所示。

public void remove() {

//根据当前线程获取threadLocals成员变量

ThreadLocalMap m = getMap(Thread.currentThread());

if (m != null)

//threadLocals成员变量不为空,则移除value值

m.remove(this);

}

remove()方法的实现比较简单,首先根据当前线程获取threadLocals成员变量,不为空,则直接移除value的值。

注意:如果调用线程一致不终止,则本地变量会一直存放在调用线程的threadLocals成员变量中,所以,如果不需要使用本地变量时,可以通过调用ThreadLocal的remove()方法,将本地变量从当前线程的threadLocals成员变量中删除,以免出现内存溢出的问题。

ThreadLocal变量不具有传递性


使用ThreadLocal存储本地变量不具有传递性,也就是说,同一个ThreadLocal在父线程中设置值后,在子线程中是无法获取到这个值的,这个现象说明ThreadLocal中存储的本地变量不具有传递性。

接下来,我们来看一段代码,如下所示。

public class ThreadLocalTest {

private static ThreadLocal threadLocal = new ThreadLocal();

public static void main(String[] args){

//在主线程中设置值

threadLocal.set(“ThreadLocalTest”);

//在子线程中获取值

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

System.out.println(“子线程获取值:” + threadLocal.get());

}

});

//启动子线程

thread.start();

//在主线程中获取值

System.out.println(“主线程获取值:” + threadLocal.get());

}

}

运行这段代码输出的结果信息如下所示。

主线程获取值:ThreadLocalTest

子线程获取值:null

通过上述程序,我们可以看出在主线程中向ThreadLocal设置值后,在子线程中是无法获取到这个值的。那有没有办法在子线程中获取到主线程设置的值呢?此时,我们可以使用InheritableThreadLocal来解决这个问题。

InheritableThreadLocal使用示例


InheritableThreadLocal类继承自ThreadLocal类,它能够让子线程访问到在父线程中设置的本地变量的值,例如,我们将ThreadLocalTest类中的threadLocal静态变量改写成InheritableThreadLocal类的实例,如下所示。

public class ThreadLocalTest {

private static ThreadLocal threadLocal = new InheritableThreadLocal();

public static void main(String[] args){

//在主线程中设置值

threadLocal.set(“ThreadLocalTest”);

//在子线程中获取值

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

System.out.println(“子线程获取值:” + threadLocal.get());

}

});

//启动子线程

thread.start();

//在主线程中获取值

System.out.println(“主线程获取值:” + threadLocal.get());

}

}

此时,运行程序输出的结果信息如下所示。

主线程获取值:ThreadLocalTest

子线程获取值:ThreadLocalTest

可以看到,使用InheritableThreadLocal类存储本地变量时,子线程能够获取到父线程中设置的本地变量。

InheritableThreadLocal原理


首先,我们来看下InheritableThreadLocal类的源码,如下所示。

public class InheritableThreadLocal extends ThreadLocal {

protected T childValue(T parentValue) {

return parentValue;

}

ThreadLocalMap getMap(Thread t) {

return t.inheritableThreadLocals;

}

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值