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:作用域值,乍一看似乎是Java神之一击,以彻底摆脱
ThreadLocal
,或者至少为虚拟线程提供替代方案。事实上,该JEP使用"迁移到作用域值"等措辞,并表示它们"比线程本地变量更受欢迎,特别是在使用大量虚拟线程时"。对于JEP中讨论的所有用例,我都只能同意。但是,在这份文件的底部,我们还发现了这一段:
考虑到早先讨论的内容,使用线程本地变量可能是"实用"但并不理想。事实上,JEP-429本身就开始于一个非常显著的言论:"如果每个百万虚拟线程都有可变的线程本地变量,则内存占用可能非常大"。有一些情况更适合于线程本地变量。例如,缓存昂贵且具有使用价值的对象,例如java.text.DateFormat的实例。众所周知,DateFormat对象是可变的,因此如果不进行同步,不能在线程之间共享。通过使用持续存在于线程生命周期中的线程本地变量,为每个线程分配自己的DateFormat对象通常是一种实用的方法。
总结一下:
您是否找到了一种从虚拟线程中分配线程本地变量到载体线程的方法?
如果没有,那么可以说对于使用虚拟线程的应用程序来说,在线程本地缓存对象的做法已经死亡,而且必须实现/使用不同的方法,例如并发缓存/映射/池/等等吗?