java并发编程实战 第八章 线程池的使用
一、在任务与执行策略直接的隐性耦合
Executor框架可以将任务的提交与任务的执行策略解耦。
有些类型的任务需要明确地指定执行策略,包括:
- 依赖性任务 大多数行为正确的任务都是独立的:他们不依赖于其他任务的执行时序、执行结果或其他效果。当在线程池中执行独立的任务时,可以随意修改线程池大小和配置。如果提交给线程池的任务需要依赖其他任务,那就隐含的给执行策略带来了约束,此时必须小心维持执行策略,一般产生活跃性问题。
- 使用线程封闭机制的任务 与线程池相比,但线程的Executor能够对并发性作出更强的承诺,他们能确保任务不糊并发的执行,使你能够放宽代码对线程安全的要求。对象可以封闭在线程中,使得在线程执行任务访问对象时不需要同步。使用场景:任务执行Executor是单线程的
- 对响应时间敏感的任务 GUI应用程序对响应时间敏感,需要用户点击按钮后尽可能快反馈结果。如果一个运行时间较长的任务提交到单线程中,或将多个运行时间较长的任务提交到只包含少量线程的线程池中,那么将降低由改Executor管理服务的响应性。
-
使用ThreadLocal的任务 ThreadLocal使每个线程都可以拥有某个变量的一个私有“版本”。然而只要条件允许,Executor可以自由的复用这些线程。在标准的Executor中,当执行需求较低时将回收空闲线程,当需求增加时将添加新的线程,并且如果从任务中抛出异常,那么将使用一个新的工作者线程替代异常线程。只有当线程本地值的生命周期受限于任务的生命周期时,在线程池的线程中使用ThreadLocal
才有意义,而在线程池的线程中不应该使用ThreadLocal在任务之间传递值。只有当任务都是同类型的并且相互独立时,线程池的性能才能达到最佳。如果将允许时间较长的与较短的任务混合在一起,除非线程池很大,否则将可能造成“拥塞”。如果提交的人依赖于其他任务,那么除非线程池无限大,否则将可能“死锁”。
1、线程饥饿死锁
在线程池中,如果任务依赖于其他任务,那么可能产生死锁。
如果所有正在执行任务的线程都由于等待其他仍处于工作队列的任务而阻塞,这种现象称为线程饥饿死锁。
只要线程池中的任务需要无限期等待一些必须由池中其他任务才能提高的资源或条件,除非线程池足够大,否则将发生线程饥饿死锁。
2、运行时间较长的任务
如果任务阻塞时间过长,即使不出现死锁,线程池的响应性也会变得糟糕。增加了线程池的堵塞,还增加了执行时间较短任务的服务时间。
可以使用限时等待解决上述问题。
二、设置线程池的大小
三、配置ThreadPoolExecutor
ThreadPoolExecutor是一个灵活的、稳定的线程池。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
1、线程的创建与销毁
线程池基本大小(corePoolSize)、最大大小(maximumPoolSize)、存活时间、等因素共同负责线程的创建与销毁。
基本大小:线程池目标大小,即在没有任务执行时线程池大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
最大大小:表示可以同时活动的线程数量的上限。如果某线程的空闲超过了存活时间,那么将标记为可回收,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。
2、管理队列任务
ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务,基本的排队方法有:无界队列、有界队列、同步移交(Synchronous Handoff)。
newFixedThreadPool和newSingleThreadExecutor默认使用无界的linkedBlockingQueue。如果所有工作者线程都处理忙碌状态,任务将在队列中等候。
如果任务持续快速到达,并且超过了线程池处理速度,队列将无限制增加。
使用有界队列更稳妥,可以避免资源耗尽。有界队列工作时,队列大小与线程池的大小必须一起调节。
如果线程池较小队列较大,那么有助于减少内存使用量,降低CPU使用率,减少上下文切换,但可能限制了吞吐量。
对于非常大的或者无界的线程池,可以通过使用SynchronousQueue来避免任务排队,直接将任务从生产者交给工作者线程。
SynchronousQueue并不是真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入其中,必须有另一个线程正在等待接受这个元素。
如果没有,并且线程池的当前大小小于最大值,那么将创建一个新线程,否则根据饱和策略,这个任务将被拒绝。
3、饱和策略
当有界队列被填满后,饱和策略开始发挥作用。
中止策略(AbortPolicy)是默认的饱和策略,该策略将抛出未检查的RejectedExecutionException。可以被捕获,然后处理自己代码。
当新提交的任务无法保存到队列中等待执行时,抛弃策略将悄悄抛弃任务,抛弃最旧的(DiscardOldest)策略则会抛弃下一个将被执行任务,
然后尝试重新提交新的任务。
调用者运行策略(CallerRunsPolicy)实现了一种调节机制,既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
4、线程工厂
每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。默认的线程工厂方法将创建一个新的、非守护线程,并且不包含特殊配置信息。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
java并发编程实战 第六章 任务执行
一、在线程中执行任务 1、串行地执行任务 当服务器正在处理请求时,新的连接必须等待直到请求处理完毕。如果请求阻塞时间过长,用户将认为服务器不可用。 2、显式地为任务创建线程 通过每个请求创建一个新的线程来提供服务,从而实现高响应性。需要创建大量线程时: 线程生命周期开销非常高,线程创建、销毁需要代价 资源消耗,活跃的线程消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程将闲置。 大量闲置线程占用内存,给垃圾回收带来压力,并在竞争cpu资源时产生性能开销。 稳定性。可创建线程数据存在一个限制。受JVM启动参数、Thread构造函数中请求栈大小、底层操作系统对线程的限制等。 在一定范围内,增加线程可以提高系统吞吐率,超过这个范围,在创建更多的线程只会降低程序的执行速度,甚至系统崩溃。 二、Executor框架 Executor提供了一种标准的方法将任务的提交过程与执行过程解耦,并用Runnable来表示任务。 public interface Executor { void execute (Runnbale command); } 提供了对生命周期的支持,以...
- 下一篇
天了噜,Java 8 要停止维护了!
前些天的中兴事件,已经让国人意识到自己核心技术的不足,这次的 JDK 8 对企业停止免费更新更是雪上加霜。。 以下是 Oracle 官网提示的 JDK8 终止更新公告。 image 原文内容:Oracle will not post further updates of Java SE 8 to its public download sites for commercial use after January 2019. Customers who need continued access to critical bug fixes and security fixes as well as general maintenance for Java SE 8 or previous versions can get long term support through Oracle Java SE Advanced, Oracle Java SE Advanced Desktop, or Oracle Java SE Suite. For more information, and ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- 设置Eclipse缩进为4个空格,增强代码规范
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Hadoop3单机部署,实现最简伪集群
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS关闭SELinux安全模块
- Docker使用Oracle官方镜像安装(12C,18C,19C)