网站首页 > java教程 正文
概述
谈到线程池, Java中提供了很多选择。其中, cached thread pool 和 thread thread pool 是最常用到的两个。在本教程中,我们将从底层源码,来分析比较他们是如何工作的,以及有什么不同。
Cached Thread Pool
让我们来看看 Java 在调用 Executors.newCachedThreadPool ()时是如何创建缓存线程池的:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从上面的代码中, 我们看到了newCachedThreadPool是通过SynchronousQueue来实现的,意思就是,假设有一个新的任务,如果队列上有一个空闲线程在等待,则任务生成器将任务移交给该线程。 否则,由于队列总是满的,执行程序将创建一个新线程来处理该任务。
它使用了一种叫做“同步切换”的策略,当且仅当另一个线程能同步接收该任务时,才可以对该项进行排队。 换句话说,SynchronousQueue 不能容纳任何任务。
缓存池从零个线程开始,可能会增长为 Integer.MAX VALUE 线程。 实际上,缓存线程池的唯一限制是可用的系统资源,即系统可打开的线程数。
当然,为了更好的管理资源,缓存的线程池将删除空闲一分钟的线程。
Cached Thread Pool用例
Cached Thread Pool会将线程缓存(因此而得名)一段时间,以便为其他任务重新使用它们。 因此,当我们处理合理数量的短期任务时,该线程池能运行的很好。
这里有两个关键点, 合理数量和短期。为了说明这一点,我们用一段伪代码举个例子:
Callable<String> task = () -> {
long oneHundredMicroSeconds = 100_000;
long startedAt = System.nanoTime();
while (System.nanoTime() - startedAt <= oneHundredMicroSeconds);
return "Done";
};
cachedPool = Executors.newCachedThreadPool();
tasks = IntStream.rangeClosed(1, 1_000_000).mapToObj(i -> task).collect(toList());
result = cachedPool.invokeAll(tasks);
在这里,我们将提交100万个任务,每个任务需要100微秒完成。这将导致大量线程转换为不合理的内存使用,更糟糕的是,大量 CPU 上下文切换。 这两种异常情况都会严重影响整体性能。
因此,当执行时间不可预测时,或者数量比较大的任务时,我们应该避免使用这个线程池。
Fixed Thread Pool
让我们来看一下固定线程池源码中是如何实现的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
这里,固定线程池使用了LinkedBlockingQueue来实现。
与缓存的线程池相反,这个线程池使用的是一个具有固定数量的,没有过期时间的无界队列。 因此,固定线程池会尝试使用固定数量的线程来执行传入的任务,而不是不断增加线程数量。 当所有线程都处于忙碌状态时,执行器将为新任务排队。 这样,我们就可以更好地控制程序的资源消耗。
因此,固定线程池更适合于执行时间不可预测的任务。
cached/fixed Thread Pool 共同的缺点
到目前为止,我们只列举了缓存线程池和固定线程池之间的区别。以及他们适合使用的场景。
但是两者都有一个共同的缺点,当有大量任务时,不管是cached thread pool 通过增加线程数量来处理,还是fixed Thread Pool对新任务进行排队处理,都会消耗大量内存来创建线程或排队任务。 更糟糕的是,缓存的线程池还会导致大量的处理器上下文切换。
所以,为了更好地控制资源消耗,强烈建议创建一个自定义 ThreadPoolExecutor:
boundedQueue = new ArrayBlockingQueue<Runnable>(1000);
new ThreadPoolExecutor(10, 20, 60, SECONDS, boundedQueue, new AbortPolicy());
在这里,我们的线程池可以有多达20个线程,并且只能将多达1000个任务排队。 此外,当它不能接受任何更多的负载时,它将简单地抛出一个异常。
总结
在本教程中,我们了解了 JDK 源代码,看到了不同的 executor 在底层是如何工作的。 然后,我们比较了固定线程池和缓存线程池,以及他们适合的使用场景。最后,我们尝试使用自定义线程池来解决这些线程池由于自身设计问题,导致的资源消耗问题。
- 上一篇: JVM简介—1.Java内存区域
- 下一篇: 13 如何利用缓存实现万级并发扣减
猜你喜欢
- 2025-01-01 Java开发者的福音 - Hutool工具(缓存组件)
- 2025-01-01 一文搞懂JAVA 中的引用
- 2025-01-01 Java并发编程(20)CPU处理器中高速缓存的数据结构
- 2025-01-01 Java 缓冲输入输出是什么?怎么用?
- 2025-01-01 网站慢?试试这个Java实时缓存高招!
- 2025-01-01 追求极致性能,Java高速缓存 Caffeine
- 2025-01-01 实战派 | Java项目中玩转Redis6.0客户端缓存
- 2025-01-01 13 如何利用缓存实现万级并发扣减
- 2025-01-01 JVM简介—1.Java内存区域
- 2025-01-01 不同业务场景该如何选择缓存的读写策略?
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)