ThreadLocal不香了,ScopedValue才是王道
ThreadLocal的缺点
在Java中,当多个方法要共享一个变量时,我们会选择使用ThreadLocal
来进行共享,比如: 以上代码将字符串“dadudu”通过设置到ThreadLocal
中,从而可以做到在main()
方法中赋值,在a()
、b()
方法中获取值,从而共享值。
生命在于思考,我们来想想ThreadLocal
有什么缺点:
- 第一个就是权限问题,也许我们只需要在
main()
方法中给ThreadLocal
赋值,在其他方法中获取值就可以了,而上述代码中a()
、b()
方法都有权限给ThreadLocal
赋值,ThreadLocal
不能做权限控制。 - 第二个就是内存问题,
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()
方法,我想大家能思考出c1和c2输出结果分别是什么:
所以,从以上分析可以看到,ScopedValue
有一定的权限控制:就算在同一个线程中也不能任意修改ScopedValue的值,就算修改了对当前作用域(方法)也是无效的。另外ScopedValue
也不需要手动remove,关于这块就需要分析它的实现原理了。
实现原理
大家先看下面代码,注意看下注释:
执行main()
方法时,main线程执行过程中会执行runWhere()
方法三次,而每次执行runWhere()
时都会生成一个Snapshot对象,Snapshot对象中记录了所绑定的值,而Snapshot对象有一个prev属性指向上一次所生成的Snapshot对象,并且在Thread类中新增了一个属性scopedValueBindings
,专门用来记录当前线程对应的Snapshot对象。
比如在执行main()
方法中的runWhere()时:
- 会先生成Snapshot对象1,其
prev
为null,并将Snapshot对象1赋值给当前线程的scopedValueBindings
属性,然后执行a()
方法 - 在执行
a()
方法中的runWhere()
时,会先生成Snapshot对象2,其prev
为Snapshot对象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,第一时间接收我的原创技术文章,谢谢大家的阅读。
来源:juejin.cn/post/7287241480770928655