目录
什么是线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池的核心逻辑是提前创建好若干个线程放在一个容器中。如果有任务需要被处理,则将任务直接分配给线程池中的线程来执行,任务处理完之后线程不会被立即销毁,而是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建大量线程增加开销。
使用线程池的优势
1.降低创建线程和销毁线程的性能开销。
2.提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行。
3.合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题。
简单使用
通过执行结果可以体现出线程池的特性
public class SelfThreadPool implements Runnable{
/**
* 伸缩性,可以动态调整线程数。空闲线程会在60S以后被回收
*/
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
/**
* 固定线程数的线程池
*/
static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
/**
* 定时任务的线程池
*/
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
/**
* 只有一个核心线程的线程池
*/
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
/**
* fork/join 线程池
*/
ExecutorService workStealingPool = Executors.newWorkStealingPool();
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread Name:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
fixedThreadPool.execute(new SelfThreadPool());
}
fixedThreadPool.shutdown();
}
}
ThreadpoolExecutor
线程池的创建都是基于ThreadpoolExecutor来创建的。找到该类参数最多的构造方法。
corePoolSize : 核心线程数量
maximumPoolSize : 线程池所允许的最大线程数量
keepAliveTime : 超时时间,超出核心线程数量以外的线程的空余存活时间
unit : 超时时间单位
workQueue : 阻塞队列,保存执行任务的队列
threadFactory : 线程工厂-创建线程的工厂对象
handler : 拒绝策略,当任务无法执行的时候的处理方式
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
Java中提供的可创建的线程池的种类
调用Executors的工厂方法,可以创建不同种类的线程池
newFixedThreadPool:返回一个固定数量的线程池,线程数不变,当有一个任务提交的时候。若线程池空闲,则立即执行。若线程池不空闲,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。此线程池的核心线程数和最大线程数都是指定值。当线程池中的线程数达到核心线程数量之后,任务都会被放到阻塞队列中。线程反复从队列中取任务执行
newSingleThreadExecutor:创建只有一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。由于只有一个线程,保证所有任务按照指定顺序(FIFO,LIFO优先级)执行
newCachedThreadPool:返回一个可以根据实际情况调整线程个数的线程池,不限制最大线程数量,若有空闲的线程则执行任务,若无空闲的线程将会继续创建线程。并且每一个线程超过空闲时间都会被回收。
newScheduledThreadPool:创建一个可以指定线程的数量的线程池,该线程还带有延迟和周期性执行任务的功能,类似定时器。
workStealingPool :fork/join 线程池
线程池的原理
为什么线程池要比手动创建线程高效?
线程池初始化是没有创建线程的,线程池的线程初始化与其它创建线程的方式一样,但是在完成任务以后。线程池中线程不会立即自行销毁,而是以挂起的状态返回到线程池。当有应用程序再次向线程池发出请求时,线程池里挂起的任务就会再度激活执行任务。这样做节省了创建线程所造成的性能损耗,也可以让多个任务反复重用同一个线程,从而在应用程序生存周期内节约大量开销。
线程池原理流程图
源码分析学习
基于入口,首先看execute方法。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
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);
}
else if (!addWorker(command, false))
reject(command);
}
关于ctl
AtomicInteger是一个原子类,它的主要作用是用来保存线程的数量和线程的状态。如下代码,一个int的数值是32个bit位,高三位来保存运行状态,低29位来保存线程数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// rs-> -1 左移 29位 wc-> 0 即 111|000 -》 111
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 方法 ctlOf 参数 rs
// -1 左移 29位 -1 的二进制是32个1
// -1 左移 29位 结果为 111
private static final int RUNNING = -1 << COUNT_BITS;
// 32-3 = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 1 左移 29 位是 0000 1000 00000 00000 0000 0000 0000 0000
// 减1 表示 线程的最大容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
addWorker
如果工作线程数小于核心线程数,调用该方法。即创建一个工作线程。该方法主要做了两件事
1.采用CAS操作来将线程数+1
2.新建一个线程并启用
总结:新建了一个 Worker 对象,该对象中维护了一个初始化任务-firstTask 何一个线程对象 final Thread thread 初始化Worker对象的时候完成对 firstTask 和 thread 的初始化。
private boolean addWorker(Runnable firstTask, boolean core) {
// 避免死循环
retry:
for (;;) {
int c = ctl.get();
// 获得线程的运行状态
int rs = runStateOf(c);
// 1.若是线程处于 shutdown 状态,还要添加新任务,直接拒绝
// 2.
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操作自增,线程数+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 {
// 构建一个 Worker 对象
w = new Worker(firstTask);
// 从 Worker 里面取出线程
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) {
// 这里线程 t 调用start方法 本质上是启动线程执行了worker对象中run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker类
通过分析源码:addWorker方法构造了一个Worker。该类继承了AQS,并实现了Runnable接口。继承AQS的目的主要是为了实现同步阻塞。
1.每一个Worker中包含一个firstTask,即初始化时首要被执行的任务。
2.最终执行任务的是runWorker()方法
总结:需要被执行的任务作为参数传递给Worker对象,Worker对象初始化的时候会调用线程工厂方法创建线程,Worker对象创建成功会保存在Set集合中。Worker创建并添加成功之后会由Worker对象中新建的线程执行start()方法启动线程。这里start()方法启动后,启动对应的run方法是worker对象中的run方法。最终调用了runWorker方法。
相对应的,如果添加worker失败,则会做失败后的处理。主要是
1.如果Worker已经构造好了,则从Workers集合中移除这个worker
2.原子递减核心线程数
3.尝试结束线程池
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
/** 由线程工厂创建的工作线程--创建失败则为空 */
final Thread thread;
/** Initial task to run. Possibly null. */
/** 需要执行的 task */
Runnable firstTask;
/** Per-thread task counter */
/** 执行任务计数器 */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
runWorker
该方法是线程池执行任务的真正逻辑
1.task不为空,则开始执行任务。
2.如果task为空,则通过getTask()从任务队列中去获取任务。
3.执行完毕后,通过while循环继续尝试获取任务并执行。getTask()为空,则整个runWorker()方法执行完毕。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 表示当前work线程允许被中断 Worker初始化时默认的 state = -1
// 通过调用 Worker 类的 tryRelease() 方法,将 state 设置为 0
// 当获取锁的时候 state 状态设置为 1
// interruptIfStarted() 中只有 state >= 0 才允许被中断
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 实现线程复用
while (task != null || (task = getTask()) != null) {
// 上锁 为了在 shutdown() 时不终止正在运行的 worker
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 {
// 执行任务中的 run 方法
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
// 需要再通过 getTask()获取任务 记录该 Worker 完成任务数量 + 解锁
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 将入参 worker 从数组 workers 里删除掉
processWorkerExit(w, completedAbruptly);
}
}
getTask方法
worker线程会从阻塞队列中获取需要执行的任务。
线程池中的线程是如何被干掉的?
当线程从工作队列中获取任务的时候,有默认的超时限制,如果线程在keepAliveTime的时间内获取不到任务,那么就会认为该条线程处于空闲状态,可以进行销毁。
总结:在执行execute方法时,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize。并且workQueue已满时,则可以增加工作线程。但是如果超时没有获取到任务,说明队列已经为空了,说明线程池不需要超过核心线程数量的线程去执行任务了,可以把多余的线程进行销毁了,保持线程数量在corePoolSize即可。
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.
// 做了两个判断 一:线程池状态为 shutdown 且 workQueue 为空
// (反映了shutdown状态的线程池还是要执行 workQueue 中剩余的任务的)
// 二:线程池状态为 stop
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// 判断是否需要进行超时控制
// allowCoreThreadTimeOut 默认为 false 核心线程不允许进行超时控制
// 当 当前线程池中的数量 大于 核心线程数量
// 对于超过核心线程数量的线程,需要进行超时控制
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;
}
}
}
当runWorker方法中while循环执行完毕以后,在finally中调用processWorkerExit来销毁工作线程。
execute方法-addWorker之后
首先判断线程池是否处于运行状态并且任务队列未满,则将任务添加到队列中。
如果核心池满了,队列也满了,这个时候可以创建非核心线程。如果非核心线程数也达到了最大线程数大小,拒绝任务,执行拒绝策略。
// 判断线程池是否处于运行状态,并且任务队列没有满
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);
}
// 核心池已满,队列已满,尝试创建新线程
else if (!addWorker(command, false))
// 创建失败 说明线程池被关闭或者线程池完全满了,拒绝任务
reject(command);
关于拒绝策略
1.AbortPolicy 直接抛出异常,默认策略。
2.CallerRunsPolicy 用调用者所在的线程来执行任务
3.DiscardOldestPolicy 丢弃阻塞队列中靠最前的任务,并执行当前任务
4.DiscardPolicy 直接丢弃任务
也可以根据场景实现RejectedExecutionHandler接口,自定义饱和策略,比如记录日志或持久化存储不能处理的任务。
线程池相关注意事项
阿里开发手册:线程池的构建不允许使用Executors去创建,而是要通过ThreadPoolExecutor的方式。
原因:使用Executors使得用户不需要关心线程池的参数配置。会导致一些问题,比如我们使用newFixedThreadPool或者singleThreadPool 允许的队列长度为Integer.MAX_VALUE。使用不当会有OOM的风险。而使用ThreadPoolExecutor来构建线程池的话,我们势必要了解线程池构造中每个参数的含义,使得开发者在配置参数的时候能够更加谨慎。
如何合理配置线程池的大小
1.首先要分析线程池执行任务的特性:CPU密集型还是IO密集型
2.每个任务执行的平均时长大概是多少,这个任务的执行时长可能还和任务处理逻辑是否涉及网络传输以及底层系统资源依赖有关系。
CPU密集型:CPU一直运行,利用率很高。线程数的配置应该根据CPU核心数来决定。CPU核心数=最大同时执行线程数。最大线程数=CPU核心数+1
IO密集型:主要进行IO操作,执行IO的时间较长。CPU处于空闲状态,导致CPU利用率不高,这种情况下可以增加线程池的大小。一般需要结合线程的等待时长做判断。等待时间越长,线程数相对越多。一般可以设置为CPU核心数的两倍。最佳数目可以依赖一个公式计算得出。
设定线程池最佳数目 = ((线程池设定的线程等待时间+CPU空闲时间)/ CPU空闲时间)*CPU数目。
相关细节
1.线程池初始化
默认情况下,线程池创建之后,线程池中是没有线程的,需要提交任务之后才会创建线程。实际使用的时候可以通过prestartCoreThread():初始化一个核心线程;prestartAllCoreThreads():初始化所有核心线程。
2.线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow().
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完才终止,但是不会接受新的任务。
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
3.任务队列
任务缓存队列,即workQueue,用来存放等待执行的任务。其类型为BlockingQueue
ArrayBlockingQueue :基于数组的先进先出队列,此队列创建时必须指定大小。
LinkedBlockingQueue :基于链表的先进先出队列,如果创建的时候没有指定此队列的大小,默认Integer.MAX_VAUE
SynchronousQueue : 特殊队列,不会保存提交的任务,而是直接创建一个线程执行新来的任务。
4.线程池的监控
线程池提供了相应的扩展方法,通过重写线程池的beforeExecute afterExecute shutdown 等方式
public class MonitorThreadPool extends ThreadPoolExecutor {
// 保存任务开始执行的时间 , 当任务结束时 , 用任务结束时间减去开始时间计算任务执行时间
private ConcurrentHashMap<String, Date> startTime;
public MonitorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.startTime = new ConcurrentHashMap<>();
}
public static ExecutorService newCachedThreadPool() {
return new MonitorThreadPool(0, Integer.MAX_VALUE, 60L,
TimeUnit.SECONDS, new SynchronousQueue());
}
@Override
public void shutdown() {
System.out.println("已经执行的任务数:"+this.getCompletedTaskCount()
+",当前活动线程数:"+this.getActiveCount()+",当前排队线程数:"+this.getQueue().size());
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
startTime.put(String.valueOf(r.hashCode()), new Date());
super.beforeExecute(t,r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
Date startDate = startTime.remove(String.valueOf(r.hashCode()));
Date finishDate = new Date();
Long diff = finishDate.getTime() - startDate.getTime();
System.out.print(" 任务耗时:"+diff);
System.out.print(" 初始线程数:"+this.getPoolSize());
System.out.print(" 核心线程数:"+this.getCorePoolSize());
System.out.print(" 正在执行的任务数量:"+this.getActiveCount());
System.out.print(" 已经执行的任务数"+this.getCompletedTaskCount());
System.out.print(" 任务总数:"+this.getTaskCount());
System.out.print(" 最大允许的线程数:"+this.getMaximumPoolSize());
System.out.print(" 线程空闲时间:"+this.getKeepAliveTime(TimeUnit.MILLISECONDS));
System.out.println();
super.afterExecute(r, t);
}
}
测试类
private static ExecutorService es = MonitorThreadPool.newCachedThreadPool();
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
es.execute(new TestMonitor());
}
es.shutdown();
}
Callable/Future使用以及原理分析
execute和submit的区别
execute | submit | |
接收参数 | Runnable | Runnable Callable |
如果出现异常会抛出 | 传入一个 Callable,可以得到一个 Future 的返回值 | |
没有返回值 | 传入 Callable 会得到一个Future的返回值 |
Callable/Future案例演示
public class CallableAndFuture implements Callable<String> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableAndFuture callableAndFuture = new CallableAndFuture();
FutureTask futureTask = new FutureTask(callableAndFuture);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
@Override
public String call() throws Exception {
return "get return value";
}
}
使用场景
Dubbo异步调用、消息中间件的异步通信和利用FutureTask Callable Thread 对耗时任务做预处理
简单分析
Callable是一个函数式接口,里面只有一个call方法。子类可以对该方法进行重写,并且该方法会有一个返回值。
FutureTask实现了RunnableFuture接口,该接口又继承了Runnable和Future接口。Future接口表示了一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。
FutureTask是Runnable和Future的结合,Runnable执行run方法计算结果,Future通过get方法获取结果
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
// 判断 当前的 Future 是否被取消,返回 true 表示已取消
boolean isCancelled();
// 当前 Future 是否已经结束。包括运行完成、抛出异常以及取消,都表示当前Future已经结束
boolean isDone();
// 获取 Future 的结果值,如果当前 Future 还没有结束,那么当前线程等待
// 直到 Future 运行结束,那么会唤醒等待结果值的线程的。
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}