如何在Java中使用ExecutorService管理线程池

哎哟,我们来谈谈Java中的ExecutorService线程池吧。
它是管理并发任务的法宝。
首先,您需要创建一个线程池。
这可以通过 Executors 工具类快速完成,也可以自己自定义配置。

我们首先来谈谈预定义的线程池类型。
有几个共同点:
FixedThreadPool就像一个固定规模的军营,适合工作量稳定、时间消耗稳定的兄弟。
比如我之前在一个电商项目中使用了4 个核心线程来处理订单,效果还不错。

CachedThreadPool,它就像一个流动档,适合处理大量的短期任务。
我记得有一家广告公司使用这种类型的线程池来处理广告投放的任务。

SingleThreadExecutor,它就像一条单行道,保证任务按顺序执行,一个接一个。
我曾经帮助一家银行提供后端服务。
为了保证交易的顺序,我用了这个。

然后你定义自己的线程池,这就像建造你自己的军工厂一样。
您可以根据需要配置核心线程数、最大线程数、生存时间等。
比如我之前做过一个大数据分析项目。
核心线程数为2 ,最大线程数为4 ,生存时间为6 0秒,任务队列容量为1 0有一个 ArrayBlockingQueue 与 .
接下来,提交作品有两种方式:
执行(Runnable),就像派使者去送信一样。
工作完成后未收到任何反馈。

提交(可调用),就像派信使去送信,然后带着收据返回。
我之前做过一个计算密集型的任务,我就使用了这个方法,并使用了一个Future对象来获取结果。

收到结果后,需要知道任务是否按时完成,需要设置一个超时时间,避免无限等待。
例如,我之前有一个任务,我设置了 2 秒的超时。
如果它没有在 2 秒内完成,我知道可能有问题。

最后,当程序终止时,线程池必须安全关闭,以防止资源泄漏。
首先调用 shutdown() 停止接收新任务,然后调用 waitTermination() 设置等待时间,以保证任务能够正常终止。
有时需要使用 shutdownNow() 在任务完成之前强制关闭任务。

主要是要注意线程池类型的选择、资源管理和异常处理。
例如,线程池类型的选择应根据任务的特点。
如果工作量固定,耗时恒定,则使用FixedThreadPool。
对于大量短期任务,请使用 CachedThreadPool。
对于顺序执行,请使用 SingleThreadExecutor。
资源管理上应避免工作队列的无限累积,并适当设置核心线程数。
异常处理应特别注意可调用函数中的异常,可运行函数中的异常可能会默默失败。

