并发的这一些东西最近都在看,看着看着发现其实还有挺有意思的,不管能理解多少,其实也是有收获的,万事开头难。当真正有兴趣了,就能看就去了,如果实在看不懂,从相关博客开始看。记住,找个凉快时间,有个舒适的心情看这些枯燥的东西才好接受!!!下一篇我说说AQS,感觉这玩意挺好,各种锁都使用了他,还有一些同步工具等也使用了AQS!
概述
- 线程池的主要作用
- 如果使用了多线程,那么多线程会比单线程来说会提高效率(如果使用多线程并没有提升效率那我们就不要用线程池了)
- 提高资源的利用率,让程序可以进行并发并行执行
- 有效的管理资源,内存资源和CPU资源是有限的,线程池创建之后需要进行管理,每次创建线程都需要系统分配资源。(如果是CPU密集型,那么线程数=核心数,因为对CPU需求量高,所以线程数多与CPU核心数也没有多与的CPU资源来处理线程。如果是IO型的,那么可以多创建一些线程,IO型主要等待IO操作时间比较长,CPU利用率相对较低,所以可以多创建一些线程)
- 一些技术的使用:
- BlockingQueue阻塞队列的使用在任务获取线程时,
- AQS:独占锁
逻辑梳理
- 线程池的几个重要参数:
- corePoolSize:核心线程数。阻塞队列没有任务时,线程池可以创建的线程
- maxPoolSize:最大线程数。当阻塞队列满了以后,线程池可以创建的线程数为maxPoolSize
- keepAliveTime:线程的存活时间,通常是指超出corePoolSize的线程数的存活时间,也可以通过方法调用设置corePoolSize中线程的存活时间。
- unit:存活时间的单位:
- BlockingQueue:阻塞队列(有界队列,避免OOM,如果是无界队列,会导致队列不断的变大,内存空间是有限的,同时该部分内存无法在OOM时回收,强引用),用来存储等待执行的任务。当前线程数大与等于核心线程数时,将任务存入阻塞队列。
- ThreadFactory:创建线程的工厂。线程池中的线程由该工厂创建
- RejectedExecutionHandler:表示拒绝任务时的策略,当阻塞队列满时,对于来请求线程池线程的处理方式
- 线程池的处理流程:线程池的设计原理中,分为两部分,一部分是创建worker(创建线程A,指定当前线程要执行的任务B),一部分执行任务,由创建线程A.start启动创建的线程,真正处理执行的传递过来的执行的任务B,同时任务B本身执行完成后,还会从阻塞队列中获取任务执行。execute方法调用(submit也会调用到该方法)
- 当前线程数<核心线程数,创建worker
- 当前线程数>核心线程数&&阻塞队列没有满,将任务添加到阻塞队列
- 当前线程数>核心线程数&&阻塞队列已满&&当线程数<最大线程数,创建worker
- 当前线程数>核心线程数&&阻塞队列已满&&当线程数>最大线程数,执行RejectedExecutionHandler拒绝策略
一开始的困惑
- 几个状态:状态是指整个线程池的状态,而不是单个线程的状态,实际上线程池中线程的数量使用定义的多个size控制,而线程由程序内部进行管理
- 执行任务时,任务执行完自身的任务后,会从线程池中获取任务进行执行,该任务的执行调用的是run方法,而不是创建新的线程(该部分的设计是启动任务和执行任务分开的过程)–找个地方有意思,后面我会在代码中具体说说,这里也是线程创建的任务重复执行请求的地方
- AtomicInteger ctl是一个32位操作数,高三位表示线程池的状态,低29位表示线程池数,通过runStateOf方法获得线程池的状态,workCountOf方法是获取当前线程数,个人感觉这么设计的好处是保持一定的灵活性,同时扩容线程池大小或者增加线程池状态时,更改COUNT_BITS即可。
- workCountOf方法是个递增的过程,每当增长一个,才会向worker中添加一个线程,知道线程数大与核心线程数(此种情况计假设当前阻塞队列没有满的情况下),当达到线程容量时,就不在创建新创建线程了,新来的任务直接添加到阻塞队列,由已经已经创建好的任务进行处理,即调用run方法。
源码浅读
- 主流程:该部分是选择直接创建worker还是添加阻塞队列还是抛弃任务等等
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//当前线程数<核心线程数 向工作集中添加线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//具体的添加线程逻辑,true\false指当先
return;
c = ctl.get();
}
//当前线程数>核心线程数&&阻塞队列没有满,将任务添加到阻塞队列,
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);
}
//当前线程数>核心线程数&&阻塞队列已满&&当线程数<最大线程数,创建worker
else if (!addWorker(command, false))
//当前线程数>核心线程数&&阻塞队列已满&&当线程数>最大线程数,执行RejectedExecutionHandler拒绝策略
reject(command);
}
- addWorker方法:具体的创建、添加线程逻辑,true\false指当线程数和最大线程数比较还是核心线程数比较,这一部分分为两部分,第一部分是线程池状态和线程池数量的判断,看是否创建worker,第二部分创建worker,启动线程。如果当前线程数大与核心线程数或者最大线程数,将不再执行第二部分,在第一部分判断的过程会直接退出,将任务添加到阻塞队列
private boolean addWorker(Runnable firstTask, boolean core) {
//线程池状态和线程池数量的判断
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 当前线程状态的判断
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) //根据core是false还是true取当前的最大线程数
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
}
}
//第二部分创建worker,启动线程,
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//一会具体看看这个,创建worker,任务都是在这里执行的
final Thread t = w.thread; //worker中创建的线程
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()) // 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) {
t.start();//启动woker中的线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
- Worker类:该部分为执行任务的部分,不管是最初创建线程,携带任务过来的时候,还是后来阻塞队列中有任务的时候,真正执行的任务的过程都在这里。
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
//构造方法,创建worker,firstTask是当前任务,thread是线程,该thread的目的主要是启动线程,执行该类的run方法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker,该阶段线程没有启动任务,所以不需要中断
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}
- runWorker:这部分为执行线程的部分,当线程池的容量多达到核心线程数,或者最大线程数以后,就不在创建线程了,而是由任务不断的从阻塞队列中获取任务执行,在这里有个阻塞队列的应用,
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts 更改AQS 中state的状态,使任务处于可被中断的状态
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();//每个woker中的任务只能由一个任务来执行
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 {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//释放worker中的线程
processWorkerExit(w, completedAbruptly);
}
}
- 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;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//从阻塞队列中获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
应用
- 目前线程池的使用,还处于比较浅显的阶段,工作中也就是用线程处理加快一些数据结果的处理,当前有200万个数据要加工,利用线程池和CountDownLatch进行处理就好了,如果需要返回结果就用FutureTask,如果不需要结果用Runnable就可以,CountDownLatch的作用是保证线程都计算完成后,才能玩下进行
- 注意点:创建线程要自己指定核心线程数等。
- FixThreadPool和SingleTheadExecutor:队列长度为Integer.MAX_VALUE,可能堆积大量的请求,导致OOM
- CachedThreadPool和ScheduledThreadPool:允许创建的线程数是Integer.maxValue,可能会创建大量的线程导致OOM
参考
https://www.jianshu.com/p/1f5195dcc75b
https://www.cnblogs.com/dolphin0520/p/3932921.html
https://www.jianshu.com/p/a2616dcb7f13