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