$(SolutionPath) NewCachedThreadPool ()与 Executors.newFixedThreadPool ()

$(SourcePath) $(光谱缓解)

与之相对的是,newCachedThreadPool() = “ http://docs.oracle.com/javase/8/docs/api/java/util/while/Executors.html # newFixedThreadPool-int-”rel = “ noReferrer”> newFixedThreadPool()

$(SQLDebug)

什么时候应该使用其中一种策略? 哪种策略在资源利用方面更好?

125248 次浏览

我认为文档很好地解释了这两种功能的区别和用法:

$(SystemDrive)

newFixedThreadPool

$(SystemRoot)

创建一个线程池,该线程池重用 固定数量的线程运行关闭 共享的无限制队列 $(TargetExt) 点,最多 nThreads 线程将 $(TargetFramework 版本) 主动处理任务 $(TargetName) 时提交附加任务 $(TargetPlatformMinVersion) 所有线程都是活动的,它们将等待 $(TargetPlatformVersion) 在队列中,直到线程 可用。如果有任何线程终止 $(TargetPlatformWinMDLocation) $(TargetUniversalCRTVersion) 由于执行过程中的失败 $(TEMP) 在关闭之前,一个新的将采取 $(TMP) 它的位置,如果需要执行 中的线程 $(ToolsetPropsfound) 池将一直存在,直到它被显式地 $(ToolsetTargetsfound) $(UCRTContentRoot) 关闭。

$(UM _ IncludePath)

newCachedThreadPool

$(UniversalCRT _ IncludePath)

创建一个线程池,该线程池创建新的 $(UniversalCRT _ LibraryPath _ arm) $(UniversalCRT _ LibraryPath _ arm64) $(UniversalCRT _ LibraryPath _ x64) $(UniversalCRT _ LibraryPath _ x86) 线程,但将重用 $(UniversalCRT _ PropsPath) 以前构造的线程时 $(UniversalCRT _ SourcePath) 这些泳池可供使用 $(UniversalCRTSdkDir) 典型地改善... 的性能 $(UniversalCRTSdkDir _ 10) 执行许多短命程序的程序 $(UseDebugLibrary) 异步任务 $(UseLegacyManagedDebugger) 将重复使用以前建造的 $(UseOfATL) 线程如果可用。如果没有现有的 $(UseOfMfc) 线程可用时,新线程将 $(用户域) 创建并添加到池中。 $(USERDOMAIN _ ROAMINGPROFILE) $(用户名) 未使用的线程 60秒结束 $(用户资料) $(UserRootDir) 从缓存中移除。因此,一个池 闲置了足够长的时间 $(VBOX _ MSI _ INSTALL _ PATH) $(VC _ ATLMFC _ IncludePath) 不消耗任何资源。请注意 $(VC _ ATLMFC _ SourcePath) 具有类似性质但 $(VC _ CRT _ SourcePath) 不同的细节(例如: $(VC _ ExecutablePath _ ARM) 超时参数) $(VC _ ExecutablePath _ ARM64) 使用 ThreadPoolExecator 构造函数。

$(VC _ ExecutablePath _ x64)

在资源方面,newFixedThreadPool将保持所有线程运行,直到它们被显式终止。在 newCachedThreadPool中,60秒内未使用的线程将被终止并从缓存中删除。

$(VC _ ExecutablePath _ x64 _ ARM)

在这种情况下,资源消耗将在很大程度上取决于。例如,如果您有大量长时间运行的任务,我建议使用 FixedThreadPool。至于 CachedThreadPool,文档说“这些池通常会提高执行许多短期异步任务的程序的性能”。

$(VC _ ExecutablePath _ x64 _ ARM64) $(VC _ ExecutablePath _ x64 _ x64)

你必须使用 newcachedThreadPool 只有当你有短期的异步任务时,如果你提交的任务需要更长的时间来处理,你会创建太多的线程。 Javadoc。如果以更快的速度向 newCachedThreadPool (http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/)提交长时间运行的任务,则 CPU 可能达到100% 。

$(VC _ ExecutablePath _ x64 _ x86)

没错,对于服务于多个客户端和并发请求的服务器代码来说,Executors.newCachedThreadPool()不是一个很好的选择。

