ThreadLocal不香了,ScopedValue才是王道

ThreadLocal的缺点

在Java中,当多个方法要共享一个变量时,我们会选择使用ThreadLocal来进行共享,比如: 以上代码将字符串"dadudu"通过设置到ThreadLocal中,从而可以做到在main()方法中赋值,在a()b()方法中获取值,从而共享值。

生命在于思考,我们来想想ThreadLocal有什么缺点:

  1. 第一个就是权限问题,也许我们只需要在main()方法中给ThreadLocal赋值,在其他方法中获取值就可以了,而上述代码中a()b()方法都有权限给ThreadLocal赋值,ThreadLocal不能做权限控制。
  2. 第二个就是内存问题,ThreadLocal需要手动强制 remove,也就是在用完ThreadLocal之后,比如b()方法中,应该调用其remove()方法,但是我们很容易忘记调用remove(),从而造成内存浪费

ScopedValue

而JDK21中的新特性ScopedValue能不能解决这两个缺点呢?我们先来看一个ScopedValue的Demo:

首先需要通过ScopedValue.newInstance()生成一个ScopedValue对象,然后通过ScopedValue.runWhere()方法给ScopedValue对象赋值,runWhere()的第三个参数是一个lambda表达式,表示作用域 ,比如上面代码就表示:给NAME绑定值为"dadudu",但是仅在调用a()方法时才生效,并且在执行runWhere()方法时就会执行lambda表达式。

比如上面代码的输出结果为:

从结果可以看出在执行runWhere()时会执行a()a()方法中执行b()b()执行完之后返回到main()方法执行runWhere()之后的代码,所以,在a()方法和b()方法中可以拿到ScopedValue对象所设置的值,但是在main()方法中是拿不到的(报错了),b()方法之所以能够拿到,是因为属于a()方法调用栈中。

所以在给ScopedValue绑定值时都需要指定一个方法,这个方法就是所绑定值的作用域,只有在这个作用域中的方法才能拿到所绑定的值。

ScopedValue也支持在某个方法中重新开启新的作用域并绑定值,比如:

以上代码中,在a()方法中重新给ScopedValue绑定了一个新值"xiaodudu",并指定了作用域为c()方法,所以c()方法中拿到的值为"xiaodudu",但是b()中仍然拿到的是"dadudu",并不会受到影响,以上代码的输出结果为:

甚至如果把代码改成:

以上代码在a()方法中有两处调用了c()方法,我想大家能思考出c1c2 输出结果分别是什么:

所以,从以上分析可以看到,ScopedValue有一定的权限控制:就算在同一个线程中也不能任意修改ScopedValue的值,就算修改了对当前作用域(方法)也是无效的 。另外ScopedValue也不需要手动remove,关于这块就需要分析它的实现原理了。

实现原理

大家先看下面代码,注意看下注释:

执行main()方法时,main线程执行过程中会执行runWhere()方法三次,而每次执行runWhere()时都会生成一个Snapshot对象 ,Snapshot对象中记录了所绑定的值,而Snapshot对象有一个prev 属性指向上一次所生成的Snapshot对象,并且在Thread类中新增了一个属性scopedValueBindings,专门用来记录当前线程对应的Snapshot对象。

比如在执行main()方法中的runWhere()时:

  1. 会先生成Snapshot对象1 ,其prev为null,并将Snapshot对象1 赋值给当前线程的scopedValueBindings属性,然后执行a()方法
  2. 在执行a()方法中的runWhere()时,会先生成Snapshot对象2 ,其prevSnapshot对象1 ,并将Snapshot对象2 赋值给当前线程的scopedValueBindings属性,使得在执行b()方法时能从当前线程拿到Snapshot对象2 从而拿到所绑定的值,runWhere()内部在执行完b()方法后会取prev,从而取出Snapshot对象1 ,并将Snapshot对象1 赋值给当前线程的scopedValueBindings属性,然后继续执行a()方法后续的逻辑,如果后续逻辑调用了get()方法,则会取当前线程的scopedValueBindings属性拿到Snapshot对象1 ,从Snapshot对象1 中拿到所绑定的值就可以了,而对于Snapshot对象2由于没有引用则会被垃圾回收掉。

所以,在用ScopedValue时不需要手动remove。

好了,关于ScopedValue就介绍到这啦,下次继续分享JDK21新特性,欢迎大家关注我的公众号:Hoeller,第一时间接收我的原创技术文章,谢谢大家的阅读。

相关推荐
救救孩子把13 分钟前
深入理解 Java 对象的内存布局
java
落落落sss15 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
万物皆字节21 分钟前
maven指定模块快速打包idea插件Quick Maven Package
java
夜雨翦春韭28 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
我行我素,向往自由34 分钟前
速成java记录(上)
java·速成
一直学习永不止步40 分钟前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
邵泽明40 分钟前
面试知识储备-多线程
java·面试·职场和发展
Yvemil71 小时前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript