线程池的探索

1、线程池

并发的基础是java.lang.Threads类。 Thread执行类型为java.lang.Runnable的对象。html

直接使用Thread类有如下缺点:java

Ø  建立新线程会致使一些性能开销。git

Ø  太多的线程可能致使性能降低,由于CPU须要在这些线程之间切换。github

Ø  不能轻易地控制线程数,所以线程过多会致使内存不足错误。web

与直接使用Threads相比,java.util.concurrent包提供了对并发性的改进支持。 算法

使用线程池的优势:apache

Ø  下降资源消耗:经过重复利用已建立的线程下降线程建立和销毁形成的消耗。编程

Ø  提升响应速度:当任务到达时,任务能够不须要等到线程建立就能当即执行。数组

Ø  提升线程的可管理性:线程是稀缺资源,若是无限制的建立。不只仅会下降系统的稳定性,使用线程池能够统一分配,调优和监控。可是要作到合理的利用线程池。必须对于其实现原理了如指掌。缓存

2、Executor

Executor框架主要由三个部分组成:任务,任务的执行,异步计算的结果。

主要的类和接口简介以下:

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask相似,解决那些须要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutorScheduledExecutorService接口实现,周期性任务调度的类实现。

Future

表明异步计算的结果

Runnable

能够被ThreadPoolExecutor或其余执行的主体逻辑代码

 

java.util.concurrent 包包含多个 Executor 实现,每一个实现都实现不一样的执行策略。


 

执行程序一般经过工厂方法例示,而不是经过构造函数。Executors 类包含用于构造许多不一样类型的 Executor 实现的静态工厂方法:

Ø  newSingleThreadExecutor()

建立一个单线程的线程池。这个线程池只有一个线程在工做,也就是至关于单线程串行执行全部任务。若是这个惟一的线程由于异常结束,那么会有一个新的线程来替代它。此线程池保证全部任务的执行顺序按照任务的提交顺序执行。

Ø  newFixedThreadPool()

建立固定大小的线程池。每次提交一个任务就建立一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,若是某个线程由于执行异常而结束,那么线程池会补充一个新线程。

Ø  newCachedThreadPool()

建立一个可缓存的线程池。若是线程池的大小超过了处理任务所须要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增长时,此线程池又能够智能的添加新线程来处理任务。此线程池不会对线程池大小作限制,线程池大小彻底依赖于操做系统(或者说JVM)可以建立的最大线程大小。

Ø  newScheduledThreadPool()

建立一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

 

所需JAR包:

Jdk 1.5 + -> java.util.concurrent

 

参考资料:

FixedThreadPool, CachedThreadPool, or ForkJoinPool? Picking correct Java executors for background tasks - https://zeroturnaround.com/rebellabs/fixedthreadpool-cachedthreadpool-or-forkjoinpool-picking-correct-java-executors-for-background-tasks/

java自带线程池和队列详细讲解 - https://www.oschina.net/question/565065_86540

Java并发编程与技术内幕:线程池深刻理解 -http://blog.csdn.net/evankaka/article/details/51489322

 

3、Fork/Join

Fork/Join框架是Java 7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每一个小任务结果后获得大任务结果的框架。

Fork-Join框架有本身的适用范围。若是一个应用能被分解成多个子任务,而且组合多个子任务的结果就可以得到最终的答案,那么这个应用就适合用 Fork-Join框架模式来解决。

Fork-Join框架可以解决不少种类的并行问题。软件开发人员只须要关注任务的划分和中间结果的组合就能充分利用并行平台的优良性能。其余和并行相关的诸多难于处理的问题,例如负载平衡、同步等,均可以由框架采用统一的方式解决。

Fork-Join框架是ExecutorService接口的一种具体实现,目的是为了更好地利用多处理器。它是为那些可以被递归地拆解成子任务的工做类型量身设计的。相似于ExecutorService接口的其余实现,Fork-Join框架会将任务分发给线程池中的工做线程。

Fork-Join框架的核心是ForkJoinPool类,它是对AbstractExecutorService类的扩展。ForkJoinPool实现了工做窃取算法,并能够执行ForkJoinTask任务。

工做窃取算法

Fork-Join框架经过一种称做工做窃取(work stealing的技术减小了工做队列的争用状况。每一个工做线程都有本身的工做队列,这是使用双端队列(或者叫作 deque)来实现的(Java 6 在类库中添加了几种 deque 实现,包括 ArrayDeque LinkedBlockingDeque)。当一个任务划分一个新线程时,它将本身推到 deque 的头部。当一个任务执行与另外一个未完成任务的合并操做时,它会将另外一个任务推到队列头部并执行,而不会休眠以等待另外一任务完成(像 Thread.join() 的操做同样)。当线程的任务队列为空,它将尝试从另外一个线程的 deque 的尾部窃取另外一个任务。

所需JAR包:

Jdk 1.7 + -> java.util.concurrent

 

参考资料:

1.     方腾飞,聊聊并发(八)——Fork/Join框架介绍

2.     JDK 7 中的 Fork/Join 模式

3.     Java 理论与实践: 应用 fork-join 框架

4、Google Guava[建议使用]

Guava工程包含了若干被Google Java项目普遍依赖的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] I/O 等等。全部这些工具天天都在被Google的工程师应用在产品服务中。

 

