8应用线程池
有些类型的任务需要明确指定一个执行策略,如依赖性任务、采用线程限制的任务,对响应时间敏感的任务、使用ThreadLocal的任务。(只有当线程本地thread_local()值的生命周期被限制在当前任务种时,在池的某线程中使用ThreadLocal才有意义;在线程池中,不应该使用ThreadLocal传递任务间的数值)
当任务都是同类的、独立时,线程池才有最佳的工作表现。
8.1线程饥饿死锁
在线程池中,如果一个任务依赖于其他任务的执行,就可能产生死锁。如:对于一个单线程化的Executor,一个任务将另一个任务提交到相同的Executor中,并等待提交的任务的结果,总会引起死锁。
第二个任务滞留在工作队列中,直到第一个任务完成;但第一个任务不会完成,因为它在等待第二个任务的完成。——这就是线程饥饿死锁。
因此,无论何时,提交一个非独立的Executor任务时,要明确出现线程饥饿死锁的可能性,且在代码或者配置文件以及其他可以配置Executor的地方,任何有关池的大小和配置约束都要写入文档。
8.1.2耗时操作
耗时任务会造成线程地堵塞,还会拖长服务时间。解决之道:限定任务的等待资源的时间。
大部分平台类库中的阻塞方法,同时提供了限时、非限时的2个版本,如Thread.join/BlockingQueue.put/CountDownLatch.await/Selector.select
8.2定制线程池大小
池的长度应该由某种配置机制来提供,不要硬编码,当然无须特别准确,只要以防过大或过小。
8.3配置ThreadPoolExcutor
核心池大小(Core Pool SIze)、最大池大小(max Pool Size)、存活时间(Keep Live Time)共同管理线程的创建与销毁。
核心池大小(Core Pool SIze)是目标的大小,线程池的实现试图维护池的大小:即没有任务执行,池的大小也等于核心池的大小;且直到工作队列充满时,池都不会创建更多的线程;
最大池大小(max Pool Size)是可同时活动的线程数的上限。如果一个线程已经闲置的时间超过了存活时间(Keep Live Time),会成为被回收的候选者,如果当前池的大小超过了核心池的大小,线程池会终止它。
Public ThreadPoolExecutor(int CorePoolSize,int maxPoolSize,long keepLiveTime,TimeUnit unit,……)
通过调节核心池的大小与存活时间,可以促进线程池归还空闲线程占有的资源。
newFixedThreadPool:工厂为请求的池设定了核心池的大小,最大池的大小,永不会超时;
newCachedThreadPool:工厂将最大池大小设置为Integer.MAX_VALUE,核心池大小为0,超时1分钟
8.3.2管理队列任务
ThreadPoolExecutor允许提供了一个BlockingQueue来持有等待执行的任务,任务排队有3种基本方法:无限队列,有限队列,同步移交。
newFixedThreadPool和newSingleThreadExecutor默认使用的是一个无限的LinkedBlockingQueue,如果所有的工作者线程都处于忙碌状态,任务将会在队列中等候;如果任务持续的快速到达,超过了它们被处理的速度,队列也会无限的增加。
稳妥的资源管理策略是使用有线的队列(如ArrayBlockingQueue、有限的LinkedBlockingQueue、PriorityBlockingQueue)
有界队列有助于避免资源耗尽的情况发生,但队列满时,要有相应的“饱和策略”处理。
newCachedThreadPool工厂提供了比定长的线程池更好的队列等候性能,它是Executor的一个很好的默认选择。
8.3.3饱和策略
当一个有线队列充满时,饱和策略开始生效。
ThreadPoolExecutor的饱和策略可以通过调用setRejectedExectionHandler来修改(如果任务提交到一个已经关闭的Executor时,也会用到饱和策略)
JDK提供了几种不同的RejectedExectionHandler实现,每一个实现不同的饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy.
默认的中止(abort)策略引起Executor抛出异常;调用者捕获异常,编写满足自己需求的处理代码;
当最新提交的任务不能进入队列等候执行时,“遗弃(Discard)”策略会放弃这个任务;
“遗弃最旧的(Discard——oldest)”策略选择丢弃的任务,是本应接下来执行的任务,该策略还会尝试重新提交新任务。(如果工作队列是优先级队列,那么遗弃最旧的策略选择丢弃的是刚好优先级最大的元素,所以混合使用遗弃最旧的饱和策略与优先级不可行)
“调用者运行(Call_runs)”策略的实现形式,既不会丢弃哪个任务,也不会抛出异常。它会把一些任务推回到调用者那里,以此缓解新任务流。它不会在线程池中执行最新的任务,但会在一个调用了executor的线程中执行。
8.3.4线程工厂
线程池创建线程,要通过一个线程工厂实现。默认的线程工厂创建了一个新的、非后台的线程。
ThreadFactory只有唯一的方法: .newThread()。它会在线程池需要创建一个新线程时调用。
8.4拓展ThreadPoolExecutor
它提供了几个“钩子”让子类去覆盖——beforeExecute、afterExecute、terminate——用于拓展ThreadPoolExecutor的行为。
(可以用于添加日志、时序、监视器、统计信息)
无论是正常从run返回,还是抛出异常,afterExecute会调用。
Terminate线程池关闭时调用。
Terminate可以用于释放Executor在生命周期中分配到的资源,还可以发出通知、记录日志、完成统计信息。
8.5并行递归算法
当每一个迭代彼此独立,且完成循环时,每个迭代的工作意义都足够大,足以弥补管理一个新任务的开销时,这个顺序循环是适合并行的。
9 GUI应用程序
几乎所有的GUI工具集都实现为“单线程化子系统”,意味着所有GUI的活动都被限制在一个单独的线程中,如Swing、SWT。
早期GUI:GUI事件在“主事件循环”进行处理;
现代GUI:创建一个专门的线程,事件派发线程EDI来处理GUI事件
(多线程GUI易受死锁影响)
单线程化的GUI框架通过线程限制来达到线程安全性,所有GUI中的对象,包括可视化组件和数据模型,都只能被事件线程访问。
Swing的线程限制
Swing的单线程规划:Swing的组件和模型只能在事件分派线程中被创建、修改和请求。
相关推荐
Java并发编程学习笔记
java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记
java并发编程学习笔记,很详细的资料
Java并发编程学习笔记.
目前,在Java并发编程方面论述系统、内容详实的中文资料很少。本文是作者在实际工作中经验总结,部分内容来自《Java Concurrency In Practice》。涵盖了Java并发编程所需掌握的大部分知识,且实例丰富通俗易懂。读完...
Java并发编程学习笔记,研究JAVA并发多线程编程的一本教程,使用并发技术可以开发出并行算法,充分利用多处理器的计算能力,避免硬件资源浪费。目前,在JAVA并发编程方面的论述系统且内容详实的技术资料不太多,Java...
java并发编程实战pdf学习笔记 总结了重要的知识点
Java并发编程系列心得笔记,可以参考,欢迎共同交流学习
Java并发编程与高并发解决方案-学习笔记-www.itmuch.com
Java并发编程与高并发解决方案笔记-基础篇.docx
Java并发编程与高并发解决方案-学习笔记
Java并发编程 学习资料
java并发编程与并发解决方案是自己多年开发和学习的笔记,有助于(ˇˍˇ) 想~进一步提高的java开发工程师或架构师深入的学习java架构并发处理。同时,它也是 在实际工作中多年高并发解决方案和经验的总结
自己总结的java并发编程的笔记,绘制了详细的思维导图,每个思维导图中均有详细的博文解释,方便大家学习和理解,免费分享给大家。
Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...
并发编程学习笔记,来源于 itmuch整理,是学习并发的不错的学习资料,有大神的个人感悟。介绍了一些概念,比如并发,高并发,并行,锁。
想对并发有一定了解的人群
主要是自己学习java多线程学习的笔记记录。作为新手练手也好,作为工作三年的我,来说只是作为跳槽之前的准备,之前对于java多线程真的是一知半解,只有到这里我才发现原来是长这样的基础模型。脚踏实地的一步一步的...
主要介绍了Java 并发编程学习笔记之Synchronized底层优化的相关资料,主要包含了重量级锁,轻量级锁,偏向锁和其他优化等方面,有需要的小伙伴可以参考下