等待线程资源消耗


问题内容

我的问题:

当线程处于TIMED_WAIT状态(非休眠)>
99.9%的时间时,JVM中的大量线程是否会消耗大量资源(内存,CPU)吗?当线程正在等待时,如果根本需要维护它们,需要花费多少CPU开销?

答案是否也适用于与非JVM相关的环境(例如linux内核)?

内容:

我的程序收到大量占用空间的程序包。它在不同的程序包中存储相似属性的计数。在收到包裹后的给定时间(可能是数小时或数天)之后,该特定包裹将过期,并且该包裹所贡献的任何计数都应减少。

目前,我通过将所有软件包存储在内存或磁盘中来实现这些功能。每隔5分钟,我会从存储中删除过期的软件包,并浏览其余的软件包以计算属性。此方法占用大量内存,并且时间复杂度较差(O(n)对于时间和内存,其中n是未过期的软件包数)。这使得程序的可伸缩性很差。

解决此问题的另一种方法是,每当一个软件包通过时增加属性计数,并Timer()在该软件包到期后启动一个减少属性计数的线程。这样就无需存储所有笨重的程序包,并将时间复杂度降低到O(1)。但是,这又带来了另一个问题,因为我的程序将开始具有O(n)多个线程,这可能会降低性能。由于大多数线程在其生命周期的绝大部分时间内都处于TIMED_WAIT状态(Java会Timer()调用该Object.wait(long)方法),它是否仍会以很大的方式影响CPU?


问题答案:

首先,Java(或.NET)线程!=内核/ OS线程。

Java
线程是一个高级包装程序,它抽象了系统线程的某些功能。这些类型的线程也称为托管线程。在内核级别,线程只有两种状态,运行和不运行。内核会跟踪一些管理信息(堆栈,指令指针,线程ID等),但是在内核级别,并没有处于某种TIMED_WAITING状态(等于该WaitSleepJoin状态的.NET
)的线程。。这些“状态”仅存在于那种上下文中(这是C ++
std::thread没有state成员的部分原因)。

话虽如此,当托管线程被阻塞时,它是以两种方式来完成的(取决于在托管级别上被请求阻塞的方式);我在OpenJDK中为线程代码看到的实现利用信号量来处理托管等待(这是我在具有某种“托管”线程类的其他C
++框架以及.NET Core中所看到的库),并将互斥锁用于其他类型的等待/锁定。

由于大多数实现都将使用某种锁定机制(例如信号量或互斥量),因此内核通常会执行相同的操作(至少在涉及您问题的地方);也就是说,内核会将线程从“运行”队列中移出,并将其放入“等待”队列中(上下文切换)。深入了解线程调度,特别是内核如何处理线程的执行,不在本问答的范围之内,尤其是因为您的问题是关于Java的,而Java可以在多种不同类型的OS上运行(每种操作系统都可以处理)完全不同的线程)。

更直接地回答您的问题:

当线程的TIMED_WAIT状态(不休眠)> 99.9%的时间时,JVM中的大量线程是否会消耗大量资源(内存,CPU)吗?

为此,有两点需要注意:创建的线程消耗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争用。

由于您尚未发布任何代码,因此很难针对您的情况提出具体建议,但是您倾向于将属性结构存储为类,并将该类保留在可以在Timer(或单独的线程)以查看当前时间是否与包的过期时间匹配,则将运行“过期”代码。这将线程数减少到1,并将访问时间减少到O(1);
但同样,如果没有代码,则该建议可能不适用于您的情况。

希望能有所帮助。