在执行一个异步任务或并发任务时,往往是通过直接new Thread()
方法来创建新的线程,这样做弊端较多。
更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:
Executors
newFixedThreadPool
创建一个指定大小的线程池,可控制线程的最大并发数,超出的线程会在LinkedBlockingQueue阻塞队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
创建一个单线程化的线程池,它只有一个线程,用仅有的一个线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行,所有的任务都保存在队列LinkedBlockingQueue中,等待唯一的单线程来执行任务。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程,线程池的大小上限为Integer.MAX_VALUE,可看作无限大。理论上来说,有多少请求,该线程池就可以创建多少的线程来处理。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
newScheduledThreadPool
创建一个定长的线程池,可以指定线程池核心线程数,支持定时及周期性任务的执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
通过观察以上线程池的构造方法,我们可以发现最后都是返回ThreadPoolExecutor对象,我们可以对其进行分析。
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;
}
execute()
异步执行传递进来的任务shutdown()
isShutdown()
用来判断线程池是否已经关闭
shutdownNow
和 shutdown
方法,isShutdown
会返回 true(即使还有任务正在运行)isTerminated()
任务全部执行完毕,并且线程池已经关闭,才会返回 truebeforeExecute
、afterExecute
、terminated
方法调用 ThreadPoolExecutor
类中的 remove()
方法
调用 ThreadPoolExecutor 类中的如下方法
如果让我们自己设计一个线程池,我们该如何做呢?
需求:实现线程的重复使用。
如何实现线程的服用
让线程实现服用的唯一方法,就是让线程不结束。
那如何让线程执行新的任务呢?
我们可以通过【共享内存】的方式来实现->比如:list.add(task);
线程会一直处于运行状态吗?
应该是有任务的时候,执行
没有任务的时候,阻塞
结论:我们可以通过阻塞队列的方式来实现线程池中线程的复用。
线程池的实现原理的流程图:
线程池中的核心线程是延迟初始化的。
先初始化核心线程。
调用阻塞队列的方法,把task存进去。(offer() -> true/false)
如果true ,说明当前的请求量不大, 核心线程就可以搞定。
false,增加工作线程(非核心线程)
如果添加失败,说明当前的工作线程数量达到了最大的线程数,直接调用拒绝策略
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//判断当前工作线程数是否小于核心线程数(延迟初始化)
if (workerCountOf(c) < corePoolSize) {
//添加工作线程的同时,执行command
if (addWorker(command, true))
return;
c = ctl.get();
}
//workQueue.offer 添加到阻塞队列
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); //拒绝策略
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//case1 通过原子操作来增加线程数量.
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;
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
}
}
//case2 初始化工作线程.
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.add(w);//添加到一个容器中。
int s = workers.size();
if (s > largestPoolSize) //重新更新largestPoolSize
largestPoolSize = s;
workerAdded = true; //添加成功。
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //启动线程
workerStarted = true;
}
}
} finally {
//失败, 回滚。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
t 是worker中的线程,我们可以看到worker类实现了Runable接口,所以t.start后触发了worker类中的run方法。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
}
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
// 获取当前线程对象的引用
Thread wt = Thread.currentThread();
// 获取worker的firstTask
Runnable task = w.firstTask;
// 获取完之后把worker的firstTask置为null 防止下次获取到
w.firstTask = null;
// 初始化worker的state = 0, exclusiveOwnerThread = null 解锁
w.unlock(); // allow interrupts
// 如果发生异常 当前线程突然退出 该值为true
boolean completedAbruptly = true;
try {
//while循环保证当前线程不结束. 直到task为null
while (task != null || (task = getTask()) != null) {
//表示当前线程正在运行一个任务,如果其他地方要shutdown().你必须要等我执行完成。
w.lock(); //Worker继承了AQS -> 实现了互斥锁
// 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(); //执行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);
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
//cas 自旋
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果线程池已经结束状态,直接返回null. 需要清理掉所有的工作线程
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//是否允许超时
// * allowCoreThreadTimeOut 为true
// * 如果当前的工作线程数量大于核心线程数
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) //cas 减少工作线程数量。
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;
}
}
}
需要针对具体情况而具体处理,不同的任务类别应采用不同规模的线程池,任务类别可划分为CPU密集型任务、IO密集型任务和混合型任务。
利用线程池提供的参数进行监控,参数如下:
通过扩展线程池进行监控:继承线程池并重写线程池的beforeExecute(),afterExecute()和terminated()方法,可以在任务执行前、后和线程池关闭前自定义行为。如监控任务的平均执行时间,最大执行时间和最小执行时间等。
我们也可以在运行过程中动态设置线程池的相关参数,比如核心线程数,最大线程数,阻塞队列长度等。
首先通过如下代码了解核心线程数的动态设置。代码中使用的是固定参数,实际操作中可以配合注册中心使用。
public class DynamicDemo {
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 6, 60,
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
printPoolStatus("before");
//动态设置
threadPoolExecutor.setCorePoolSize(4); //可以将动态的值存放在配置中心
threadPoolExecutor.setMaximumPoolSize(14);
printPoolStatus("after");
}
public static void printPoolStatus(String name) {
System.out.println(name + "核心线程数量:" + threadPoolExecutor.getCorePoolSize() + ""
+ "最大线程数:" + threadPoolExecutor.getMaximumPoolSize());
}
}
输出结果如下,可以看到动态修改成功。
before核心线程数量:2最大线程数:6
after核心线程数量:4最大线程数:14
如果我们需要修改队列长度就需要重新实现LinkedBlockingQueue了。因为从代码中我们可以发现队列容量是一个final属性,我们是不能修改的。
所以我们直接复制LinkedBlockingQueue的代码到ReSizeLinkedBlockingQueue中,并对其修改。添加一个设置容量的方法,同时取消final属性。
//设置容量的方法
public void setCapacity(int capacity) {
int oldCapacity = this.capacity;
this.capacity = capacity;
int size = count.get();
if (capacity > size && size >= oldCapacity) {
//唤醒
signalNotEmpty();
}
}
/**
* The capacity bound, or Integer.MAX_VALUE if none
*/
//private final int capacity;
private int capacity;
重新测试:
public class DynamicDemo {
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 6, 60,
TimeUnit.SECONDS, new ReSizeLinkedBlockingQueue<>(30));
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
printPoolStatus("before");
//动态设置
threadPoolExecutor.setCorePoolSize(4); //可以将动态的值存放在配置中心
threadPoolExecutor.setMaximumPoolSize(14);
ReSizeLinkedBlockingQueue rb = (ReSizeLinkedBlockingQueue) threadPoolExecutor.getQueue();
rb.setCapacity(100);
printPoolStatus("after");
}
public static void printPoolStatus(String name) {
ReSizeLinkedBlockingQueue rb = (ReSizeLinkedBlockingQueue) threadPoolExecutor.getQueue();
System.out.println(name + "核心线程数量:" + threadPoolExecutor.getCorePoolSize() + ""
+ "最大线程数:" + threadPoolExecutor.getMaximumPoolSize() + ""
+ "队列长度:" + (rb.size() + rb.remainingCapacity()));
}
}
运行结果如下:
before核心线程数量:2最大线程数:6队列长度:30
after核心线程数量:4最大线程数:14队列长度:100