为什么? 它基本上有两个(相关的)问题:

    $(VC _ ExecutablePath _ x86) $(VC _ ExecutablePath _ x86 _ ARM)
  1. 它是无限的,这意味着您正在为任何人打开大门,通过简单地向服务注入更多的工作(DoS 攻击)来削弱您的 JVM。线程消耗的内存量不容忽视,而且基于它们正在进行的工作,还会增加内存消耗,因此这样很容易就会推翻服务器(除非您有其他断路器)。

  2. $(VC _ ExecutablePath _ x86 _ ARM64) $(VC _ ExecutablePath _ x86 _ x64)
  3. 无界问题因为 Execector 前面有一个 SynchronousQueue而变得更加严重,这意味着在任务提供者和线程池之间有一个直接的切换。如果所有现有线程都忙,则每个新任务将创建一个新线程。对于服务器代码来说,这通常是一个糟糕的策略。当 CPU 饱和时,现有的任务需要更长的时间才能完成。然而,提交的任务越来越多,创建的线程也越来越多,因此完成任务的时间越来越长。当 CPU 饱和时,更多的线程肯定不是服务器所需要的。

$(VC _ ExecutablePath _ x86 _ x86)

以下是我的建议:

$(VC _ IFCPath) $(VC _ IncludePath)

使用固定大小的线程池 Executors.newFixedThreadPool或具有设定最大线程数的 线程池执行器。;

$(VC _ LibraryPath _ ARM)

如果您看到 grepcode 中的代码,您将看到,它们正在内部调用 线程池执行器。并设置其属性。您可以创建一个来更好地控制您的需求。

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}


public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
$(VC _ LibraryPath _ ARM64) $(VC _ LibraryPath _ ATL _ ARM)

如果不担心 可调用/可运行任务的无限队列,可以使用其中之一。正如布鲁诺所建议的那样,相对于这两个,我也更喜欢 newFixedThreadPool而不是 newCachedThreadPool

$(VC _ LibraryPath _ ATL _ ARM64)

但与 newFixedThreadPoolnewCachedThreadPool相比,线程池执行器提供了更灵活的特性

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
$(VC _ LibraryPath _ ATL _ x64)

优点:

    $(VC _ LibraryPath _ ATL _ x86) $(VC _ LibraryPath _ VC _ ARM)
  1. 你可以完全控制 BlockingQueue的大小。它不是无界的,不像前两个选项。我不会得到一个内存不足的错误,由于一个悬而未决的可调用/可运行任务的巨大堆积,当有意外的动荡在系统中。

  2. $(VC _ LibraryPath _ VC _ ARM _ Desktop) $(VC _ LibraryPath _ VC _ ARM _ OneCore)
  3. 您可以实现自定义 拒收处理策略或使用其中一个策略:

      $(VC _ LibraryPath _ VC _ ARM _ Store)
    1. 在默认 ThreadPoolExecutor.AbortPolicy中,处理程序在拒绝时抛出运行时 RejectedExectionException。

    2. $(VC _ LibraryPath _ VC _ ARM64)
    3. ThreadPoolExecutor.CallerRunsPolicy中,调用 execute 本身的线程运行任务。这提供了一个简单的反馈控制机制,可以减慢新任务的提交速度。

    4. $(VC _ LibraryPath _ VC _ ARM64 _ Desktop)
    5. ThreadPoolExecutor.DiscardPolicy中,不能执行的任务被简单地删除。

    6. $(VC _ LibraryPath _ VC _ ARM64 _ OneCore) $(VC _ LibraryPath _ VC _ ARM64 _ Store)
    7. ThreadPoolExecutor.DiscardOldestPolicy中,如果执行器没有关闭,那么将删除工作队列前端的任务,然后重试执行(这可能会再次失败,从而导致重复执行)

  4. $(VC _ LibraryPath _ VC _ x64)
  5. 您可以为以下用例实现一个自定义线程工厂:

      $(VC _ LibraryPath _ VC _ x64 _ Desktop)
    1. 设置更具描述性的线程名
    2. $(VC _ LibraryPath _ VC _ x64 _ OneCore)
    3. 设置线程守护进程状态
    4. $(VC _ LibraryPath _ VC _ x64 _ Store)
    5. 设置线程优先级
$(VC _ LibraryPath _ VC _ x86)

为了完成其他的回答,我想引用 Joshua Bloch 的有效 Java 第二版,第10章,第68条:

$(VC _ LibraryPath _ VC _ x86 _ Desktop) $(VC _ LibraryPath _ VC _ x86 _ OneCore) $(VC _ LibraryPath _ VC _ x86 _ Store) $(VC _ LibraryPath _ x64) $(VC _ LibraryPath _ x86) $(VC _ PGO _ RunTime _ Dir) $(VC _ ReferencesPath _ ARM)

