关于多线程
- 为什么要使用多线程? 提高效率
- 项目中哪些地方使用多线程? 高并发项目中使用多线程
- 多线程处理会有哪些问题? 多线程同时共享一个全局变量,可能会被其他线程干扰,从而产生线程安全问题,无法获取正确的结果,还有可能发生死锁
- 怎么解决线程的安全问题? 使用lock,Synchronized,CAS(乐观)等
- 死锁的问题怎么解决? 可以采用诊断工具检测。
为什么使用线程池
如果我们频繁的创建活销毁线程,对CPU的消耗是非常大的,需要考虑多线程的复用机制。其思路是创建线程,执行了run方法的逻辑后,不会立刻停止线程,而是继续复用执行下一个任务,可以使用一个线程执行多个不同的任务,从而实现复用机制。
- 怎么保证线程不停止呢 写一个死循环
- 线程池的优点? · 复用线程,减少频繁创建 · 提高程序的效率,减少CPU的调度

线程池的原则
- 如果当前线程数少于核心线程数,那么新的请求你总是会新创建新的线程执行
- 如果当前线程数大于核心线程数,那么新提交的请求会放入阻塞队列
- 如果请求无法被加入到队列中,如果超过了最大线程数,则执行饱和策略,否则新建非核心线程执行请求
任务排队
- 直接交接任务。
有一个不错的默认选择是队列
SynchronousQueue,该队列用于叫任务转交给线程,而不是持有该任务。在这里,如果没有立即可用的线程,尝试将任务排队来运行它将失败,因此将构建一个新线程。 任务直接交接通常需要无大小限制的maximumPoolSize,这样能避免新任务被拒绝执行。 - 无边界队列。
无边界队列不需要指定容量,例如
LinkedBlockingQueue,当所有的核心线程都在使用时,新的任务都会在队列中等待。所以小于核心线程数的线程才被创建,所以maximumPoolSize这个值没有什么意义。 - 有界队列
有界队列,例如
ArrayBlockingQueue,有助于防止资源耗尽,它与有限的maximumPoolSizes一起使用,但可能更难调整和控制。 队列大小和最大池大小可以交替使用:使用大队列和小池可最大程度地减少CPU使用率,操作系统资源和上下文切换开销,但会导致人为地降低吞吐量。如果任务经常阻塞例如,如果它们受I / O约束),系统可能可以安排时间超出了您的允许范围。使用小队列通常需要更大的池大小,这会使CPU繁忙,但可能会遇到无法接受的调度开销,这也降低吞吐量。
任务拒绝
Executor被关闭后,或者线程池达到最大线程数并且饱和了,通过execute(Runnable)提交的任务会被拒绝,无论哪种case,execute会调用 RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)}。提供四种预定义的拒绝策略
ThreadPoolExecutor.AbortPolicy默认的拒绝策略,程序会抛一个运行时异常,RejectedExecutionExceptionThreadPoolExecutor.CallerRunsPolicy执行execute的线程会运行这个策略,这是个简单的反馈机制,以此来减慢任务提交的频率ThreadPoolExecutor.DiscardPolicy不能被执行的任务会被丢弃。ThreadPoolExecutor.DiscardOldestPolicy如果线程池没有关闭,那么工作队列中的头部任务将被丢弃,然后重试执行。
线程池的五种状态


execute过程
基本的过程如下图所示

关键变量 线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount)。在实现中,线程池将运行状态(runState)、线程数量(workerCount)两个关键参数的维护放在了一起,而在实现中,用一个AtomicInteger类型来标识两个值,低29位保存保存workerCount,高3位保存runState,而且在获取是通过位运算的标识方式,这种做法不仅节省了锁资源,而且相对于基本运算,速度也提升很多。代码如下所示:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it should not, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
1.如果 workerCount < corePoolSize,那么创建并启动一个线程来执行新提交的任
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
2.如果workerCount >= corePoolSize,且阻塞队列未满,则将任务添加到阻塞队列中
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);
}
3.如果workerCount >= corePoolSize且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务
else if (!addWorker(command, false))
4.如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常
reject(command);
}
- 如果当前运行线程数少于核心线程数,那么启动新线程执行任务
- 如果一个任务成功入队了,仍然需要双重检查一下
- 如果无法插入队列,则尝试新建一个线程执行,如果失败,则是线程池关闭了或者是处于饱和状态
addWorker方法
/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked. If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
*
* @param firstTask the task the new thread should run first (or
* null if none). Workers are created with an initial first task
* (in method execute()) to bypass queuing when there are fewer
* than corePoolSize threads (in which case we always start one),
* or when the queue is full (in which case we must bypass queue).
* Initially idle threads are usually created via
* prestartCoreThread or to replace other dying workers.
*
* @param core if true use corePoolSize as bound, else
* maximumPoolSize. (A boolean indicator is used here rather than a
* value to ensure reads of fresh values after checking other pool
* state).
* @return true if successful
*/
**core 是否是核心线程**
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;
通过CAS workerCouont自加1
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
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
创建线程
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中,workers是个HashSet结构
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
如果添加成功,则运行新建线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
如果添加成功,则回滚线程数量,销毁线程等操作
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
以上就是增加任务的操作,那么我们现在看下线程池是怎么把保证核心线程不死的,以及非核心线程的超时设置是怎么用的。带着这些问题,我们来继续看下代码
核心线程复用和非核心线程超时设置原理
这个主要看下Worker这个类就好了,在上述代码中,w = new Worker(firstTask);中创建了Worker实例,来看下这个类
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
.....
Worker(Runnable firstTask) {
构造方法中初始化了两个变量,firstTask 和 由线程工厂创建的线程
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
下面来关注一下关键的run方法,
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
很明显,如果这个while一直执行的话,那么这个线程就不会被销毁,也就能达到复用的目的
如果当前的task不为null,那么直接当前的task,如果为null,则通过getTask()去阻塞队列里获取任务
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);
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 {
回收线程操作
processWorkerExit(w, completedAbruptly);
}
}
根据以上注释,我们可以看到,线程之所以能够维持复用的原因就在于while这个循环,任务执行结束后,在while循环中去阻塞队列中再去获取任务执行,那么这样就达到了线程复用的目的,而不用再去新建线程执行任务了,所以这里看下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?
allowCoreThreadTimeOut是核心线程超时设置,默认核心线程没有超时限制
如果工作线程数大于核心线程数则为true,否则为false
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
根据上述注释,如果timed为false,则为核心线程,执行workQueue的take操作,这是一个阻塞操作,所以核心线程不死的原因就在这里。
如果timer为true,则为非阻塞线程,那么会等待keepAliveTime的时间,如果超过keepAliveTime这个时间仍然未获得任务,则该线程执行完毕,销毁。否则执行队列中的任务。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
总结
- 线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker
- Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
- 。Worker被创建出来后,就会不断地进行轮询(通过getTask()),然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。