JEP-425: Virtual Threads声明“应该为每个应用程序任务创建一个新的虚拟线程“,并两次提到在JVM中运行“数百万”个虚拟线程的可能性。
相同的JEP意味着每个虚拟线程都可以访问自己的线程本地值:
虚拟线程支持线程局部变量[...],就像平台线程一样,因此它们可以运行使用线程局部变量的现有代码。
线程局部变量经常被用于缓存非线程安全且创建成本高的对象。JEP警告:
但是,由于虚拟线程可能非常多,因此请在仔细考虑后使用线程局部变量。
确实很多!特别是考虑到虚拟线程是如何不被池化的(或者至少不应该被池化)。作为一个短期任务的代表,在虚拟线程中使用线程局部变量来缓存一个昂贵的对象似乎是没有意义的。除非!我们可以从一个虚拟线程创建和访问绑定到它的载体线程的线程局部变量🤔
为了澄清,我想从类似这样的内容(当只使用限制为池大小的本地线程时,这是完全可以接受的,但当运行数百万个不断重新创建的虚拟线程时,这显然不再是一个非常有效的缓存机制:
static final ThreadLocal<DateFormat> CACHED = ThreadLocal.withInitial(DateFormat::getInstance);
到这个(可惜这个类不是公共API的一部分):
static final ThreadLocal<DateFormat> CACHED = new jdk.internal.misc.CarrierThreadLocal();
// CACHED.set(...)
在我们到达那里之前。人们必须问,这是一个安全的做法吗?
嗯,就我对虚拟线程的正确理解而言,它们仅仅是在平台线程上执行的逻辑阶段(又名“载体线程”),具有卸载而不是阻塞等待的能力。因此,我假设-如果我错了请纠正我-1)虚拟线程永远不会被同一载体线程上的另一个虚拟线程交叉存取,或者在另一个载体线程上重新调度 * 除非代码已经阻塞,否则 *,因此,如果2)我们在高速缓存的对象上调用的操作从不阻塞,则任务/虚拟线程将简单地在同一载体上从头到尾运行,因此是的,在平台线程局部上高速缓存对象将是安全的。
冒着回答我自己问题的风险,JEP-425表明这是不可能的:
载体的线程局部变量对虚拟线程不可用,反之亦然。
我找不到一个公共API来获取载体线程或显式地在平台线程上分配线程局部变量(从虚拟线程),但这并不是说我卑微的研究是确定的,也许有一种方法?
然后我读了JEP-429: Scoped Values,乍看起来似乎是Java之神为了完全摆脱ThreadLocal
而做的尝试,或者至少为虚拟线程提供了一种替代方案。事实上,JEP使用了诸如“迁移到作用域值”之类的措辞,并表示它们“比线程局部变量更受欢迎,尤其是在使用大量虚拟线程时”。
对于JEP中讨论的所有用例,我只能同意。但在本文的底部,我们还发现了这一点:
有几种情况支持线程局部变量。一个例子是缓存创建和使用成本很高的对象,如java. text. DateFormat的示例。众所周知,DateFormat对象是可变的,因此没有同步就不能在线程之间共享。通过在线程的生命周期内保持的线程局部变量为每个线程提供自己的DateFormat对象通常是一种实用的方法。
根据前面的讨论,使用thread-local可能是“实用的”,但不是非常理想。事实上,JEP-429本身实际上是从一个非常有说服力的评论开始的:“如果一百万个虚拟线程中的每一个都具有可变的线程局部变量,则存储器占用量可能是显著的”。
总结如下:
您是否找到了从虚拟线程在载体线程上分配线程局部变量的方法?
如果不是,那么可以肯定地说,对于使用虚拟线程的应用程序,在线程本地缓存对象的做法已经过时,必须实现/使用一种不同的方法,如并发缓存/Map/池/其他方法?
1条答案
按热度按时间plicqrtu1#
老实说,我不确定我是否正确理解了您的问题。JEP 429声明如下:
现在回到你关于“从虚拟线程分配线程局部变量到载体线程”的问题。你为什么需要这个?在线程局部变量中缓存对象在某些情况下是有意义的(比如在例子中缓存用于DB访问的主体)。由于虚拟线程相对较新(Java 19),现在宣布线程局部变量死亡可能有点早。