”为特定应用程序选择执行者服务可能很棘手。如果您正在编写 小程序负载较轻的服务器,使用 执行程序 new-CachedThreadPool 是通常是 不错的选择,因为它不需要配置,而且通常“做正确的事情”但是对于 重载生产服务器来说,缓存的线程池是 不是个好选择

$(VC _ ReferencesPath _ ARM64) $(VC _ ReferencesPath _ ATL _ ARM) $(VC _ ReferencesPath _ ATL _ ARM64) $(VC _ ReferencesPath _ ATL _ x64)

缓存线程池中,提交的任务不排队会立即移交给线程执行。如果没有可用的线程,则创建一个新线程.如果服务器负载过重,以至于它的所有 CPU 都被充分利用,并且有更多的任务到达,那么就会创建更多的线程,这只会使情况变得更糟。

$(VC _ ReferencesPath _ ATL _ x86) $(VC _ ReferencesPath _ VC _ ARM) $(VC _ ReferencesPath _ VC _ ARM64) $(VC _ ReferencesPath _ VC _ x64)

因此,对于 在重负载的生产服务器中,您最好使用 Executors.newFixedThreadPool,它为您提供一个具有固定数量线程的池,或者直接使用 ThreadPoolExecator 类 为了最大限度的控制。

$(VC _ ReferencesPath _ VC _ x86)

我做了一些快速测试,结果如下:

1)如果使用 SynchronousQueue:

$(VC _ ReferencesPath _ x64)

当线程达到最大大小后,任何新的工作都将被拒绝,例如下面的异常。

$(VC _ ReferencesPath _ x86) $(VC _ SourcePath)

在线程“ main”java.util.current 中出现异常。RejectedExectionException: 任务 java.util.while。FutureTask@3fe733d 被 java.util.concur.拒绝。ThreadPoolExecator@5acf9800[运行中,池大小 = 3,活动线程 = 3,排队任务 = 0,完成任务 = 0]

执行(ThreadPoolExecutor.java: 2047)

$(VC _ VC _ IncludePath) $(VC _ VS _ IncludePath)

2)如果使用 LinkedBlockingQueue:

$(VC _ VS _ LibraryPath _ VC _ VS _ ARM)

线程从不从最小大小增加到最大大小,这意味着线程池的大小是固定的。

$(VC _ VS _ LibraryPath _ VC _ VS _ x64) $(VC _ VS _ LibraryPath _ VC _ VS _ x86)

ThreadPoolExecutor类是从许多 Executors工厂方法返回的执行器的基本实现。因此,让我们从 ThreadPoolExecutor的角度来研究 修好了缓存线程池。

$(VC _ VS _ SourcePath)

线程池执行器

$(VCIDEInstallDir) $(VCIDEInstallDir _ 150)

这个类的 主要建筑工程承建商是这样的:

public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
$(VCInstallDir)

核心池面积

$(VCInstallDir _ 150)

corePoolSize确定目标线程池的最小大小。即使没有要执行的任务,实现也会维护一个这样大小的池。

$(VCLibPackagePath)

泳池最大面积

$(VCProjectVersion)

maximumPoolSize是可以同时激活的最大线程数

$(VCTargetsPath)

在线程池增长并变得超过 corePoolSize阈值之后,执行器可以终止空闲线程并再次到达 corePoolSize。 $(VCTargetsPath10) $(VCTargetsPath11) 如果 allowCoreThreadTimeOut为 true,那么如果核心池线程的空闲时间超过 keepAliveTime阈值,那么执行器甚至可以终止它们。

$(VCTargetsPath12)

所以底线是,如果线程空闲时间超过 keepAliveTime阈值,它们可能会被终止,因为没有对它们的需求。

$(VCTargetsPath14)

排队

$(VCTargetsPath15) $(VCTargetsPathActal)

当一个新任务进入并且所有的核心线程都被占用时会发生什么?新任务将在该 BlockingQueue<Runnable>实例中排队。当线程变为空闲时,可以处理这些排队的任务之一。

$(VCTargetsPatheffect)

