一.什么是线程池?
为何会有线程池这个概念呢?为了避免系统频繁地创建和销毁线程,将创建的线程进行复用。由此线程池的概念应运而生,简而言之,创建线程变成了从线程池中获得空闲线程,关闭线程变成了向池子归还线程。
二.引入JDK
多线程在一定程度上以最大限度地发挥现代多核处理器的计算能力,但是如果不加控制和管理的随意使用线程,对系统的性能反而会产生不利的影响;
2.1 java Executor框架
其中,ThreadPoolExecutor表示一个线程池。Executor类则扮演着线程池工厂的角色,通过Executor可以取得拥有特定功能的线程池。
Executor框架提供了各种类型的线程池,主要有以下工厂方法:
public static ExecutorService newFixedThreadpool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduleExcutorService newSingleThreadScheduledExcutor
public static ScheduleExcutorService newSingleThreadScheduledExcutor(int corePoolSize)
newFixedThreadPool()方法:该方法返回一个固定线程数量的线程池。该线程池中的数量始终不变。当有一个新的任务提交时,线程池中 若有空闲线程,则立即执行。若没有,则新的任务会暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
newSingleThreadExecutor()方法:该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
newCachedThreadPool():该方法返回一个可根据实际情况的调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务在当前任务执行完毕后,将返回线程池进行复用。
newSingleThreadScheduleExecutor()方法:该方法返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService接口在ExecutorService接口之上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。
newScheduleThreadPool()方法:该方法返回一个ScheduleExccutorService对象,但线程池可以指定线程数量;
值得注意的一个方法是newScheduleThreadPool()。它返回一个ScheduleExecutorService对象可以根据时间需要对线程进行调度,它的一些主要方法如下:
public ScheduleFuture<?> schedule(Runnable ,long delay,TimeUnit unit);
public ScheduleFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,long period,TimeUnit unit);
public ScheduleFuture<?> scheduleWithFixedDelay
(Runnable command, long initialDelay,long Delay,TimeUnit unit);
与其他几个线程池不同,ScheduleExecutorService并不一定会立即安排执行任务,它是起到了计划任务的作用。它会在指定的时间,对任务进行调度。
然而这几种方法略有不同,方法schedule()会在给定时间,对任务进行一次调度。方法
scheduleAtFixedRate()和
scheduleWithFixedDelay()会对任务进行周期性的调度,区别在于,对于FixedRate方式来说,任务调度的频率是一定的。它是以上一个任务开始执行时间为起点。之后的period时间,调度下一次任务,而FixDelay则是在上一个任务结束后,再经历delay时间进行任务调度。
对于核心的几个线程池,不论是newFixedThreadPool()方法,newSingleThreadExecutor还是newCachedThreadPool()方法,在创建线程是各有千秋,但其内部实现均使用了ThreadPoolExecutor类的封装。
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ScheduleExecutorServiceDemo {
public static void main(String[] args){
ScheduleExecutorService ses = Executors.newFixedThreadPool(10);//ScheduleExecutorService是newScheduleThreadPool()线程池所返回的对象;
//Executor线程池工厂可以获取一个拥有特定功能的线程池,并且此框架还提供了各种类型的线程池;
//如果前面的任务没有完成,则调度也不会启动;
ses.scheduleAtFixedRate(new Runnable(){
public void run(){
try{
Thread.sleep(1000);
System.out.println(System.currentTimeMillis()/1000);
} catch(InterruptedException e){
e.printStackTrace();
}
}
},0,2,TimeUnit.SECONDS);
}
}
三.核心线程池的内部实现
对于核心的几个线程池,无论是newFixedThreadPool()方法,newSingleThreadExecutor()还是newCachedThreadPool()方法,从创建线程上来看,都有着完全不同的功能特点,但其内部实现均使用了ThreadPoolExecutor实现。
以上三种线程池的实现方式中均有ThreadPoolExecutor,而这三种线程池其实都只是ThreadPoolExceutor类的封装(封装实际上使用方法是将类的数据隐藏起来,控制用户对类的修改和访问数据的程度)为何ThreadExecutor有如此强大的功能,究其原因在于有重要的构造函数。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit ,BlockngQueue<Runnable> workQueue,ThreadFactory threadfactory, RejectedExecutionHandler handler)
函数的参数含义如下:
corePoolSize:指定了线程池中的线程数量;
maximumPoolSize:指定了线程池中最大线程数量;
keepAliveTime:当线程数量超过corePoolSize时,多余的空闲线程的存货时间;
unit:keepAliveTime的单位;
WorkQueue:任务队列,被提及但尚未执行的任务;
threadfactory:线程工厂,用于创建线程,一般用默认的即可;
handler:拒绝策略。当任务太多来不及处理,如何拒绝任务;
以上参数中,大部分很简单,只有workQueue和handler需要进行详细说明;
参数workQueue指被提交但未执行的任务队列,它是一个BlockingQueue接口的对象,仅用于存放Runnable对象,根据队列功能分类,在ThreadPoolExecutor的构造函数中可使用以下几种BlockingQueue;
a.直接提交的队列:该功能由SynchronousQueue对象提供,SynchronousQueue是一个特殊的BlockingQueue,
BlockingQueue没有容量,每一个插入操作都要等待一个相应的删除操作,与之相反,仍然成立,
SynchronousQueue,提交的任务不会被真实的保存,而总是将新的任务提交给线程执行,因此在执行的过程中,如果没有空闲的进程,则需要尝试建立新的进程,如果进程数量已经达到最大值,则执行拒绝策略。(注意:使用SynchronousQueue队列,通常要设置很大的maximumPoolSize值,否则很容易执行拒绝策略。)
有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现。其构造函数中必须带一个容量参数;
public ArrayBlockingQueue(int capacity)
执行此队列的构造函数时,在有界队列,无界队列,优先任务队列中,执行均有不同之处。
a.有界队列:有界队列仅当任务队列装满时,才可能将线程数量提升到corePoolSize以上,换句话说,除非系统非常的繁忙,否则确保核心线程数维持在corePoolSize。
b.无界的任务队列:无界的任务可通过Linked
BlockingQueue类实现。与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务队列入队失败的情况。
c.优先任务队列:优先任务队列是带有执行优先级的队列。它通过PriorityBlockingQueue实现。可以控制任务的执行先后顺序。它是一个特殊的无界队列,有界队列的无界队列都是按照先进先出的算法处理任务的。而PriorityBlockingQueue则可以根据任务自身的优先顺序先后执行,在确保系统性能的同时,也有很好的质量保证(总是保证高优先级的任务先执行)。
以下是ThreadPoolExecutor线程池的核心调度代码;(参考:
http://blog.csdn.net/xieyuooo/article/details/8718741 java 线程池架构原理和源码解析(ThreadPoolExecutor)
public void execute(Runnable command){
if(command == null){
throw new NullPointerException();
int c = ct1.get();
if(WorkerCountOf(c)< corePoolSize){//WorkerCountOf()获取当前线程池的线程总数
if(addWorker(command,true))//当前线程总数小于corePoolSize核心线程时,会将任务通过addWorker()方法直接调度执行
return;
c = ct1.get();
}
if(isRunning(c) && workQueue.offer(command)){//当前的线程数目大于corePoolSize核心线程时。提交等待队列
int recheck = ct1.get();
if(! isRunning(recheck) && remove(command))
reject(command);
else if(workerCountOf(recheck)==0)//进入等待队列失败,则将其提交给线程池;
addWorker(null,false);
}
else if(!addWorker(command,false))//进入等待队列失败,提交给线程池,若当前的线程数目大于maximumPoolSize时,则执行拒绝策略;
reject(command);
}
具体的调度逻辑图如下:
在上面的代码中,任务中执行的线程数目一旦超过系统所能承载的最大线程数目时,就会采用拒绝策略。
四.拒绝策略
拒绝策略顾名思义就是当任务数量超过系统实际承载的数量时,采用拒绝策略,拒绝策略可以说是系统在超负荷运行下的补救措施,也就是说线程池的线程已经用完了,无法继续为新任务服务,同时,等待队列已经排满了,再也塞不下新的任务了,此时,我们就需要有一套机制来处理这个问题;
JDK内置的拒绝策略如下:
<1>AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
<2>CallerRunsPolicy策略:只有线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降;
<3>DiscardOledestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务;
<4>DiscardPolicy策略:该策略默默地丢弃无法处理的任务,不予任何处理,如果允许任务丢失,这个方案是最好的方案。
以上的内置策略均实现了RejectedExecutionHandler接口,若以上的策略仍然无法满足实际应用需要,完全可以自己扩展
RejectedExecutionHandle接口。
RejectedExecutionHandler的定义如下:
public interface RejectedExecutionHandler{
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
}
其中r为请求执行任务,exectuor为当前的线程池;以下的代码是使用自定义线程池和拒绝策略
package ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectThreadPoolDemo {
public static class MyTask implements Runnable{//创建一个任务来继承接口;
public void run(){
System.out.println(System.currentTimeMillis()+":Thread ID:"+ Thread.currentThread().getId());
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException{
MyTask task = new MyTask();
//自定义了一个线程池,其参数分别表示:5个常驻线程,最大线程数量为5,与固定线程数量大小一样,但是却拥有一个容量为10的等待队列,使用无界队列很可能不是最佳方案;/*无界任务队列:无界任务队列可以通过 LinkedBlockingQueue类来实现,与有界队列相比,除非系统资源耗尽,否则无界的队列不存在任务入队列失败的情况。*/ExecutorService es = new ThreadPoolExecutor(5,5,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new RejectedExecutionHandler(){
public void rejectedExecution(Runnable r,ThreadPoolExecutor executor){//为了避免防任务量大,将内存撑爆的情况,因此在定义任务队列的大小时,设置一个合理的值;
System.out.println(r.toString()+"is discard");//自定义拒绝策略,打印出被丢弃的信息;
}
});
for(int i=0;i<Integer.MAX_VALUE;i++){//在任务提交端没有设置异常处理。有可能导致系统崩溃;
es.submit(task);
Thread.sleep(10);
}
}
}
java.util.concurrent.FutureTask@1e933c07is discard
1474985265521:Thread ID:9
1474985265525:Thread ID:10
1474985265535:Thread ID:11
1474985265545:Thread ID:12
1474985265556:Thread ID:13
java.util.concurrent.FutureTask@5449ff3fis discard
java.util.concurrent.FutureTask@4614ac54is discard
java.util.concurrent.FutureTask@773de2bdis discard
从结果中可知,因为MyTask需要花费100毫秒,因此会导致大量的任务被直接丢弃,执行几个任务后,拒绝策略生效,看到系统的任务被丢弃。
五.自定义线程创建:ThreadFactory
线程池的主要作用是为了线程复用,也就是避免了线程的频繁创建,那么有一个问题?这些线程到底是从何而来的,追根溯源就是ThreadFactory。
ThreadFactory是一个接口,它只有一个方法,用来创建线程:
Thread newThread(Runnable r);
当线程池需要创建新建线程时,就会调用这个方法。自定义线程池可以帮助我们做不少的事,例如:我们可以跟踪线程池究竟在何时创建了多少线程,也可以自定义线程的名称,组以及优先级等信息,使用自定义线程池可以让我们更加自由地设置池子中所有线程的状态。
public static void main(String[] args) throws InterruptedException{
MyTask task = new MyTask();
//自定义线程池
ExecutorService es = new ThreadPoolExecutor(5,5,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),new ThreadFactory(){
public Thread newThread(Runnbale r){//利用ThreadFactory
Thread t = new Thread(r);//将接口中的实例化对象传递给线程
t.setDemo(true);//将所有的线程设置为守护进程
System.out.println("create "+t);//记录创建了多少线程
return t;
}
}
);
for(int i = 0; i<5;i++){
es.submit(task);}
Thread.sleep(2000);
}