浅谈线程池

 

什么是线程池

本人理解:线程池是一个可以重用的线程的集合,在执行任务的时候就从这个集合中取线程执行,等任务执行完毕,再把线程放回等待下一次任务。

 

为什么使用线程池

1.减少了线程在创建和销毁上浪费的时间

2.如果不使用线程池当创建大量线程的时候可能会导致消耗完系统内存。

3.提高响应速度。当任务到达时,节省了等待线程创建的时间。

4.提高可管理性。使用线程池可以对线程进行统一的分配和管理。

 

线程池是怎么做到线程复用的

首先我们要知道当线程执行完run方法之后,线程就进入死亡状态,那么线程池是如何做到线程复用的呢?

 

参考:https://www.linuxidc.com/Linux/2017-10/147970.htm

 

首先我们需要知道线程池管理的一批线程被封装成了Worker(进行了一些属性设置,如线程名,优先级等),当我们向线程池提交任务的时候,我们通常使用execute()方法,当当前工作做线程的数量小于核心线程池的时候,我们需要使用addWorker(Runnable firstTask, boolean core),向线程池里添加线程,这里的firstTask是当前用户提交的任务,该方法主要的工作是,创建一个Worker线程,加入到Worker集合,启动线程。重点来了,t.start启动线程需要执行Worker的run()方法,该方法里只有一句runWorker(this),该方法中有一个循环体while (task != null || (task = getTask()) != null),在这个循环的最后将task设为null,这样在下一次循环的时候就执行getTask(),达到循环执行任务队列里的任务。

 

简单地说:当线程池中的任务被创建后,他会执行一个runWorker方法,该方法循环的从任务队列中获取任务,执行完任务后判断是否销毁,如果不销毁就进入一个休眠时间,休眠时间过了就继续取任务。

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//JDK8的源码中,线程池本身的状态跟worker数量使用同一个变量ctl来维护
int c = ctl.get();
//通过位运算得出当然线程池中的worker数量与构造参数corePoolSize进行比较
if (workerCountOf(c) < corePoolSize) {
//如果小于corePoolSize,则直接新增一个worker,并把当然用户提交的任务command作为参数,如果成功则返回。
if (addWorker(command, true))
return;
//如果失败,则获取最新的线程池数据
c = ctl.get();
}
//如果线程池仍在运行,则把任务放到阻塞队列中等待执行。
if (isRunning(c) && workQueue.offer(command)) {
//这里的recheck思路是为了处理并发问题
int recheck = ctl.get();
//当任务成功放入队列时,如果recheck发现线程池已经不再运行了则从队列中把任务删除
if (! isRunning(recheck) && remove(command))
//删除成功以后,会调用构造参数传入的拒绝策略。
reject(command);
//如果worker的数量为0(此时队列中可能有任务没有执行),则新建一个worker(由于此时新建woker的目的是执行队列中堆积的任务,
//因此入参没有执行任务,详细逻辑后面会详细分析addWorker方法)。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果前面的新增woker,放入队列都失败,则会继续新增worker,此时线程池的状态是woker数量达到corePoolSize,阻塞队列任务已满
//只能基于maximumPoolSize参数新建woker
else if (!addWorker(command, false))
//如果基于maximumPoolSize新建woker失败,此时是线程池中线程数已达到上限,队列已满,则调用构造参数中传入的拒绝策略
reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {
//这里有一段基于CAS+死循环实现的关于线程池状态,线程数量的校验与更新逻辑就先忽略了,重点看主流程。
//...

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//把指定任务作为参数新建一个worker线程
w = new Worker(firstTask);
//这里是重点,咋一看,一定以为w.thread就是我们传入的firstTask
//其实是通过线程池构造函数参数threadFactory生成的woker对象
//也就是说这个变量t就是代表woker线程。绝对不是用户提交的线程任务firstTask!!!
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//加锁之后仍旧是判断线程池状态等一些校验逻辑。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
//把新建的woker线程放入集合保存,这里使用的是HashSet
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//然后启动woker线程
//这里再强调一遍上面说的逻辑,该变量t代表woker线程,也就是会调用woker的run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//如果woker启动失败,则进行一些善后工作,比如说修改当前woker数量等等
addWorkerFailed(w);
}
return workerStarted;
}

 

public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//task就是Woker构造函数入参指定的任务,即用户提交的任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
//一般情况下,task都不会为空(特殊情况上面注释中也说明了),因此会直接进入循环体中
//这里getTask方法是要重点说明的,它的实现跟我们构造参数设置存活时间有关
//我们都知道构造参数设置的时间代表了线程池中的线程,即woker线程的存活时间,如果到期则回收woker线程,这个逻辑的实现就在getTask中。
//来不及执行的任务,线程池会放入一个阻塞队列,getTask方法就是去阻塞队列中取任务,用户设置的存活时间,就是
//从这个阻塞队列中取任务等待的最大时间,如果getTask返回null,意思就是woker等待了指定时间仍然没有
//取到任务,此时就会跳过循环体,进入woker线程的销毁逻辑。
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//该方法是个空的实现,如果有需要用户可以自己继承该类进行实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
//真正的任务执行逻辑
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//该方法是个空的实现,如果有需要用户可以自己继承该类进行实现
afterExecute(task, thrown);
}
} finally {
//这里设为null,也就是循环体再执行的时候会调用getTask方法
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//当指定任务执行完成,阻塞队列中也取不到可执行任务时,会进入这里,做一些善后工作,比如在corePoolSize跟maximumPoolSize之间的woker会进行回收
processWorkerExit(w, completedAbruptly);
}
}

 

 

 

当一个任务进入线程池中,线程池是怎么处理的

 

1.如果当前线程数<核心线程数(corePoolSize),不管有没有空闲的线程都新建一个线程。

2.如果当前线程数>=核心线程数(corePoolSize),缓冲队列未满,把任务放在缓冲队列里,等待空闲线程来执行。

3.如果当前线程数>=核心线程数(corePoolSize),缓冲队列已满,且当前线程数<最大线程数(maximumPoolSize),创建新线程来处理新添加的任务

4.如果当前线程数=最大线程数,采取任务拒绝策略。

注意:核心线程数<当线程数在<=最大线程数 此时如果线程空闲时间超过存活时间,线程将被终止,直到<=核心线程池大小为止

 

任务拒绝策略

1.AbortPolicy:抛异常(默认)

2.DiscardPolicy:直接丢弃

3.DiscardOldestPolicy:丢弃最老的,执行最新的

4.CallerRunsPolicy:不用线程池中的线程,直接用调用者所在的线程

 

常用方法

submit()和execute()方法区别

submit有返回值execute没有

submit方便异常地处理,如果希望外面的调用者感知到这个异常那么需要调用submit的方法通过捕获uture.get抛出异常

 

shutdown()和shutdownNow()的区别

shutdown():如果有空闲线程,则销毁空闲线程,等待所有正在执行的任务以及处于阻塞队列中的全部执行完毕,销毁所有线程。

shutdownNow():取消所有阻塞队列中的任务,你把他放入一个List容器里作为返回值,中所有正在执行的任务,销毁所有空闲线程

 

 

配置线程池大小

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值