在 Java 中 BlockingQueue接口有不同的实现,所以我们可以实现不同的排队方法,比如:

    $(VCToolArchitecture)
  1. 有界队列 : 新任务将在有界任务队列中排队。

  2. $(VCToolsInstallDir)
  3. 无界队列 : 新任务将在无界任务队列中排队。因此,这个队列可以在堆大小允许的范围内增长。

  4. $(VCToolsInstallDir _ 150)
  5. 同步切换 : 我们也可以使用 SynchronousQueue对新任务进行排队。在这种情况下,在对新任务排队时,另一个线程必须已经在等待该任务。

提交工作报告

$(VCToolsVersion)

下面是 ThreadPoolExecutor如何执行一个新任务:

    $(VisualStudioDir)
  1. 如果正在运行的线程少于 corePoolSize,请尝试启动 $(VisualStudioEdition) $(VisualStudioVersion) 将给定任务作为其第一个作业的新线程。
  2. 否则,它将尝试使用 $(VS _ ExecutablePath) $(VS140COMTOLS) 如果队列已满,offer方法将不会阻塞,并立即返回 false
  3. $(VSAPPIDDIR) $(VSAPPIDNAME)
  4. 如果它未能对新任务进行排队(即 offer返回 false) ,那么它将尝试向线程池添加一个新线程,并将该任务作为其第一个作业。
  5. $(VSInstallDir)
  6. 如果添加新线程失败,那么执行器要么关闭,要么饱和。无论哪种方式,新任务都将使用提供的 RejectedExecutionHandler被拒绝。
$(VSInstallDir _ 150)

固定线程池和缓存线程池之间的主要区别归结为以下三个因素:

  1. 核心池面积
  2. $(VsInstallRoot)
  3. 泳池最大面积
  4. 排队


+-----------+-----------+-------------------+---------------------------------+
| Pool Type | Core Size |    Maximum Size   |         Queuing Strategy        |
+-----------+-----------+-------------------+---------------------------------+
|   Fixed   | n (fixed) |     n (fixed)     | Unbounded `LinkedBlockingQueue` |
+-----------+-----------+-------------------+---------------------------------+
|   Cached  |     0     | Integer.MAX_VALUE |        `SynchronousQueue`       |
+-----------+-----------+-------------------+---------------------------------+


$(VSLANG)

固定线程池


$(VSSKUEDITION) $(VSVersion) 以下是 Excutors.newFixedThreadPool(n)的工作原理:

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
$(WDKBinRoot)

如你所见:

    $(WebBrowserDebuggerDebuggerlype)
  • 线程池大小是固定的。
  • $(WebServiceDebuggerDebuggerlype)
  • 如果需求很大,就不会增长。
  • $(WebServiceDebuggerSQLDebug)
  • 如果线程长时间处于空闲状态,它就不会收缩。
  • $(整个程序优化)
  • 假设所有这些线程都被一些长时间运行的任务占据,并且到达率仍然很高。由于执行器使用的是无界队列,因此它可能会占用堆的很大一部分。不幸的是,我们可能会遇到 OutOfMemoryError

什么时候应该使用其中一种策略? 哪种策略在资源利用方面更好?

$(全程优化可用性工具)

当出于资源管理的目的限制并发任务的数量时,固定大小的线程池似乎是一个很好的选择。

$(全程优化可用性优化)

例如,如果我们要使用一个执行器来处理 Web 服务器请求,一个固定的执行器可以更合理地处理请求突发。

$(全程优化可用性真)

为了更好地进行资源管理,强烈建议使用有界 BlockingQueue<T>实现和合理的 RejectedExecutionHandler创建自定义 ThreadPoolExecutor


$(整个程序优化可用性更新)

缓存线程池


以下是 Executors.newCachedThreadPool()的工作原理:

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
$(windir)

如你所见:

    $(Windows81SdkInstallated)
  • 线程池可以从零线程增长到 Integer.MAX_VALUE
  • $(WindowsAppContainer)
  • 如果任何线程空闲超过1分钟,它可能被终止。因此,如果线程保持太多的空闲,池就会收缩。
  • $(WindowsSDK _ ExecutablePath) $(WindowsSDK _ ExecutablePath _ arm)
  • 如果所有分配的线程在新任务进入时都被占用,那么它将创建一个新线程,因为当另一端没有人接受新任务时,向 SynchronousQueue提供新任务总是会失败!

什么时候应该使用其中一种策略? 哪种策略在资源利用方面更好?

$(WindowsSDK _ ExecutablePath _ arm64) $(WindowsSDK _ ExecutablePath _ x64) $(WindowsSDK _ LibraryPath _ x86) $(WindowsSDK _ MetadataFoundationPath)

当您有大量可预测的短期运行任务时使用它。