jdk1.5之前我们使用线程的时候就去创建一个线程,这样实现起来的非常方便。
但是有一个问题就是,如果并发的线程数量很多,并且每个线程创建出来就执行一个任务就结束了,这样频繁的创建和销毁线程会大大的降低系统的效率,因为创建和销毁线程需要时间。
jdk5之后加入了java.utilconcurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。
什么是线程池?
用来管理线程的一个概念,使得线程可以复用,就是线程执行完成一个任务并不被销毁,而是可以继续执行其他的任务。java通过线程池来达到这样的效果的。
线程池的有点
- 线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用
- 可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多的内存导致服务器崩溃
ThreadPoolExecutor
java.util.concurrent.ThreadPoolExecutor是线程池中最核心的类。
构造方法
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
ThreadPoolExcutor类继承了AbstrackExecutorService类,并提供了四个构造方法,事实上,前三个构造方法都是调用的第四个构造方法。
构造方法中各个参数的含义:
- corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任何任务到来之前就创建corePoolSize个线程或者一个线程,默认情况下,在创建了线程池之后,线程池中的线程数为0,当有任务来了之后,就会创建一个线程去执行任务,当线程池中的线程数达到corePoolSize(核心线程数)后,就会把到达的任务放到缓冲队列中;
- maximumPoolSize:线程池中最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示除了核心线程外的其他线程没有任务执行时最多保持多长时间会终止。默认情况下,只有对那个线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean )方法,在线程池中的线程数不大于核心线程数 的时候,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- unit:参数keepAliveTime的时间单位,有7中取值,在TimeUnit类中有7中静态属性;
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种。
ArrayBlockQueue;
LinkedBlockingQueue;
SynchronousQueue;
- threadFactory:线程工厂,主要用来创建线程;
- handler:表示当拒绝处理任务时的策略,有一下四种取值;
ThreadPoolExecutor.AbortPolicy //丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy//也是丢弃任务,但是不抛出异常--悄无声息的丢弃
ThreadPoolExecutor.DiscardOldestPolicy//丢弃队列最前面的任务,并执行当前任务
ThreadPoolExecutor.CallerRunsPolicy //直接调用executor来执行当前任务
public interface Executor {
void execute(Runnable command);
}
在上面的类关系图中可以看出,Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数类型为Runnable类型,从字面意思可以理解就是用来执行传进去的任务的。
interface ExecutrorService
然后ExecutorService接口继承了Executor接口,并声明了一些方法
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
ThreadPoolExecutro类继承了类AbstranctExecutorService
在ThreadPoolExecutor类中有几个非常重要的方法:
execute()
submit()
shutdown()
shutdownNow()
execute()方法实际上是ExecutorService中声明的方法,在ThreadPoolExecutor中进行了具体的实现,这个方法是ThreadPoolExecutro的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务的执行结果,去看submit()方法的实现,会发现它实际上还是调用execute()方法,只不过它利用了Future来获取任务执行结果
shutdown()和shutdownNow()是用来关闭线程池的
核心成员变量
线程池的主要控制状态时ctl,它是一个原子的整数,其包含两个概念字段:
- workerCount:有效的线程数量
- runState:线程池的状态
为了在一个整型值里面包含着两个字段,我们限制workerCount最多(2^29) -1
线程池的运行状态
- RUNNING:接受新的任务,并处理队列中的任务
- SHUTDOWN:不接受新的任务,继续处理队列中的任务
- STOP:不接收新的任务,也不继续处理队列中的任务,并且中断正在处理的任务
- TIDYING:所有任务都结束了,workerCount是0,通过调用terminated()方法转换状态
- TERMINATED:terminated()方法已经完成
状态之间的转换
RUNNING->SHUTDOWN
调用shutdown()方法,或者隐式调用finalize()方法
(RUNNING或者SHUTDOWN)->STOP
调用shutdownNow()方法
SHUTDOWN->TIDYING
当队列和池都是空的时候
STOP->TIDYING
当池是空的时候
TIDYING->TERMINATED
当terminated()方法调用完成时。
Integer.SIZE=31
Integer.SIZE - 3 = 29
所以,COUNT_BITS = 29
高3位存储runState
线程池的执行流程
- 由图可知,任务进来的时候,首先判断核心线程池是否已满,如果已经满了判断核心线程是否有空闲,如果有空闲或者核心线程池不满,就由核心线程先执行任务
- 如果核心线程已满,则判断任务队列是否有地方存放该任务,如果有就将任务保存在任务队列中,等待执行
- 如果等待队列队列也满了,再判断是否达到最大线程数,如果没有达到就创建非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
submit()方法和execute()方法
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
这两个方法有什么区别呢?
我们发现submit最终还是会调用execute方法,不同的是submit方法提供了一个Future来托管返回值的处理,当调用这个方法需要有返回值的时候,可以用这个方法,execute方法只能接收Runnable作为参数,而submit方法还可以接收Callable。
public void execute(Runnable command) {
//如果当前任务为空,抛出异常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//wordCountOf获取线程池的当前线程数,如果小于规定的核心线程数,则执行addWorker创建新线程执行任务并返回
if (workerCountOf(c) < corePoolSize) {
//true是代表往核心线程池创建线程
//false是代表创建核心线程之外的工作线程
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果线程池处于RUNNING状态,并且成功的把任务放入等待队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果线程的状态不是RUNNING,并尝试从等待队列中删除任务成功
if (! isRunning(recheck) && remove(command))
//则调用线程饱和策略处理任务
reject(command);
//如果处于running状态,但是没有线程,则创建线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//再次尝试创建核心线程池之外的线程并执行任务
else if (!addWorker(command, false))
//如果
reject(command);
}
executor方法基本分三步
- 开启线程执行任务,直到达到最大核心线程数
- 达到核心线程数时,将接受的新的任务放入工作队列
- 当工作队列也满之后,就开启非核心线程,直到到达最大线程数
- 以上条件都不满足的时候,就执行默认的拒绝策略
addWorker方法源码
private boolean addWorker(Runnable firstTask, boolean core) {
retry: //循环标志
for (;;) { 死循环
int c = ctl.get();//获取状态位
int rs = runStateOf(c);//计算线程池的状态
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;//这一段说的是线程池不能正常运行的情况:线程池状态关闭、任务为空、队列为空返回错误
for (;;) {//死循环
int wc = workerCountOf(c);//计算线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;//如果线程数超出核心线程数,返回错误
if (compareAndIncrementWorkerCount(c))//增加worker的数量
break retry;//回到进入该方法的循环状态
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;//如果状态发生改变,就回退
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;//线程是否开始运行
boolean workerAdded = false;//worker是否添加成功
Worker w = null;
try {
w = new Worker(firstTask);//封装成worker
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;//加锁
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());计算线程池状态
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;//worker添加成功
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();//启动刚刚添加的任务
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);//失败后执行的操作
}
return workerStarted;
}
线程测试
package cn.tedu.ThreadPoolDemo1;
import java.util.concurrent.*;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(4,7,50,TimeUnit.SECONDS,queue);
for(int i = 0;i<10;i++){
executor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("线程池中活跃的线程数为"+executor.getActiveCount());
if(queue.size()>0){
System.out.println("被阻塞的线程数为"+queue.size());
}
}
}
}
线程池中活跃的线程数为1
线程池中活跃的线程数为2
线程池中活跃的线程数为3
线程池中活跃的线程数为4
线程池中活跃的线程数为4
被阻塞的线程数为1
线程池中活跃的线程数为4
被阻塞的线程数为2
线程池中活跃的线程数为4
被阻塞的线程数为3
线程池中活跃的线程数为5
被阻塞的线程数为3
线程池中活跃的线程数为6
被阻塞的线程数为3
线程池中活跃的线程数为7
被阻塞的线程数为3
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task cn.tedu.ThreadPoolDemo1.ThreadPoolDemo1$1@330bedb4 rejected from java.util.concurrent.ThreadPoolExecutor@2503dbd3[Running, pool size = 7, active threads = 7, queued tasks = 3, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at cn.tedu.ThreadPoolDemo1.ThreadPoolDemo1.main(ThreadPoolDemo1.java:14)
可以看到创建了3个核心线程和4个非核心线程,当线程数超过了线程池可容纳的最大数量。执行了拒绝策略Reject,说明队列和线程池都满了,线程池处于饱和状态,另外一个原因是完成的线程没有及时释放而是进入了休眠状态。