并发是我们(开发人员)在日常工作中可能面临的最复杂的问题之一。此外,这也是我们在解决日常问题时可能面临的最常见问题之一。这两个因素的结合真正使并发和多线程成为软件工程师可能遇到的最危险的问题。

DZone 编码 Java 语言(一种计算机语言,尤用于创建网站) 线程、线程池和执行器:Java中的多线程处理
线程、线程池和执行器:Java中的多线程处理
对Java Executor接口如何实现的详细描述集中在所有执行器及其用例之间的关系上。
经过 Bartłomiej Żyliński user avatar 巴尔托梅伊·żyliński DZone Core核心部分 · 24年2月13日 · 辅导的
喜欢 (2)
评论(0)
救援
自录音再现装置发出的高音
分享
3.3K视图
加入DZone社区并获得完整的会员体验。 免费加入
并发是我们(开发人员)在日常工作中可能面临的最复杂的问题之一。此外,这也是我们在解决日常问题时可能面临的最常见问题之一。这两个因素的结合真正使并发和多线程成为软件工程师可能遇到的最危险的问题。
Multi-thread processing in Java image
此外,用低级抽象解决并发问题可能是一个相当大的认知挑战,并导致复杂的、不确定的错误。这就是为什么大多数语言引入了更高级别的抽象,使我们能够相对轻松地解决与并发相关的问题,而不必花时间调整低级别的开关。
在本文中,我将深入探讨Java标准库提供的这些抽象,即ExecutorService接口及其实现。我还希望这篇文章成为我下一篇关于Java流基准测试的文章的切入点。
在此之前,让我们快速回顾一下流程和线索:
什么是过程?
它是可以在我们的计算机内部独立执行的最简单的单元。多亏了这些流程,我们可以将机器内部完成的工作分成更小、更模块化、更易管理的部分。
这种方法使我们能够更专注于特定的部分,从而提高性能。像这样的分割还可以充分利用我们CPU内置的多个内核。
一般来说,每个进程都是一个特定程序的实例——例如,我们正在运行的Java进程就是一个JVM程序实例。
此外,每个进程都植根于操作系统内部,有自己独特的资源、访问和行为(程序代码)——与我们的应用程序用户类似。
每个进程可以有几个线程(至少在大多数操作系统中),它们共同完成进程分配的共同任务。
什么是线程?
它可以被视为我们的代码的一个分支,具有一组特定的指令,与应用程序的其余部分并行执行。线程支持在单个进程中同时执行多个指令序列。
在软件层面上,我们可以区分两种类型的线程:
内核(系统)线程:线程由操作系统直接管理。操作系统内核执行线程创建、调度和管理。
应用程序(用户)线程:线程由线程库或运行时环境在用户级别管理,独立于操作系统。它们对操作系统内核不可见。因此,操作系统就像管理单线程进程一样管理它们。
在这里,我将主要关注应用程序线程。我还会提到与CPU相关的线程。这些是硬件线程,这是我们CPU的一个特点。它的数字描述了我们的CPU同时处理多个线程的能力。
原则上,线程可以比进程更轻松地共享资源。进程中的所有线程都可以访问其父进程拥有的所有数据。
此外,每个线程都可以将其数据称为更常见的线程本地变量(或者,对于Java,更新和更推荐的变量)限定范围的值).此外,线程之间的切换比进程之间的切换容易得多。
什么是线程池?
线程池是一个比线程和进程更具体的术语。它与应用程序线程相关,并描述了我们可以在应用程序内部使用的一组线程。
它的工作基于一个非常简单的行为。我们只是从池中一个接一个地取出线程,直到池变空。就是这样。然而,这个规则有一个额外的假设,特别是一旦线程的任务完成,它们将返回到池中。
当然,应用程序可能有多个线程池,事实上,我们的线程池越专业,对我们越有利。通过这种方法,我们可以限制应用程序中的争用并消除单点故障。如今的行业标准是至少有一个单独的线程池用于数据库连接。
线程、线程池和Java
在以前的Java老版本中Java 21—应用程序中使用的所有线程都绑定到CPU线程。因此,它们又贵又重。
如果偶然(或有意),您将在Java应用程序中产生太多线程;例如,通过调用“new Thread()".那么您可能会很快耗尽资源,并且您的应用程序的性能会迅速下降—除其他外,CPU需要进行大量的上下文切换。
项目织机作为Java 21版本的一部分,旨在通过向Java标准库中添加虚拟线程(即不绑定到CPU线程的线程,即所谓的绿色线程)来解决这一问题。如果您想了解更多关于Loom及其给Java线程带来的变化,我推荐这篇文章.
在Java中,线程池的概念是由ThreadPoolExecutor—表示有限大小的线程池的类,其上限由maximumPoolSize类构造函数的参数。
顺便说一下,我想补充一点,这个执行器在更复杂的执行器中作为内部线程池进一步使用。
遗嘱执行人、遗嘱执行人服务和遗嘱执行人
在我们开始描述更复杂的执行器接口实现之前,该接口使用了一个ThreadPoolExecutor,我还想回答另一个问题:即Executor和ExecutorService他们自己?
执行者
这Executor是一个只公开一个使用以下签名执行的方法的接口:void execute(Runnable command)。该接口旨在描述一个非常简单的操作——确切地说,实现它的类可以执行这样的操作:执行提供的runnable。该接口的目的是提供一种将任务提交与任务运行机制相分离的方法。
执行服务
这ExecutorService是另一个接口,是Executor界面。它的合约比Executor.
大约有13个方法需要重写,如果我们决定实现它,它的主要目的是通过在Java Future中包装异步任务来帮助管理和运行这些任务。
此外,在ExecutorService扩展了Autocloseable实施。然而,我打赌你的机器在达到最大值之前就会停机。ExecutorService如果你想知道更多关于这个数字是极限背后的推理,有一个非常好的JavaDoc描述了这一点。它位于声明
班级。我将给出一个提示,它与
Executors保持其状态。
正在生成ThreadPoolExecutor
Executors类总共为您提供了6种方法来生成
。我将在两个包中描述它们,因为这是它们的设计工作方式。ExecutorService这些方法创建一个固定大小的线程池—大小core和max相等。此外,您可以传递一个
作为参数,如果您不想使用标准库中的默认参数。
ThreadPoolExecutor
ForkJoinPool
ScheduledThreadPoolExecutor
ThreadPerTaskExecutor
批量接下来的两种方法:Executors上述方法通过设置ExecutorService:
DelegatedExecutorService
DelegatedScheduledExecutorService
AutoShutdownDelegatedExecutorService