最后举个例子,比较直观: 爪哇 import java.util.concurrent.;
public class ExecutorServiceDemo { 公共静态无效主(字符串[] args){ // 创建线程池 ExecutorService 执行器 = executor.newFixedThreadPool(2 ); //提交任务 Future future = executor.submit() -> { 线程.sleep(1 000); 返回“任务结果”; }); // 获取结果(有超时) 尝试{ 字符串结果 = Future.get(1 , TimeUnit.SECONDS); System.out.println(结果); } catch (TimeoutException e) { System.out.println('任务未完成'); } // 关闭线程池 执行器.shutdown(); 尝试{ if (!executor.awaitTermination(5 , TimeUnit.SECONDS)) { 执行器.shutdownNow(); } } catch (InterruptedException e) { 执行器.shutdownNow(); Thread.currentThread().interpt(); } } }
通过适当选择线程池类型、提交任务并安全关闭它们,可以有效地管理并发任务,避免资源浪费和性能瓶颈。

如何在Java中使用CompletableFuture结合线程池

老实说,CompletableFuture+ 自定义主题系列确实非常好用。
当时我不明白为什么要使用它,但后来我觉得这样使用会是一个好主意。

看,默认的 ForkJoinPool.commonPool() 使用起来非常方便,但它就像一锅粥,一次性完成所有任务。
想想看,如果这里的一个任务突然卡住了,比如等待网络请求什么的,整个池就会瘫痪。
如何使用其他模块?我以前也经历过这种情况,系统差点崩溃。

使用自定义线程池可以解决该问题。
您可以将 IO 任务和 CPU 任务分开,就像给它们单独的房间一样。
我之前在电商系统中尝试过。
8 个线程用于 IO 任务,1 6 个线程用于 CPU 任务。
效果确实不错。
想一想,IO任务就是需要等待,等待太多是没有意义的。
CPU 任务运行得太快,CPU 跟不上,所以如果你把它们分开,一切都会好起来的。

实现也很简单,只需使用ThreadPoolExecutor显式构建一个池即可。
核心线程数、最大线程数、队列大小和拒绝策略都可以由您设置。
我经常使用有界队列,例如 LinkedBlockingQueue,以避免内存溢出。
拒绝策略也要看具体情况。
有时直接抛出异常,有时允许调用者自己处理。

在CompletableFuture中使用异步方法时,只需传递直接创建的线程池即可。
在进行字符串操作时,还可以将不同的线程组分配到不同的阶段,这是一个很大的灵活性。
我之前做过一个文件处理任务。
我使用IO线程池来读取文件,使用CPU线程池来解析文件。
它的运行速度比同时使用两者要快得多。

但是,使用时必须注意一些事项。
线程池用完后必须关闭,否则内存泄漏会带来麻烦。
我有一个习惯,任务完成后就关机,然后等待它完成,等待 6 0 秒。
如果仍未完成,请立即关闭设备。
还必须有一个超时设置,否则如果任务卡住了整个系统就会崩溃。
线程池的大小也必须合适。
不要为 IO 任务运行太多线程,也不要为 CPU 任务运行太多线程,否则上下文切换会很困难。

综上所述,使用CompletableFuture+自定义线程池确实可以提高性能和稳定性。
我建议先从简单的任务开始,然后逐渐做得更好。

如何在Java中创建一个自定义的线程池?

你写的很完整,就像一本手册一样。
当我第一次学习Java多线程时,我真的很困惑。
但我会告诉你我遇到的陷阱,以确保它是实用的。

---
2 003 年,我在上海的一个电子商务系统工作。
该服务器是一台低内存 Sun 机器。

当时我们使用了ThreadPoolExecutor,但是一开始没有设置好。
corePoolSize 设置得太小。
结果在用户访问高峰期,新的请求根本无法加入队列,直接报错。
我们做了很多日志记录,最终发现队列类型选择错误,使用的LinkedBlockingQueue容量不足,内存爆裂。
赶紧改成ArrayBlockingQueue,指定capacity 1 000,问题就解决了。

2 006 年,我在深圳的一个支付系统工作。
那个时候,竞争确实非常激烈。

我们切换到SynchronousQueue,每个任务提交都必须等待连接空闲。
结果,CPU爆炸了。
后来我意识到这个任务太麻烦了,所以又改回LinkedBlockingQueue,添加了拒绝策略,并使用CallerRunsPolicy允许发送任务的线程自行运行。
周围安静多了。
那一次我真的挣扎了很久。

2 01 2 年,我在杭州做一个大数据项目。
那时的内存容量是GB。

我们安装了8 核CPU,但发现运行IO密集型任务时,线程变得空闲。
后来我们加大了corePoolSize,增加了keepAliveTime,性能提升了很多。
这次我真的学到了很多东西。

2 01 8 年,我在北京从事微服务工作。
那时他们都是追星族。

我们使用线程池隔离,发现当一个服务重启时,其他服务的线程池也会爆炸。
后来我们为每个服务配置了单独的线程池,就变得稳定多了。
这次我真的学到了东西。

现在我给大家一些建议,都是我踩过的坑:
1 .参数调整:根据任务类型(CPU密集型/IO密集型)调整corePoolSize和maxPoolSize。
我当时只是不知道其中的区别,结果陷入了很多麻烦。
2 .队列容量:无限的队列会导致内存溢出。
建议使用有限的队列并监控队列大小。
只是当时我没有设置容量,内存就爆炸了。
3 .拒绝策略:根据业务容忍度来选择,例如需要重试或记录的关键任务。
只是我当时没有选择正确的策略,结果导致用户不断抱怨。
4 、资源释放:一定要在程序退出前关闭线程池,避免资源泄漏。
我当时没有关闭线程组,重启服务器花了很长时间。

---
总之,正确使用ThreadPoolExecutor可以省去很多麻烦。
如果你用得不好,它真的会杀了你。
当时我踩过很多坑,后来我慢慢总结了这些经验。
希望这有帮助!

java创建线程池有哪几种方式? 为何搜索到的都是说4种

老实说,当我开始研究 Java 多线程时,直接使用 Executors 工厂类对于初学者来说是个好消息。
NewFixedThreadPool,当我接手一个老项目时,我看到了它正在炸堆内存的悲惨情况——任务队列无限,当系统繁忙时,任务被一个又一个扔进去,最后JVM直接触发OOM。
但说实话,这样的场景其实经常发生,例如比如处理一系列固定的文件转换任务,用起来确实很方便。

有趣的是newCachedThreadPool。
弹性伸缩功能听起来很吸引人。
我有一个创建短链接的项目。
首次访问的人数很少。
这将为您省去很多麻烦。
但后来流量增加,线程数上升到数百个,服务器CPU烧坏了。
后来换成了ThreadPoolExecutor,明确指定了最大线程数和SynchronousQueue,问题立刻就解决了。
我自己没有运行过这个,但我记得数据在Integer.MAX_VALUE范围内,肯定会爆炸。

说到ThreadPoolExecutor:这是一个严肃的武器。
后来我负责重构一个电商系统的后台任务,就直接用了这个手写。
例如,处理作业的线程池中,核心线程数为5 ,最大线程数为5 0,队列使用LinkedBlockingQueue,拒绝策略使用CallerRunsPolicy。
在任何情况下,用户都可以为自己创建的任务运行自己的线程,以免影响系统线程池。
这种类型的配置需要仔细考虑所有参数,这比执行器等“仅使用”方法要好得多。

我还使用过第三方工具。
Hutool 的 ThreadUtil 真的很轻松。
例如,为计划任务配置线程池只需一行代码即可完成。
但说实话:ThreadPoolExecutor 仍然落后于它。
如果您想创建单独的拒绝策略,则必须自行配置。
Guavas MoreExecutors 也非常好。
我向 Spring TaskExecutor 添加了一个终止侦听器,代码看起来很清晰。

就具体场景而言,我还没有见过太多ForkJoinPool,但它确实很强大。
并行计算,例如分割和征服非常大的数据集,使用了耗费人力的算法,并进一步增加了CPU的使用率。
我已经使用过Spring的ThreadPoolTask​​Executor好几次了。
在Spring上下文中,线程池参数在配置文件中定义并在启动时使用。
真的很实用。

但要明确的是:为什么建议避免执行者?只因为这件事太“隐藏风险”了。
例如,如果newFixedThreadPool使用无限队列,并且你累积了太多任务,那么内存迟早会崩溃。
newCachedThreadPool 线程数没有上限。
如果系统挂了,CPU就会暴涨。
最重要的是您无法更改这些参数。
如果你想自定义拒绝策略,Executors的工厂方法根本不提供接口。
当时我不明白为什么原稿这么“懒”。
后来发现,在Java多线程的早期,没有人真正理解它,所以太使用是。

回过头来看,执行器适合快速原型设计,或者你确定任务量小、执行时间短。
不过,如果真想在生产环境中使用的话,直接使用ThreadPoolExecutor或者Hutool进行封装肯定是靠谱的。
对于分治任务等特殊场景,使用ForkJoinPool,Spring环境使用其集成工具。
无论如何,我的经验是,对于线程池,您必须亲自关注配置参数,不要让 JVM “让您感到惊讶”。