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

概括地说,使用 ExecutorService 管理线程池有四个步骤:构建池、处理任务、获取结果和关闭池。
但困难在于选择正确的类型并防止陷阱。

我们先来说说最重要的事情。
Pool的构建有两种类型:Executor的惰性创建和ThreadPoolExecutor的手动参数调整。
去年我们跑了一个电商闪购项目,使用FixedThreadPool就炸了,因为并发高的时候队列就爆炸了——行话里叫雪崩效应。
事实上,前面的一小部分延迟会导致后面的大量延迟。
然后我发现有些不对劲,所以我改用ArrayBlockingQueue来稳定它。
还有一点就是CachedThreadPool适合短任务,但是线程的频繁创建和销毁对性能的消耗很大。
我们测得,并发数在2 000左右时,CPU的利用率会跳到9 0%。
还有一个重要的细节,比如核心线程数。
对于CPU密集型项目,建议增加核心数+1 对于IO密集型项目,可以多开一些。
去年的爬虫项目如果有2 0个核就完美了。

一开始我以为调度任务很简单,execute和submit都可以,但是后来发现不对劲——submit可以得到返回值,但是Future.get()会阻塞线程。
去年我们有一个报表生成任务,我们等了1 0分钟,没有加超时。
最后,我们不得不狠狠地杀死这个进程。
等等,还有别的事。
未来的异常必须具体处理。
可以调用的异常是ExecutionException。
很多人并不关心这个。

最后的警告:当你离开池时,不要直接调用Thread.stop()。
一起使用 shutdown+wait 终止。
如果没有,请再次 shutdownNow() - 但请记住,shutdownNow 将中断所有正在运行的任务,并且数据可能会丢失。
我认为值得尝试自定义ThreadFactory并打印异常,这可以节省大量故障排除时间。

什么是线程池,如何使用,为什么要用

说实话,我第一次接触线程池是在运行电商闪购系统的时候。
由于当时的请求很紧急,所以后台接口直接使用new Thread,开启多个线程。
结果,系统一开始运行,JVM就直接内存溢出。
我印象特别深刻。
那天晚上我调试了它,终于意识到线程被创建和销毁如此频繁,以至于CPU不得不频繁地切换事物,这浪费了资源。

有趣的是,线程池的核心解决了两个问题:第一;创建线程太耗时(其实运行线程的成本也不小);其次,线程管理非常困难。
例如.NET,实际上运行在System.Threading.ThreadPool.QueueUserWorkItem上。
您转储作业并在内部有一个线程池来保存工作线程。
当任务出现时,它们被指定为空闲线程,无需手动创建和删除它们,并且使用后可以自动回收。

说白了,线程池非常适合“工作负载稍大”的情况。
以我们的闪购系统为例。
用户发送请求,处理后返回。
这种短作业非常适合扔进棉花池。
后来改用ThreadPool后,内存溢出问题就消失了。
但如果你正在做下载G文件这样的长期工作,使用线程池比直接运行长线程更能节省问题;这是因为线程切换的开销。

据我所知,Windows系统上默认的线程池线程数在5 00-1 000左右;但是,确切的值必须取决于系统配置。
在Linux方面,我记得有一些关于核心数量的内容。
我自己没有做过,所以建议查看官方文档的最新版本。
但说实话,线程池并不是万能的。
这取决于场景。