;第二个版本类似于
,允许传递自定义的TheadPoolExecutor最后两种方法:
这两种方法都创建一个使用单个线程的线程池。此外,方法会产生一个ThreadPoolExecutor那是包在corePoolSize和maximumPoolSize。它只公开corePoolSize:没有
具体方法是有的。maximumPoolSize此外,借助于
并且当它变成ThreadPoolExecutor幻影可达BlockingQueueThreadPoolExecutor添加新任务
添加由执行的新任务的默认方式是
是使用submit方法的一个版本。另一方面,也可以使用corePoolSize方法从execute直接接口。但是,这不是推荐的方式。
使用corePoolSize方法将返回void,而不是future:这意味着您对其执行的控制较少,因此选择权在您。当然,这两种方法都将触发上述线程池的所有逻辑。execute相对isRunning© && workQueue.offer(command).
福克琼普尔addWorker(null, false);.
是完全独立的
!isRunning(recheck) && remove(command)
其主要卖点是工作窃取概念的实现。RejectedExecutionException: reject(command);.
窃取工作是一个相当复杂的概念,值得专门发布一篇博文。然而,它可以用足够高的抽象来合理地简单描述——池中的所有线程都试图执行提交给池的任何任务,无论其原始所有者是谁。else if (!addWorker(command, false)).
这就是为什么这个概念被称为工作窃取,因为线程“窃取”了彼此的工作。从理论上讲,这种方法应该会带来显著的性能提升,尤其是在提交的任务很小或产生其他子任务的情况下。RejectedExecutionException在不久的将来,我计划发布一个单独的帖子,专门讨论窃取工作和
Task X rejected from java.util.concurrent.ThreadPoolExecutor@3a71f4dd[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

。在此之前,您可以阅读更多关于ThreadPoolExecutor在这里偷东西Task4ForkJoinPool行为Task5这个执行器是Java 7中引入的Java Fork/Join框架的一部分,Java 7是一组旨在通过利用工作窃取的概念来添加更有效的任务并行处理的类。目前,该框架广泛用于以下方面Task6和溪流。Task6如果您想充分利用Task4ForkJoinPool行为Task5,我建议从整体上熟悉Fork/Join框架。然后,尝试将处理此类任务的方法转换为更符合Fork/Join需求的方法。
在进入Fork/Join框架之前,请进行基准测试和性能测试,因为潜在的收益可能不如预期的那么好。
此外,中还有一个表格corePoolSize描述用户应该使用的交互方法的Java文档Task6才能得到最好的结果。

DZone 编码 Java 语言(一种计算机语言,尤用于创建网站) 线程、线程池和执行器:Java中的多线程处理
线程、线程池和执行器:Java中的多线程处理
对Java Executor接口如何实现的详细描述集中在所有执行器及其用例之间的关系上。
经过 Bartłomiej Żyliński user avatar 巴尔托梅伊·żyliński DZone Core核心部分 · 24年2月13日 · 辅导的
喜欢 (2)
评论(0)
救援
自录音再现装置发出的高音
分享
3.3K视图
加入DZone社区并获得完整的会员体验。 免费加入
并发是我们(开发人员)在日常工作中可能面临的最复杂的问题之一。此外,这也是我们在解决日常问题时可能面临的最常见问题之一。这两个因素的结合真正使并发和多线程成为软件工程师可能遇到的最危险的问题。
Multi-thread processing in Java image
此外,用低级抽象解决并发问题可能是一个相当大的认知挑战,并导致复杂的、不确定的错误。这就是为什么大多数语言引入了更高级别的抽象,使我们能够相对轻松地解决与并发相关的问题,而不必花时间调整低级别的开关。
在本文中,我将深入探讨Java标准库提供的这些抽象,即ExecutorService接口及其实现。我还希望这篇文章成为我下一篇关于Java流基准测试的文章的切入点。
在此之前,让我们快速回顾一下流程和线索:
什么是过程?
它是可以在我们的计算机内部独立执行的最简单的单元。多亏了这些流程,我们可以将机器内部完成的工作分成更小、更模块化、更易管理的部分。
这种方法使我们能够更专注于特定的部分,从而提高性能。像这样的分割还可以充分利用我们CPU内置的多个内核。
一般来说,每个进程都是一个特定程序的实例——例如,我们正在运行的Java进程就是一个JVM程序实例。
此外,每个进程都植根于操作系统内部,有自己独特的资源、访问和行为(程序代码)——与我们的应用程序用户类似。
每个进程可以有几个线程(至少在大多数操作系统中),它们共同完成进程分配的共同任务。
什么是线程?
它可以被视为我们的代码的一个分支,具有一组特定的指令,与应用程序的其余部分并行执行。线程支持在单个进程中同时执行多个指令序列。
在软件层面上,我们可以区分两种类型的线程:
内核(系统)线程:线程由操作系统直接管理。操作系统内核执行线程创建、调度和管理。
应用程序(用户)线程:线程由线程库或运行时环境在用户级别管理,独立于操作系统。它们对操作系统内核不可见。因此,操作系统就像管理单线程进程一样管理它们。
在这里,我将主要关注应用程序线程。我还会提到与CPU相关的线程。这些是硬件线程,这是我们CPU的一个特点。它的数字描述了我们的CPU同时处理多个线程的能力。
原则上,线程可以比进程更轻松地共享资源。进程中的所有线程都可以访问其父进程拥有的所有数据。
此外,每个线程都可以将其数据称为更常见的线程本地变量(或者,对于Java,更新和更推荐的变量)限定范围的值).此外,线程之间的切换比进程之间的切换容易得多。
什么是线程池?
线程池是一个比线程和进程更具体的术语。它与应用程序线程相关,并描述了我们可以在应用程序内部使用的一组线程。
它的工作基于一个非常简单的行为。我们只是从池中一个接一个地取出线程,直到池变空。就是这样。然而,这个规则有一个额外的假设,特别是一旦线程的任务完成,它们将返回到池中。
当然,应用程序可能有多个线程池,事实上,我们的线程池越专业,对我们越有利。通过这种方法,我们可以限制应用程序中的争用并消除单点故障。如今的行业标准是至少有一个单独的线程池用于数据库连接。
线程、线程池和Java
在以前的Java老版本中Java 21—应用程序中使用的所有线程都绑定到CPU线程。因此,它们又贵又重。
如果偶然(或有意),您将在Java应用程序中产生太多线程;例如,通过调用“new Thread()".那么您可能会很快耗尽资源,并且您的应用程序的性能会迅速下降—除其他外,CPU需要进行大量的上下文切换。
项目织机作为Java 21版本的一部分,旨在通过向Java标准库中添加虚拟线程(即不绑定到CPU线程的线程,即所谓的绿色线程)来解决这一问题。如果您想了解更多关于Loom及其给Java线程带来的变化,我推荐这篇文章.
在Java中,线程池的概念是由ThreadPoolExecutor—表示有限大小的线程池的类,其上限由maximumPoolSize类构造函数的参数。
顺便说一下,我想补充一点,这个执行器在更复杂的执行器中作为内部线程池进一步使用。
遗嘱执行人、遗嘱执行人服务和遗嘱执行人
在我们开始描述更复杂的执行器接口实现之前,该接口使用了一个ThreadPoolExecutor,我还想回答另一个问题:即Executor和ExecutorService他们自己?
执行者
这Executor是一个只公开一个使用以下签名执行的方法的接口:void execute(Runnable command)。该接口旨在描述一个非常简单的操作——确切地说,实现它的类可以执行这样的操作:执行提供的runnable。该接口的目的是提供一种将任务提交与任务运行机制相分离的方法。
执行服务
这ExecutorService是另一个接口,是Executor界面。它的合约比Executor.
大约有13个方法需要重写,如果我们决定实现它,它的主要目的是通过在Java Future中包装异步任务来帮助管理和运行这些任务。
此外,在ExecutorService扩展了Autocloseable实施。然而,我打赌你的机器在达到最大值之前就会停机。ExecutorService如果你想知道更多关于这个数字是极限背后的推理,有一个非常好的JavaDoc描述了这一点。它位于声明
班级。我将给出一个提示,它与
Executors保持其状态。
正在生成ThreadPoolExecutor
Executors类总共为您提供了6种方法来生成
。我将在两个包中描述它们,因为这是它们的设计工作方式。ExecutorService这些方法创建一个固定大小的线程池—大小core和max相等。此外,您可以传递一个
作为参数,如果您不想使用标准库中的默认参数。
ThreadPoolExecutor
ForkJoinPool
ScheduledThreadPoolExecutor
ThreadPerTaskExecutor
批量接下来的两种方法:Executors上述方法通过设置ExecutorService:
DelegatedExecutorService
DelegatedScheduledExecutorService
AutoShutdownDelegatedExecutorService
到
Dependency graph between classes
;第二个版本类似于
,允许传递自定义的TheadPoolExecutor最后两种方法:
这两种方法都创建一个使用单个线程的线程池。此外,方法会产生一个ThreadPoolExecutor那是包在corePoolSize和maximumPoolSize。它只公开corePoolSize:没有
具体方法是有的。maximumPoolSize此外,借助于
并且当它变成ThreadPoolExecutor幻影可达BlockingQueueThreadPoolExecutor添加新任务
添加由执行的新任务的默认方式是
是使用submit方法的一个版本。另一方面,也可以使用corePoolSize方法从execute直接接口。但是,这不是推荐的方式。
使用corePoolSize方法将返回void,而不是future:这意味着您对其执行的控制较少,因此选择权在您。当然,这两种方法都将触发上述线程池的所有逻辑。execute相对isRunning© && workQueue.offer(command).
福克琼普尔addWorker(null, false);.
是完全独立的
!isRunning(recheck) && remove(command)
其主要卖点是工作窃取概念的实现。RejectedExecutionException: reject(command);.
窃取工作是一个相当复杂的概念,值得专门发布一篇博文。然而,它可以用足够高的抽象来合理地简单描述——池中的所有线程都试图执行提交给池的任何任务,无论其原始所有者是谁。else if (!addWorker(command, false)).
这就是为什么这个概念被称为工作窃取,因为线程“窃取”了彼此的工作。从理论上讲,这种方法应该会带来显著的性能提升,尤其是在提交的任务很小或产生其他子任务的情况下。RejectedExecutionException在不久的将来,我计划发布一个单独的帖子,专门讨论窃取工作和
Task X rejected from java.util.concurrent.ThreadPoolExecutor@3a71f4dd[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Simplified visualization of ThreadPoolExecutor’s internal state
。在此之前,您可以阅读更多关于ThreadPoolExecutor在这里偷东西Task4ForkJoinPool行为Task5这个执行器是Java 7中引入的Java Fork/Join框架的一部分,Java 7是一组旨在通过利用工作窃取的概念来添加更有效的任务并行处理的类。目前,该框架广泛用于以下方面Task6和溪流。Task6如果您想充分利用Task4ForkJoinPool行为Task5,我建议从整体上熟悉Fork/Join框架。然后,尝试将处理此类任务的方法转换为更符合Fork/Join需求的方法。
在进入Fork/Join框架之前,请进行基准测试和性能测试,因为潜在的收益可能不如预期的那么好。
此外,中还有一个表格corePoolSize描述用户应该使用的交互方法的Java文档Task6才能得到最好的结果。
Final state: ThreadPoolExecutor
情况下的关键参数
是并行度—它描述了池将使用的工作线程的数量。默认情况下,它等于CPU上可用处理器的数量。在大多数情况下,这至少是一个足够的设置,我建议在没有进行适当的性能测试之前不要更改它。请记住Java线程是corePoolSize和maximumPoolSize受CPU限制,
我们很快就会耗尽处理能力来完成任务。请记住Java线程是maximumPoolSize正在生成ForkJoinPool实例Integer.MAX_VALUE类提供了两种方法来生成ThreadPoolExecutor第一个创建了
默认并行度(可用处理器的数量),而第二种方式让我们可以自己指定并行度级别。ThreadPoolExecutor此外,ThreadPoolExecutor卵
下面是FIFO队列,而默认设置为
本身(例如,通过新的ThreadPoolExecutor)是LIFO。使用后进先出法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
是不可能的。尽管如此,您可以通过使用threadFactory的构造函数参数
Executors.newFixedThreadPool(2);
Executors.newFixedThreadPool(2, Executors.defaultThreadFactory());
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
使用FIFO设置时maxPollSize可能更适合从未加入的任务—例如,可调用或可运行的用途。Integer.Max ScheduledThreadPoolExecutor FixThreadPool在经典之上添加一层ThreadFactory.
Executors.newCachedThreadPool();
Executors.newCachedThreadPool(Executors.defaultThreadFactory());
并允许调度任务。
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
它支持三种类型的调度:ThreadPoolExecutors安排一次固定延迟-AutoShutdownDelegatedExecutorService每个时间单位的时间表-ExecutorService执行之间有固定延迟的调度-ThreadPoolExecutor当然,您也可以使用“普通的”ExecutorService API。请记住,在这种情况下,方法submit和execute等同于调用
日程安排Cleanable延迟为0的方法—立即执行所提供的任务。如同.
延伸
,其实现的某些部分与classic中的相同ThreadPoolExecutor。然而,它使用自己的任务实现execute和队列:Executor总是创建固定大小的
作为它的底层线程池,所以execute总是平等的。
Runnable task = () -> System.out.print(“test”);
Future<?> submit = executorService.submit(task);
但是,在的实现中隐藏着一两个问题
executorService.execute(task);
首先,如果两个任务被安排同时运行(或最终被安排同时运行),它们将根据提交时间以FIFO的方式执行。
ForkJoinPool下一个问题是第一个问题的逻辑结果。没有真正的保证特定的任务会在某个时间点执行——例如,它可能会在队列中从上一行开始等待。ExecutorService最后但同样重要的是,如果由于某种原因两个任务的执行相互重叠,线程池保证第一个任务的执行将“先于”后一个任务执行。本质上,以FIFO的方式,即使任务是从不同的线程调用的。
正在生成计划的ThreadPoolExecutor
类为我们提供了五种方法来生成
。它们的组织方式类似于ForkJoinFramework使用固定数量的线程:第一种方法允许我们创建一个.
具有特定数量的螺纹。第二种方法增加了可用性以传递
选择。CompletableFuture使用单线程:
创建一个ForkJoinPool谁的基础
只有一个线程。事实上,这里我们产生了
,它使用ForkJoinPool作为委托,所以最终,基础的ForkJoinPool委托的只有一个线程。

生成的最后一种方法ForkJoinPool是通过使用:
此方法允许您包装自己的与。。。接触—中的一个私有静态类
班级。这个方法只公开了
这Executors界面。ForkJoinPool:
Executors.newWorkStealingPool();
Executors.newWorkStealingPool(2);
在某种程度上,我们可以将其视为封装助手。在您的实现中可以有多个公共方法,但是当您用委托包装它时,所有这些方法对用户都是隐藏的。forkJoinPoll我不太喜欢这种封装方法。应该是执行的问题。然而,也许我遗漏了所有代表的一些其他重要用例。
threadpertasktexecutorExecutors它是Java标准库的最新补充之一,并且ForkJoinPool班级。它不是一个线程池实现,而是一个线程生成器。顾名思义,每个提交的任务都有自己的线程与其执行绑定在一起,线程在任务处理开始时启动。ForkJoinPool为了实现这样的行为,这个执行器使用它自己的自定义Future实现,即,ForkJoinPool(2)。由这些执行器创建的线程的生命周期大致如下:Executors线程是在asyncMode已创建。ForkJoinPool线程只有在由
时线程被中断ForkJoinPool被打断了。
线程在上停止
ScheduledThreadPoolExecutor完成。ThreadPoolExecutor此外,如果
无法为特定任务启动新线程,并且在此过程中没有引发异常,则
将schedule
此外scheduleAtFixedRate
保存一组上行线程。每次线程启动时,它都会被添加到集合中。当线程停止时,它将从集合中移除。scheduleWithFixedDelay
然后,您可以使用该集合来跟踪在给定时间通过方法。
正在生成ThreadPerTaskExecutorScheduledThreadPoolExecutor类公开了生成此的两种方法ThreadPoolExecutor。我会说一个比另一个更值得推荐。让我们从不推荐的开始:ThreadPoolExecutor上述方法产生ScheduledFutureTask使用提供的线程工厂。至少我不建议这样做的原因是DelayedWorkQueue.
threadpertasktexecutorScheduledThreadPoolExecutor将在普通的老式Java CPU绑定线程上运行。ThreadPoolExecutor在这种情况下,如果通过corePoolSize和MaxPoolSize,您很容易耗尽应用程序的处理能力。
当然,没有什么能阻止你做下面的“把戏”和使用ScheduledThreadPoolExecutor.
虚拟线程
总之。
但是,当您可以简单地使用以下内容时,没有理由这样做:
的这个实例
这Executors将充分利用Java 21的虚拟线程。这样的设置还会大大增加您的ScheduledThreadPoolExecutor将能够在处理能力耗尽之前处理它。ThreadPoolExecutor.
ScheduledThreadPoolExecutor摘要
Executors.newScheduledThreadPool(2);
Executors.newScheduledThreadPool(2, Executors.defaultThreadFactory());
如您所见,Java提供了一组不同的执行器,从classicScheduledThreadPoolExecutor通过以下方式实施ThreadFactory到更复杂的如
ScheduledThreadPoolExecutor,它充分利用了Java 21的一个特性——虚拟线程。
Executors.newSingleThreadScheduledExecutor();
Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory());
更重要的是,每个SingleThreadScheduledExecutor实现有其独特的特点:ThreadExecutorPool :经典DelegatedScheduledExecutorService履行ScheduledThreadPoolExecutor:偷工作ThreadExecutorPool:定期安排任务
:虚拟线程的使用以及在单独的短期线程中运行任务的可能性ScheduledThreadPoolExecutor尽管存在差异,但所有执行器都有一个定义性特征:它们都公开了一个API,使多个任务的并发处理变得更加容易。
Executors.unconfigurableScheduledExecutorService(new DummyScheduledExecutorServiceImpl());
我希望这些知识在将来的某个时候会对你有用。谢谢你的时间。ScheduledExecutorService注:感谢米歇尔·格拉博斯基和克日什托夫·阿特拉西克的评论。DelegatedScheduledExecutorService线程池Executors执行器(软件)ScheduledExecutorServiceJava语言
处理
经许可在DZone出版
点击这里查看原文。
DZone贡献者所表达的意见是他们自己的意见。Executors合作伙伴资源
评论ThreadBoundFuture关于我们
关于DZoneFuture发送反馈
职业Executor.
网站地图Future通知
用DZone做广告Future在DZONE上投稿
文章提交指南Executor成为贡献者Executor核心程序RejectedExecutionException.
参观作家区ThreadPerTaskExecutor合法的
服务条款Executor隐私策略threadCount()联系我们
希尔周边大道3343号
这Executors套房100Executor田纳西州纳什维尔37211
Executors.newThreadPerTaskExecutor(Executors.defaultThreadFactory());
support@dzone.comThreadPerTaskExecutor让我们成为朋友:ThreadPerTaskExecutor将在普通的老式Java CPU绑定线程上运行。
在这种情况下,如果通过Executor,您很容易耗尽应用程序的处理能力。
当然,没有什么能阻止你做下面的“把戏”和使用虚拟线程总之。
Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory());
但是,当您可以简单地使用以下内容时,没有理由这样做:
Executors.newVirtualThreadPerTaskExecutor();
的这个实例ThreadPerTaskExecutor将充分利用Java 21的虚拟线程。这样的设置还会大大增加您的Executor将能够在处理能力耗尽之前处理它。
摘要
如您所见,Java提供了一组不同的执行器,从classicThreadPool通过以下方式实施ThreadPoolExecutor到更复杂的如ThreadPerTaskExecutor,它充分利用了Java 21的一个特性——虚拟线程。
更重要的是,每个Executor实现有其独特的特点:
ThreadPoolExecutor:经典ThreadPool履行
ForkJoinPool:偷工作
ScheduledThreadPoolExecutor:定期安排任务
ThreadPerTaskExecutor:虚拟线程的使用以及在单独的短期线程中运行任务的可能性
尽管存在差异,但所有执行器都有一个定义性特征:它们都公开了一个API,使多个任务的并发处理变得更加容易。
我希望这些知识在将来的某个时候会对你有用。谢谢你的时间。
本文指出并发和多线程是开发中复杂且常见的问题,介绍了Java标准库中ExecutorService接口及其实现。回顾了进程、线程、线程池的概念,详细阐述了多种执行器,如ThreadPoolExecutor、ForkJoinPool等,各有特点,都能让多任务并发处理更简单。

139

被折叠的 条评论
为什么被折叠?



