在之前的文章中总结了Java线程的两种创建方式:继承Thread和实现Runnable接口,在Java中使用多线程不一定非得用此两种方式,JDK为我们封装了大量的线程实用类,本文主要对Java中的线程池ThreadPoolExecutor做一下详细的介绍。
线程池的好处
使用线程池比通过Thread或者Runnable直接实现多线程有哪些好处呢?
1.减少系统资源消耗:线程池中线程能够被复用,不需要每次创建一个线程执行任务,执行完任务之后进行线程销毁,减少线程创建与线程销毁带来的系统开销以及时间片轮转和上下文切换带来的开销;
2.提高执行任务的响应速度:将任务提交到线程池中,一旦线程池中有空闲线程,可以直接调用线程执行任务,节省线程创建所带来的时间;
3.线程的统一调度管理:能够对线程池中的线程都进行统一的管理、调度以及销毁;
ThreadPoolExecutor
JDK提供了ThreadPoolExecutor线程池,ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的,因此有必要弄清楚ThreadPoolExecutor;当我们想线程池提交一个任务时,需要经过什么样的步骤?有什么不同的处理结果呢?
线程池组成部分:线程(核心线程与非核心线程)、任务队列(存储处于等待的任务)、线程工厂(用于创建线程)、异常处理handler、非核心线程空闲超时时间等部分组成;
线程池的状态:
1.RUNNING:接受新的任务,执行任务队列里面的任务;
2.SHUDOWN:不再接受新的任务,但会执行任务队列里面的任务;
3.STOP:不再接受新的任务,也不执行任务队列里面的任务;同时中断正在执行的任务;
4.TIDYING:所有的任务已经完成,工作线程为0;TIDYING会调用terminated()进入TERMINATED状态;
5.TERMINATED:terminated()函数执行完成,线程池终止;
线程池状态间的转换:
RUNNING -> SHUTDOWN:调用shutdown()函数
RUNNING -> STOP:调用shutdownNow()函数
SHUTDOWN -> STOP:调用shutdownNow()函数
SHUTDOWN -> TIDYING:任务队列和工作线程都为空(等待任务队列里面的任务执行完成,工作线程终止)
STOP -> TIDYING:工作线程都为空(当进入STOP状态后中断所有工作线程进入TIDYING)
TIDYING -> TERMINATED:调用terminated()函数执行完成(进入TIDYING状态会调用terminated()函数)
往线程池中提交一个任务,要经过一下步骤:
步骤1:如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务,而不会把这个任务添加到任务队列中;
步骤2:如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务插入任务队列中,不会马上执行该任务,等待运行的线程执行完当前的任务空闲后,再从任务队列中取出任务继续执行。
步骤3:如果这时候任务队列满了,无法继续插入到任务队列,如果此时正在运行的线程数量小于 maximumPoolSize,那么还是会创建一个线程来运行这个任务;
步骤4:如果任务队列满了,并且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”,通过异常处理handler进行处理。
当然上述步骤不是完全的步骤,当在执行过程中发现线程池的状态为非RUNNING状态时,则抛出异常,拒绝此任务,交给异常处理handler进行处理;
任务执行策略
根据上面的介绍我们可以知道,当线程池正在运行的线程大于或者等于corePoolSize时,任务队列的大小直接决定任务的执行;任务队列的大小可以分为三种:size为0的队列、有界队列以及无界队列;
1.size为0的队列
任务队列大小为0,每次往任务队列中插任务时都失败,此时正在运行的线程小于maximumPoolSize时,则会创建新的线程直接运行任务,或者由空闲线程执行任务;否则会拒绝接受此任务,交给异常处理handler进行处理。
线程池的特点
1.线程池线程数量最多为maximumPoolSize个,最少为0个
2.提交线程池的任务不需要等待,当前线程小于corePoolSize时,直接创建新的线程执行;如果当前运行的线程数量等于或者大于maximumPoolSize时,则直接拒绝接受此任务,交给异常处理handler进行处理;
3.线程池最多同时执行maximumPoolSize个任务
下面我们来看一个例子
创建一个任务队列大小为0的ThreadPoolExecutor,其中corePoolSize为1,maximumPoolSize为2
public static ExecutorService synchronousQueueExecutor() {
return new ThreadPoolExecutor(1, 2,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
创建3个任务Runnable,在开始执行与结束执行run()打印log,中间sleep() 1秒
private static Runnable runnable1 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable1 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("executor","runnable1 end");
}
};
private static Runnable runnable2 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable2 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("executor","runnable2 end");
}
};
private static Runnable runnable3 = new Runnable() {
public String getName() {
return "runnable3";
}
@Override
public void run() {
Log.i("executor","runnable3 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("executor","runnable3 end");
}
};
设置RejectedExecutionHandler,并执行上面的3个任务
private static RejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Log.i("executor","rejectedExecution");
}
};
public static void testSynchronousQueueExecutor () {
ExecutorService executor = ExecutorUtils.synchronousQueueExecutor();
((ThreadPoolExecutor)executor).setRejectedExecutionHandler(handler);
executor.execute(runnable1);
executor.execute(runnable2);
executor.execute(runnable3);
}
因为线程池的maximumPoolSize为2,任务队列大小为0,当提交到第2个任务的时候,会拒绝接受任务,抛出rejectedExecution,交给handler处理,我们来运行看看结果如何
I/executor: rejectedExecution
I/executor: runnable2 start
I/executor: runnable1 start
I/executor: runnable2 end
I/executor: runnable1 end
从结果可以知道,runnable3没有执行,并且抛出了rejectedExecution,在handler中进行处理。
2.有界队列
有界队列,当正在运行的线程数量等于或者大于corePoolSize,每次提交任务时,都会往任务队列中插入,若往任务队列中插任务时失败,此时正在运行的线程小于maximumPoolSize时,则会创建新的线程直接运行任务,或者由空闲线程执行任务;否则会拒绝接受此任务,交给异常处理handler进行处理。
线程池特点
1.线程池线程数量最多为maximumPoolSize个,最少为0个
2.提交线程池的任务可能需要等待,当前线程小于corePoolSize时,直接创建新的线程执行;如果当前运行的线程数量等于或者大于corePoolSize时,会插入任务队列进行等待,当任务队列满了且当前正在运行的线程数量小于maximumPoolSize个,会创建新的线程直接运行,否则直接拒绝接受此任务,交给异常处理handler进行处理;
3.线程池最多同时执行 任务队列大小 + maximumPoolSize 个任务
下面我们来看一个例子
首先创建一个有界任务队列的线程池,任务队列大小为3
public static ExecutorService boundaryQueueExecutor() {
return new ThreadPoolExecutor(1, 2,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3));
}
创建10个runnable对象
private static Runnable runnable1 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable1 start");
}
};
private static Runnable runnable2 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable2 start");
}
};
private static Runnable runnable3 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable3 start");
}
};
private static Runnable runnable4 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable4 start");
}
};
private static Runnable runnable5 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable5 start");
}
};
private static Runnable runnable6 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable6 start");
}
};
private static Runnable runnable7 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable7 start");
}
};
private static Runnable runnable8 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable8 start");
}
};
private static Runnable runnable9 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable9 start");
}
};
private static Runnable runnable10 = new Runnable() {
@Override
public void run() {
Log.i("executor","runnable10 start");
}
};
设置RejectedExecutionHandler,并执行上面的10个任务
public static void testBoundaryQueueExecutor () {
ExecutorService executor = ExecutorUtils.boundaryQueueExecutor();
((ThreadPoolExecutor)executor).setRejectedExecutionHandler(handler);
executor.execute(runnable1);
executor.execute(runnable2);
executor.execute(runnable3);
executor.execute(runnable4);
executor.execute(runnable5);
executor.execute(runnable6);
executor.execute(runnable7);
executor.execute(runnable8);
executor.execute(runnable9);
executor.execute(runnable10);
}
因为我们的创建的线程池corePoolSize为1,maximumPoolSize为2,任务队列大小为3,因此线程池最多只能执行5个任务,有5个任务将会被拒绝,我们来看看运行结果如何
I/executor: rejectedExecution
I/executor: rejectedExecution
I/executor: rejectedExecution
I/executor: rejectedExecution
I/executor: rejectedExecution
I/executor: runnable5 start
I/executor: runnable2 start
I/executor: runnable3 start
I/executor: runnable4 start
I/executor: runnable1 start
说明结果还是和我们预期相符的
但是这里有一点要注意的,执行的任务的顺序是不一定的,有些博客上说因为中间的任务加入队列里面等待而后面的任务因为任务队列满了执行创建线程执行,认为后面的任务比队列中的任务先执行,这种说法是错误的;有两点原因:1.后面的任务不一定比在队列中的任务先创建线程,也有可能在为后面的任务创建线程的过程中,有正在运行的线程空闲下来了,执行队列中的任务;2.即使后面的任务先创建线程,也不一定先执行,因为到底哪个线程先获得CPU时间片进行执行也是不可确定的。
3.无界队列
无界队列,当正在运行的线程数量等于或者大于corePoolSize,每次提交任务时,都会往任务队列中插入,因为任务队列无限大,因此不会失败。
线程池特点
1.线程池线程的最大数量为corePoolSize
2.线程池任务队列无线大,在线程池RUNNING状态下不会拒绝接受任务,抛出rejectedExecution
3.当线程池的正在运行的线程数量等于corePoolSize时,任务都会插入到任务队列
具体就不举例子了,有兴趣的可以自己去试试
Executors
JDK提供了Executors类,其中提供了创建ThreadPoolExecutor的静态工厂方法,主要包含三类线程池的创建:newFixedThreadPool、newSingleThreadExecutor和newCachedThreadPool。
newFixedThreadPool
创建只有核心线程的线程池,其中任务队列是无界队列
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
创建只有一个核心线程的线程池,其中任务队列为无界队列,相当于是单线程执行任务队列中的任务
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
创建没有核心线程池、非核心线程池最多为Integer.MAX_VALUE个、任务队列大小为0的线程池,相当于每加入一个任务创建一个线程执行任务,线程超时后会销毁
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}