1.概念
1.1. 回顾线程创建方式
- 继承Thread
- 实现Runnable
1.2. 线程的状态
- New:刚刚创建,没做任何操作
Thread thread = new Thread();
System.out.println(thread.getState());
- RUNNABLE:调用run,可以执行,但不代表一定在执行(RUNNING,READY)
thread.start();
System.out.println(thread.getState());
- BLOCKED:抢不到锁
final byte[] lock = new byte[0];
new Thread(new Runnable() {
public void run() {
synchronized (lock){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Thread thread2 = new Thread(new Runnable() {
public void run() {
synchronized (lock){
}
}
});
thread2.start();
Thread.sleep(1000);
System.out.println(thread2.getState());
- WAITING
Thread thread2 = new Thread(new Runnable() {
public void run() {
LockSupport.park();
}
});
thread2.start();
Thread.sleep(500);
System.out.println(thread2.getState());
LockSupport.unpark(thread2);
Thread.sleep(500);
System.out.println(thread2.getState());
- TIMED_WAITING
Thread thread3 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread3.start();
Thread.sleep(500);
System.out.println(thread3.getState());
- TERMINATED
//等待1s后再来看
Thread.sleep(1000);
System.out.println(thread.getState());
1.3.线程池基本概念
根据上面的状态,普通线程执行完,就会进入TERMINATED销毁掉,而线程池就是创建一个缓冲池存放线程,执 行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等候下次任务来临,这使得线程池比手动 创建线程有着更多的优势:
- 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
- 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行
- 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM
- 节省cpu切换线程的时间成本(需要保持当前执行线程的现场,并恢复要执行线程的现场)
- 提供更强大的功能,延时定时线程池。(Timer vs ScheduledThreadPoolExecutor)
1.4. 常用线程池类结构
可以通过idea查看到 (查看:ScheduledThreadPoolExecutor,ForkJoinPool类图)
说明:
- 最常用的是ThreadPoolExecutor
- 调度用ScheduledThreadPoolExecutor
- 任务拆分合并用ForkJoinPool
- Executors是工具类,协助你创建线程池的
2. 工作机制
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部 协调空闲的线程,如果有,则将任务交给某个空闲的线程。一个线程同时只能执行一个任务,但可以同时向一个线 程池提交多个任务。
2.1. 线程池状态
- RUNNING:初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务 数为0。RUNNING状态下,能够接收新任务,以及对已添加的任务进行处理。
- SHUTDOWN:SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。调用线程池的shutdown()接口 时,线程池由RUNNING -> SHUTDOWN。
//shutdown后不接受新任务,但是task1,仍然可以执行完成
ExecutorService poolExecutor = Executors.newFixedThreadPool(5);
poolExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
System.out.println("finish task 1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
poolExecutor.shutdown();
poolExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("ok");
- STOP:不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。调用线程池的shutdownNow()接 口时,线程池由(RUNNING 或 SHUTDOWN ) -> STOP
注意:运行中的任务还会打印,直到结束,因为调的
//改为shutdownNow后,任务立马终止,sleep被打断,新任务无法提交,task1停止
poolExecutor.shutdownNow();
- TIDYING:所有的任务已终止,队列中的”任务数量”为0,线程池会变为TIDYING。线程池变为TIDYING状态 时,会执行钩子函数terminated(),可以通过重载terminated()函数来实现自定义行为
//自定义类,重写terminated方法
public class MyExecutorService extends ThreadPoolExecutor {
public MyExecutorService(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void terminated() {
super.terminated();
System.out.println("treminated");
}
//调用 shutdownNow, ternimated方法被调用打印
public static void main(String[] args) throws InterruptedException {
MyExecutorService service = new MyExecutorService(1, 2, 10000, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5));
service.shutdownNow();
}
}
- TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED
2.2. 结构说明
(源码查看:两个集合,一个queue,一个hashset)
2.3.任务的提交
- 添加任务,如果线程池中线程数没达到coreSize,直接创建新线程执行
- 达到core,放入queue
- queue已满,未达到maxSize继续创建线程
- 达到maxSize,根据reject策略处理
- 超时后,线程被释放,下降到coreSize
3. 源码解析
//任务提交阶段:(4个if条件路线)
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
int c = ctl.get();
//判断工作数,如果小于coreSize,addWork,注意第二个参数core=true
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) return;
c = ctl.get();
}
//否则,如果线程池还在运行,offer到队列
if (isRunning(c) && workQueue.offer(command)) {
//再检查一下状态
int recheck = ctl.get();
//如果线程池已经终止,直接移除任务,不再响应
if (!isRunning(recheck) && remove(command)) reject(command);
//否则,如果没有线程干活的话,创建一个空work,该work会从队列获取任务去执行
else if (workerCountOf(recheck) == 0) addWorker(null, false);
}
//队列也满,继续调addWork,但是注意,core=false,开启到maxSize的大门
// 超出max的话,addWork会返回false,进入reject
else if (!addWorker(command, false)) reject(command);
}
//线程创建
private boolean addWorker(Runnable firstTask, boolean core) {
//第一步,计数判断,不符合条件打回false
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);
//判断线程数,注意这里!
// 也就说明线程池的线程数是不可能设置任意大的。
// 最大29位(CAPACITY=29位二进制)
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
if (compareAndIncrementWorkerCount(c)) break retry;
c = ctl.get();// Re‐read ctl
if (runStateOf(c) != rs) continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//第二步,创建新work放入线程集合works(一个HashSet)
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//符合条件,创建新的work并包装task
w = new Worker(firstTask);
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;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//注意,只要是成功add了新的work,那么将该新work立即启动,任务得到执行
t.start();
workerStarted = true;
}
}
} finally {
if (!workerStarted) addWorkerFailed(w);
}
return workerStarted;
}
//任务获取与执行
// 在worker执行runWorker()的时候,不停循环,先查看自己有没有携带Task,如果有,执行
while(task!=null||(task=getTask())!=null)
//如果没用,会调用getTask,从队列获取任务
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// 判断是不是要超时处理,重点!!!决定了当前线程要不要被释放
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//线程数超出max,并且上次循环中poll等待超时了,那么说明该线程已终止
// 将线程队列数量原子性减
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) return null;
continue;
}
try {
//重点!!!
// 如果线程可被释放,那就poll,释放的时间为:keepAliveTime
// 否则,线程是不会被释放的,take一直被阻塞在这里,知道来了新任务继续工作
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null) return r;
//到这里说明可被释放的线程等待超时,已经销毁,设置该标记,下次循环将线程数减少
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
完整流程回顾:
4. 注意点
4.1. 线程池是如何保证线程不被销毁的呢?
答案:如果队列中没有任务时,核心线程会一直阻塞在获取任务的方法,直到返回任务。而任务执行完后,又会进 入下一轮 work.runWork()中循环
验证:秘密就藏在核心源码里 ThreadPoolExecutor.getTask()
//work.runWork():
while (task != null || (task = getTask()) != null) //work.getTask():
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS):workQueue.take();
4.2. 那么线程池中的线程会处于什么状态?
答案:TIMED_WAITING,RUNNABLE,WAITING
验证:起一个线程池,放置一个任务sleep,debug查看结束前后的状态
//debug add watcher:
((ThreadPoolExecutor)poolExecutor).workers.iterator().next().thread.getState()
ThreadPoolExecutor poolExecutor = Executors.newFixedThreadPool(5);
poolExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("ok");
4.3. 核心线程与非核心线程有区别吗?
答案:没有。被销毁的线程和创建的先后无关。即便是第一个被创建的核心线程,仍然有可能被销毁
验证:看源码,每个works在runWork的时候去getTask,在getTask内部,并没有针对性的区分当前work是否是核 心线程或者类似的标记。只要判断works数量超出core,就会调用poll(),否则take()
5. Executors工具
以上构造函数比较多,为了方便使用,提供了一个Executors工具类
- newCachedThreadPool() : 弹性线程数
- newFixedThreadPool(int nThreads) : 固定线程数
- newSingleThreadExecutor() : 单一线程数
- newScheduledThreadPool(int corePoolSize) : 可调度,常用于定时