在一般情况下当我们在执行多个任务的时候可能就会想到线程池取处理,确实,在很多时候线程池的性能比线程的性能要快很多,也方便很多。下面我们就深入源码级理解线程池。
1、线程池的5种状态
RUNNING = -1 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行 处理。 线程池被 一旦被创建,就处 于 RUNNING状态,并且线程池中的任务数为0。
SHUTDOWN = 0 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
STOP = 1 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务 interrupt()方法中断(线程可能并 并没有杀掉)
TIDYING = 2 当前任务全部停止,ctl里面记录线程池执行任务数量变成0
TERMINATED = 3 线程池彻底终止,就变成TERMINATED状态
2、一般在java种创建线程会有很多种方式,我们经常用到基本有以下几种方式:
当创建为固定大小的线程池时,return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()); 当超过最大线程数量时会放入一个无界的阻塞队列里面(LinkedBlockingQueue)
创建单个线程池 : 其余的也放入一个无界的阻塞队列里面(LinkedBlockingQueue)
创建缓存的线程池: 这个是可以无限制创建线程 线程一开始进来不会立刻去调度,而是存入缓存中(线程的复用 最大线程数是最大的)
不管我们用哪种方式创建线程底层都会调用如下同一个方法,唯一不同的是传入的参数不一样和队列的类型不一样
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
需要注意的是,一般情况下如果在任务量比较大的情况下,一般不推荐使用Executors工具类 创建各种类型的线程池 调用execute()方法去创建线程(一般情况下我们是不推荐使用java自带的创建线程池的工具类的,因为线程池里面的队列的大小默认是非常大的(Interger.value()))
3、使用java自带的Executors工具类导致OOM和CPU100%的问题
OOM的情况和cup100%的情况创建缓存的线程池会出现因为它的队列是无界队列,出CPU100%的问题是因为创建缓存的线程池的最大线程是无限大的,在创建线程的时候会巨大的开销CPU资源,创建线程会调用CPU底层的pthread库,开销比较大。
4、使用自定义线程池方式
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));
5、线程池工作流程:
说到线程池工作流程之前首先要说到几个概念
1、核心线程数量 2、非核心线程数 3、阻塞队列 4、拒绝策略
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));//自定义线程
for (int i = 1; i <= 100; i++) {
//调用threadPoolExecutor.execute方法取提交任务
threadPoolExecutor.execute(new MyTask(i));
}
}
上面代码为自定义线程池伪代码,通过调用execute方法提交任务,首先不得不提到两个优先级1、提交优先级 2、执行优先级
提交优先级意思是将任务提交给线程池的核心线程等的先后顺序:先提交到核心线程中,然后再提交到阻塞队列里面,最后在提交到非核心线程中,下面为具体的源码
int c = ctl.get();
1、判断当前的线程数是否小于corePoolSize如果是,
使用入参任务通过addWord方法创建一个新的线程,
如果能完成新线程创建exexute方法结束,成功提交任务;
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
2、在第一步没有完成任务提交;状态为运行并且能成功加入任务到工作队列后,
再进行一次check,如果状态在任务加入队列后变为了非运行(有可能是在执行到这里线程池shu
tdown了)
非运行状态下当然是需要reject;
然后再判断当前线程数是否为0(有可能这个时候线程数变为了0),如是,新增一个线程;
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false); 判断当前工作线程池数是否为0
如果是创建一个null任务,任务在堵塞队列存在了就会从队列中取出 这样做的意义是
保证线程池在running状态必须有一个任务在执行
}
3、如果不能加入任务到工作队列,将尝试使用任务新增一个线程,如果失败,
则是线程池已经shutdown或者线程池已经达到饱和状态,所以reject;
从上面新增任务的execute方法也可以看出,拒绝策略不仅仅是在饱和状态下使用,
在线程池进入到关闭阶段同样需要使用到;
上面的几行代码还不能完全清楚这个新增任务的过程,
肯定还需要清楚addWorker方法才行:
else if (!addWorker(command, false))
reject(command);
执行优先级体现在runwork方法里面的getwork方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//这个是说明了一开始就从核心线程和非核心线程拿任务
//task不为空 或者阻塞队列中拿到了任务
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
上面的runworke中的getTask可知:执行的优先级是先拿核心线程任务在拿非核心线程的任务,最后再拿阻塞队列里面的任务