关于Java并发操做,guava提供了一组API用于封装线程。

有两个关键接口:

ListenableFuture:完成后触发回调的Future

Service:抽象可开启和关闭的服务,帮助你维护服务的状态逻辑

其中:

1ListenableFuture接口继承自JDK concurrent包下的Future接口,对比Future接口有一些优势:

Ø  大多数Futures 方法中须要它。

Ø  转到ListenableFuture 编程比较容易。

Ø  Guava提供的通用公共类封装了公共的操做方方法,不须要提供FutureListenableFuture的扩展方法。

 

2Service接口用于封装一个服务对象的运行状态、包括startstop等方法。例如web服务器,RPC服务器、计时器等能够实现这个接口。对此类服务的状态管理并不轻松、须要对服务的开启/关闭进行妥善管理、特别是在多线程环境下尤其复杂。Guava包提供了一些基础类帮助你管理复杂的状态转换逻辑和同步细节。

实现:

AbstractIdleService

AbstractExecutionThreadService

AbstractScheduledService

AbstractService

ServiceManager

 

所需JAR包:

google-guava_xxx.jar

 

参考资料:

Google Guava官方教程(中文版) | 并发编程网

http://www.baeldung.com/thread-pool-java-and-guava

 

5、threadly

一个工具库,以协助安全并发java开发。提供一个独特的基于优先级的线程池,以及安全地分发线程工做的方法。

 

参考资料:

GitHub: https://github.com/threadly/threadly

APIhttp://threadly.github.io/threadly/javadocs/4.4.2/

Download: http://mvnrepository.com/artifact/org.threadly/threadly/4.9.0

Examples: https://github.com/threadly/threadly_examples/blob/master/src/main/java/org/threadly/examples/features/PrioritySchedulerExample.java

 

6、talent-thread-pool

talent-thread-pool是基于jdk5内置线程池的封装,省却你一些事件的框架
1
、帮你完成使用线程池所带来的繁琐的同步安全工做
2
、为你提供一个更靠谱的RejectedExecutionHandlerjdk自带的是抛异常,本框架默认的是用定时继续提交) 
3
、为你提供一个更友好的ThreadFactoryjdk自带的Factory产生出来的Thread名字是形如thread-pool-1的,本框架默认的是形如:myname-1,其中“myname”是应用提供的参数) 
4
、提供更简单的ThreadPoolExecutor构造器,固然你也能够根据业务须要构造更细化的ThreadPoolExecutor 

 

 

 

 

参考资料:

http://tywo45.iteye.com/blog/1944341

http://tywo45.iteye.com/blog/1536159

7、Eclipse Jobs

Eclipse 提供了一套多线程类库(包括 Job 等)极大的方便了开发人员对多线程程序的处理。本文经过对 Eclipse 内核代码的研究,分析 Eclipse 多线程库的内部实现机制,特别是其内部线程池的实现方式,Job 的调度,线程同步机制等。

 

Eclipse org.eclipse.core.runtime.osgi 运行时插件里提供了 Jobs API Jobs API 被普遍的应用到 Eclipse 平台中,用户所开发的 eclipse 插件里。 Job Eclipse 运行时重要的组成部分(基于 equinox OSGi 框架则是 Eclipse 运行时的最重要的组成部分)。 Job 能够理解成被平台调用异步运行的代码块。 Job 能够异步的执行,多个 Jobs 能够并发执行。那么读者会问了?为何 eclipse 平台要提供这样的 API 出来,为何不直接使用 java.lang.Thread 呢?

缘由有如下几点:

1)性能更好:经过使用线程池实现了线程共享,减小了建立和销毁线程的开销,提升了性能。

2)交互响应信息:Job 提供了一个框架,开发人员使用 Job 很容易实现与用户的交互,例如容许用户取消 Job 的执行或者显示 Job

3)调度的灵活性:能够立刻运行一个 Job,能够稍后运行一个 Job, 还能够反复的运行一个 Job

4Job 的监听机制:Job 监听器监听 Job 的状态信息,好比,知道一个 Job 什么时候开始运行以及什么时候结束运行等。

5)优先级及互斥的灵活应用:Job 提供了多种方式来控制 Job 的调度,开发者能够设定 Job 的优先级(读者应注意这一点,JobManager 不保证优先级高的 Job 必定比优先级低的 Job 先被调度执行),也可使用调度规则保证 Jobs 的同步与互斥。

