目录
一、线程和线程池的效率对比
二、Java线程池
三、线程池源码分析
四、ScheduledThreadPoolExecutor源码分析
使用线程方式执行程序
/***
* 使用线程的方式去执行程序
*/
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Long start = System.currentTimeMillis();
final Random random = new Random();
final List list = new ArrayList();
for (int i = 0; i < 100000; i++) {
Thread thread = new Thread(() -> list.add(random.nextInt()));
thread.start();
thread.join(); // 阻塞主线程,知道所有线程执行完成
}
System.out.println("时间:" + (System.currentTimeMillis() - start)+ " list大小:" + list.size());
}
}
/***
* 使用线程池执行
*/
public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
Long start = System.currentTimeMillis();
final Random random = new Random();
final List list = new ArrayList();
ExecutorService executorService = Executors.newSingleThreadExecutor(); // 线程复用
for (int i = 0; i < 100000; i++) {
executorService.execute(() -> list.add(random.nextInt()));
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);// 阻塞,等各子线程都运行完毕后再执行
System.out.println("时间:" + (System.currentTimeMillis() - start) + " list大小:" + list.size());
}
}
可以发现,使用线程池去执行程序,效率要远远高于单个线程。
主要原因在于,使用单个线程的方式去执行程序,会导致线程重复创建且无法复用,使用线程池方式,可以实现线程复用,整个过程只创建了两个线程,即主线程和线程池中线程,线程池在执行任务的时候,并不会再重新创建线程(源码分析中会详细说明)。
常见的线程池有四种。
ScheduledThreadPool 源码后边分析,其他三种线程池的对比图示如下:
线程池执行原理图示:
线程池源码流程图
接下来,分析源码。首先从使用线程池开始
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolTest {
public static void main(String[] args) {
// 三种线java程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();//快
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);//慢
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//最慢
// 自定义拒绝策略
RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// 核心改造点,由 Blockingqueue 的offer改成put阻塞方法
executor.getQueue().put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(10), rejectedExecutionHandler);
for (int i = 1; i <= 100; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
}
}
/***
* 模拟项目代码
*/
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程,正在执行第" + i + "个任务");
try {
Thread.sleep(3000L);//模拟业务处理逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
在代码中,我们使用到了线程池的创建类
public ThreadPoolExecutor(int corePoolSize, // 核心线程数量
int maximumPoolSize, // 最大线程数量
long keepAliveTime, // 存活时间
TimeUnit unit, // 时间单位
BlockingQueue 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;
}
然后调用到了ThreadPoolExecutor的核心方法execute(),源码如下:
public void execute(Runnable command) { // 提交优先级
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { // 1-判断是否小于核心线程数
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { // 2-如果是运行状态,放入对列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) // 非运行状态,去除
reject(command); // 决绝策略
else if (workerCountOf(recheck) == 0)
// 判断当前工作线程池数是否为0
// 如果是创建一个null任务,任务在堵塞队列存在了就会从队列中取出
// 意义,保证线程池在running状态必须有一个任务在执行
addWorker(null, false);
}
else if (!addWorker(command, false)) // 3-创建临时线程
reject(command); // 4-执行拒绝策略
}
如果满足线程池执行任务要求,会调用addWorker()方法,创建一个线程,然后去队列中获取任务,有关Worker的继承关系图如下
addWorker()方法源码
private boolean addWorker(Runnable firstTask, boolean core) {
retry: // goto写法 用于重试
for (;;) { // 1-参数校验循环
int c = ctl.get();
// 获取当前线程状态
int rs = runStateOf(c);
// 线程状态非运行状态&&非shutdown状态,任务为空,队列非空,不能新增线程
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
}
}
// 以下逻辑才是真正的开始做事
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// Worker为内部类,里边封装了线程和任务,通过threadfactory创建线程
w = new Worker(firstTask); // 2-创建一个工作线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock; // 上锁
mainLock.lock();
try {
// 重新获取当前线程状态
int rs = runStateOf(ctl.get());
// 继续判断状态,小于shutdown就是running状态
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // 添加到工作线程集合(Set)
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s; // 记录最大线程数
workerAdded = true; // 把标记设为true
}
} finally {
mainLock.unlock();
}
if (workerAdded) { // 如果标记为true,调用线程start()方法
t.start(); // 3-会去执行run()方法
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w); // 4-失败回退 从wokers移除w 线程数减1 尝试结束线程池
}
return workerStarted;
}
当执行到start()方法时,会调用到内部类Worker中的run()方法
runWorker()方法中的逻辑如下
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts 允许中断,Worker创建时不允许中断(创建时会带一个标记)
boolean completedAbruptly = true;
try {
// 1-非常关键:执行优先,只有任务为空,才会去从队列中获取任务 getTask()
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为任务,而非线程
task.run(); // 2-线程池里边执行的是run()方法的调用,而非多个start()
} 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++; // 完成任务数+1
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); // 4-工作线程的退出逻辑,线程复用
}
}
这里有一个细节,自定义的run()方法一定要做try catch ,不要直接抛异常,因为run()中的异常会被吃掉,并不会抛出来。最后,看一下 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);
// timed是标志超时销毁,核心线程池也是可以被销毁的
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try { // 核心:移除并返问队列头部的元素,如果为空,take()会阻塞
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
首先,对ScheduledThreadPoolExecutor具体使用示例如下
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ScheduledThreadPoolExecutorExample {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(5);
Task task = new Task("Task");
System.out.println("Created : " + task.getName());
// executor.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s执行一次任务
// executor.scheduleWithFixedDelay(task, 0, 2, TimeUnit.SECONDS); // 周期性执行,任务执行时间(3s)+延迟执行时间(2s)=5s
executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);// 稳定定时器(固定任务时间3s)
}
}
// 任务
class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void run() {
System.out.println("Executing : " + name + ", Current Seconds : " + new Date().getSeconds());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
示例中,最后调用了scheduleAtFixedRate()方法,延迟周期性执行任务,scheduleAtFixedRate()方法源码如下
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
// 使用的内部类ScheduledFutureTask,Task中具备run()方法
ScheduledFutureTask sft =
new ScheduledFutureTask(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
// 空实现,拓展方法,可以对线程池进行拓展
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
// 具体执行,相当于其他线程的execute()方法
delayedExecute(t);
return t;
}
跟进delayedExecute()方法
private void delayedExecute(RunnableScheduledFuture> task) {
if (isShutdown())
reject(task);
else {
// 与ThreadPoolExecutor不同,这里直接把任务加入延迟队列
super.getQueue().add(task);
// 如果当前状态无法执行任务,移除任务,并把任务标记设置为取消
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
// 增加Worker,确保提交的任务能够被执行
ensurePrestart();
}
}
继续跟进ensurePrestart()方法,可以发现,真正执行任务时,还是使用的addWorker()方法,在该方法中会生成线程,然后调用start()方法,之后会执行任务的run()方法
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
在该线程池中,任务不再Worker的封装类,而是上边提到的ScheduledFutureTask,所以需要跟进到ScheduledFutureTask中的run()方法
public void run() {
// 1-判断是否是周期性任务
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
// 2-如果非周期性任务直,接调用run运行
else if (!periodic)
ScheduledFutureTask.super.run();
// 3-如果成功runAndRest,则设置下次运行时间并调用reExecutePeriodic。
else if (ScheduledFutureTask.super.runAndReset()) {
// 时间设置
setNextRunTime();
// 重新将任务(outerTask)放到工作队列中
reExecutePeriodic(outerTask);
}
}
跟进时间设置的方法setNextRunTime(),线程池设置的参数不同,延迟时间的处理也不同
private void setNextRunTime() {
long p = period;
// fixed‐rate模式,时间设置为上一次时间+p。
if (p > 0)
time += p;
// fixed‐delay模式,计算下一次任务可以被执行的时间
else
time = triggerTime(-p);
}
返回来,跟进 reExecutePeriodic() 方法
void reExecutePeriodic(RunnableScheduledFuture> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task); // 把任务重新放入延迟对列
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
// 再次去执行任务
ensurePrestart();
}
}
这里重点跟一下
super.getQueue().add(task); // 把任务重新放入延迟对列
Runnable任务添加的逻辑如下:
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture> e = (RunnableScheduledFuture>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
// 容量扩增50%。
grow();
size = i + 1;
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
// 使用堆排序
siftUp(i, e);
}
// 如果新加入的元素成为了堆顶,则之前的leader就无效了,设置为Null
if (queue[0] == e) {
leader = null;
// 由于原先leader已经无效被设置为null了,这里随便唤醒一个线程(未必是原先的leader)来取走堆顶任务。
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
offer()中,有一个堆排序的siftUp()方法,跟进去看下
private void siftUp(int k, RunnableScheduledFuture> key) {
while (k > 0) {
// 1-获取父节点
int parent = (k - 1) >>> 1;
RunnableScheduledFuture> e = queue[parent];
// 2-如果key节点的执行时间大于父节点的执行时间,无需排序
if (key.compareTo(e) >= 0)
break;
// 3-key节点的执行时间小于父节点的执行时间,需要把父节点移到后面
queue[k] = e;
setIndex(e, k);
k = parent;
}
// 4-把key设置到排序后的位置
queue[k] = key;
setIndex(key, k);
}
以上逻辑可以理解为:循环的根据key节点与它的父节点来判断,如果key节点的执行时间小于父节点,则将两个节点 交换,使执行时间靠前的节点排列在队列的前面。 其实就是一个树形的最小堆结构,在最小堆结构中父节点一定小于子节点
在DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序(time小的排在前 面),若time相同则根据sequenceNumber排序( sequenceNumber小的排在前面)。