首先,一个 Java(或 .NET)线程 != 一个内核/操作系统线程。
Java线程是一个高级包装器,它抽象出系统线程的一些功能;这些类型的线程也称为托管线程。在内核级别,线程只有 2 种状态,即正在运行和未运行。内核会跟踪一些管理信息(堆栈、指令指针、线程 ID 等),但在内核级别,没有线程处于TIMED_WAITING
状态(.NET 等效于 WaitSleepJoin
状态)这样的东西。这些“状态”只存在于这些类型的上下文中(部分原因C++ std::thread
没有成员)。state
话虽如此,当托管线程被阻止时,它以几种方式完成(取决于如何请求在托管级别阻止它);我在OpenJDK中看到的线程代码实现利用信号量来处理托管等待(这是我在其他具有“托管”线程类的 C++框架以及.NET Core库中看到的),并将互斥体用于其他类型的等待/锁定。
由于大多数实现将使用某种锁定机制(如信号量或互斥体),内核通常做同样的事情(至少在您的问题方面);也就是说,内核会将线程从“run”队列中取出,并将其放入“等待”队列(上下文切换)。进入线程调度,特别是内核如何处理线程的执行超出了本问答的范围,特别是因为你的问题是关于Java的,Java可以在相当多的不同类型的操作系统上运行(每种操作系统处理线程完全不同)。
更直接地回答您的问题:
当线程处于TIMED_WAIT状态(非休眠状态)时,JVM 中的大量线程是否会消耗大量资源(内存、CPU)>99.9%的时间?
为此,有几件事需要注意:创建的线程消耗JVM的内存(堆栈,ID,垃圾回收器等),内核消耗内核内存以在内核级别管理线程。消耗的内存不会更改,除非您明确说明。因此,如果线程处于休眠状态或正在运行,则内存是相同的。
CPU是根据线程活动和请求的线程数而变化的(请记住,线程也会消耗内核资源,因此必须在内核级别进行管理,因此必须处理的线程越多,管理它们的内核时间就越多)。
请记住,调度和运行线程的内核时间非常小(这是设计的一部分),但是如果您计划运行大量线程,这仍然是需要考虑的事情;此外,如果您知道您的应用程序将在只有几个内核的CPU(或集群)上运行,那么您可以使用的内核越少,内核就越需要上下文切换,这通常会增加额外的时间。
当线程等待时,如果需要维护它们,需要多少 CPU 开销?
没有。请参阅上文,但用于管理线程的 CPU 开销不会根据线程上下文而变化。额外的CPU可用于上下文切换,并且当线程本身处于活动状态时,线程本身肯定会使用额外的CPU,但是CPU没有额外的“成本”来维护等待线程而不是正在运行的线程。
答案是否也适用于与JVM无关的环境(如linux内核)?
是和否。如前所述,托管上下文通常适用于大多数这些类型的环境(例如Java,.NET,PHP,Lua等),但这些上下文可能会有所不同,线程习语和一般功能取决于所使用的内核。因此,虽然一个特定的内核可能能够处理每个进程1000多个线程,但有些可能有硬性限制,而另一些内核可能具有其他问题,每个进程的线程数更多;您必须参考OS / CPU规格才能查看您可能有什么样的限制。
由于大多数线程在其生命周期的绝大多数时间内都将处于TIMED_WAIT状态(Java的Timer()调用Object.wait(long)方法),它是否仍然以非常大的方式影响CPU?
否(阻塞线程的一部分),但需要考虑一些事情:如果(边缘情况)所有(或>50%)这些线程需要同时运行,该怎么办?如果你只有几个线程管理你的包,这可能不是问题,但假设你有500多个;同时唤醒 250 个线程将导致大量 CPU 争用。
由于您尚未发布任何代码,因此很难对您的方案提出具体建议,但人们倾向于将属性结构存储为类,并将该类保存在可在(或单独的线程)中引用的列表或哈希映射中,以查看当前时间是否与包的过期时间匹配, 然后“过期”代码将运行。这将线程数减少到 1,并将访问时间减少到 ;但同样,如果没有代码,该建议可能不适用于您的方案。Timer
O(1)
希望有所帮助。