6)使用Job能够提升程序的性能,节省线程建立和销毁的开销。Eclipse中的Job封装了线程池的实现。当咱们启动一个Job时,Eclipse不会立刻新建一个Thread,它会在它的线程池中寻找是否有空闲的线程,若是有空闲线程,就会直接用空闲线程运行你的Job。一个Job终止时,它所对应的线程也不会当即终止,它会被返回到线程池中以备重复利用。这样,咱们能够节省建立和销毁线程的开销。

 

1. Jobs 框架

 

 

2. Work WorkPool,线程池机制

 

 

3. Schedule方法调用顺序

 

所需JAR包:

org.eclipse.core.jobs-3.3.0.jar

org.eclipse.core.runtime-3.1.0.jar

org.eclipse.osgi-3.2.0-v20060601.jar

参考资料:

https://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-mthrd/

https://www.ibm.com/developerworks/cn/opensource/os-cn-eclipse-multithrd/

http://defrag-sly.iteye.com/blog/344837

8、Tomcat ThreadPool

Tomcat 的线程池位于tomcat-util.jar文件中,包含了两种线程池方案。

方案一:使用APRPool技术,使用了JNI

方案二:使用Java实现的ThreadPool。这里介绍的是第二种。若是想了解APRPool技术,能够查看APR的源代码。

ThreadPool默认建立了5个线程,保存在一个200维的线程数组中,建立时就启动了这些线程,固然在没有请求时,它们都处理“等待”状态(其实就是一个while循环,不停的等待notify)。若是有请求时,空闲线程会被唤醒执行用户的请求。

具体的请求过程是: 服务启动时,建立一个一维线程数组(maxThread200个),并建立空闲线程(minSpareThreads5)随时等待用户请求。 当有用户请求时,调用 threadpool.runIt(ThreadPoolRunnable)方法,将一个须要执行的实例传给ThreadPool中。其中用户须要执行的 实例必须实现ThreadPoolRunnable接口。 ThreadPool 首先查找空闲的线程,若是有则用它运行要执行ThreadPoolRunnable;若是没有空闲线程而且没有超过maxThreads,就一次性建立 minSpareThreads个空闲线程;若是已经超过了maxThreads了,就等待空闲线程了。总之,要找到空闲的线程,以便用它执行实例。找到 后,将该线程从线程数组中移走。 接着唤醒已经找到的空闲线程,用它运行执行实例(ThreadPoolRunnable)。 运行完ThreadPoolRunnable后,就将该线程从新放到线程数组中,做为空闲线程供后续使用。

由此能够看出,Tomcat的线程池实现是比较简单的,ThreadPool.java也只有840行代码。用一个一维数组保存空闲的线程,每次以一个较小步伐(5个)建立空闲线程并放到线程池中。使用时从数组中移走空闲的线程,用完后,再“归还”给线程池。

tomcat5.5.10以上版本支持apr,支持经过apache runtime module进行JNI调用,使用本地代码来加速网络处理。

若是不使用apr以前,TomcatServlet线程池使用的是阻塞IO的模式,使用apr以后,线程池变成了 NIO的非阻塞模式,并且这种NIO仍是使用了操做系统的本地代码,看tomcat文档上面的说法是,极大提高web处理能力,再也不须要专门放一个web server处理静态页面了。

我本身直观的感觉是,不用apr以前,你配置多少个等待线程,tomcat就会启动多少个线程挂起等待,使用apr之后,无论你配置多少,就只有几个NIO调度的线程,这一点你能够经过kill -3 PID,而后察看log得知。

假设不使用apr,可能端口的线程调度能力比较差,因此经过iptables进行端口转发,让两个端口去分担一个端口的线程调度,就有可能减小线程调度的并发,从而提升处理能力,减小资源消耗。

 

所需JAR包:

tomcat-util.jar

tomcat-juli.jar

 

参考资料:

Tomat组件研究之ThreadPool - 老码农的专栏http://blog.csdn.net/chen77716/article/details/344764

 

9、task-frame

负责线程池中任务的高效率和并发执行。
1.
您能够自定义线程池大小,以便您能够调整项目的性能要求。
2.
您没必要担忧任务运行时间过长,由于任务框架已经被实现来监视超时任务,一旦任务运行时间不长,任务监视器将从线程池中删除它,还可帮助您自动结束超时任务。
3.
任务调度框架具备出色的性能,若是任务队列为空,则框架将自动进入等待状态,直到任务队列有要添加的任务。这将大大下降CPU的消耗。
框架很容易使用,例如:
TaskQueue queue = new TaskQueue
(); 
taskAssigner = new TaskAssigner
queue10; 
taskAssigner.start
(); 
queue.add
task;

 

参考资料:

https://sourceforge.net/projects/task-frame/?source=directory

 

10、本身实现一个Thread Pool

参考资料:

https://github.com/gauravrmazra/java-threadpool

http://geekrai.blogspot.in/2014/12/implementing-thread-pool-in-java.html

相关文章
相关标签/搜索