线程池
由于创建线程需要资源和时间,所以并不是线程越多,执行指令速度越快。
使用线程池的好处:
- 效率变高。
直接使用线程池执行Java代码,执行时间如下所示
Long start = System.currentTimeMillis();
final Random random = new Random();
final List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
list.add(random.nextInt());
});
thread.start();
thread.join();
}
System.out.println("时间:" + (System.currentTimeMillis() - start));
System.out.println("大小:" + list.size());
而使用线程池的话,执行速度如下所示:
ExecutorService executorService = Executors.newCachedThreadPool(); // 最快
Long start = System.currentTimeMillis();
final Random random = new Random();
final List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
executorService.execute(() -> {
list.add(random.nextInt());
});
Thread thread = new Thread(() -> {
list.add(random.nextInt());
});
thread.start();
thread.join();
}
System.out.println("时间:" + (System.currentTimeMillis() - start));
System.out.println("大小:" + list.size());
对比可知,效率不是提高了一点点。
- 降低资源消耗。
创建线程池的时候可以自定义核心线程数和线程失效时间,避免无用线程占用系统资源。 - 线程可管理
可以对线程池的线程统一进行管理,避免无休止创建线程使得资源耗尽。
创建线程池
JDK1.5自带了几种创建线程池的方式,分别是:
- Executors.newCachedThreadPool(); 创建一个可扩容的线程池,没有核心线程,容量大小为Interger.MAX_SIZE,定时回收空闲线程。
- Executors.newFixedThreadPool(10); 创建一个核心线程10大小的可缓存线程池,池中线程可以最大出来10个任务,超过的任务则会进入队列中等待。
- Executors.newScheduledThreadPool(5);创建一个定长周期线程池,可以根据周期规则定时执行任务。
- Executors.newSingleThreadExecutor();创建一个单线程实例的线程池,可以保证所有任务的优先级执行顺序。
JDK自带的线程池很鸡肋,一般不建议直接使用,例如使用 Executors.newCachedThreadPool();创建的线程池并没有对容量进行限制,导致如果任务多的话,为每个任务都创建一个线程池,最终会引起OOM。所以我们一般是自定义线程池执行。
自定义线程池有如下几个参数
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize: 核心线程数,在创建线程池是创建的线程数,是线程池中基本的线程数。
- maximumPoolSize: 最大线程数,线程池最大线程容量。
- keepAliveTime: 空闲时间,线程空闲时的存活时间。
- TimeUnit: 时间单位,线程空闲时的存活时间。
- workQueue: 任务队列,存放线程任务。
- threadFactory: 线程工厂,根据线程工程自定义线程对象。
- RejectedExecutionHandler: 拒绝策略,线程池和队列满了时调用的拒绝策略。
源码
execute
线程池任务执行的流程如下图所示:
public void execute(Runnable command) {
// 如果任务为空则抛出空指针一次
if (command == null)
throw new NullPointerException();
// 获取ctl,在ctl中可以获取当线程池的状态和线程数
int c = ctl.get();
// 判断当前线程数是否小于核心线程数,如果小于则添加一个Worker并直接返回
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 程序执行到这里就表示核心线程已满,该往队列中塞任务了。先判断线程池是否在运行,如果在运行的话则往队列中添加一条任务,如果线程池没运行的话就执行不到workQueue.offer(command)这条语句了
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 因为线程是随时都在可能发生改变的,所以这里要做双重检查检查线程池的状态,如果线程池未在执行,则从任务队列中移除这条任务并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果当前工作线程数为0则新建一个worker来从任务队列中获取任务进行执行, addWorker(null, false)似乎有点迷惑,我的理解是,因为上面的语句已经将任务保存到队列中了,所以这里就不需要传任务了,直接传空并从任务队列中拿取任务进行执行即可,这里还是必须要的,为了保证任务中的任务可以有线程执行。之前还在疑惑,这里是非必要的,因为下面还有个非核心线程的添加操作,但是仔细一想,还是很有必要到,如果没有这条语句的话,执行下面那条语句,那么任务队列中和workers中有两条一模一样的任务,不就执行了两次了吗?
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果线程池没在运行且往任务队列中添加任务失败了则添加非核心线程,添加失败则执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}
线程状态
线程池有以下几种状态:
- RUNNING: 接收新任务,并且也能处理阻塞队列中的任务。
- SHUTDOWN: 不接收新任务,但是却可以继续处理阻塞队列中的任务。
- STOP: 不接收新任务,同时也不处理队列任务,并且中断正在进行的任务。
- TIDYING: 所有任务都已终止,workercount(有效线程数)为0,线程转向 TIDYING 状态将会运行 terminated() 钩子方法。
- TERMINATED: terminated() 方法调用完成后变成此状态。
在线程池中使用Interger size前3位二进制来表示线程池的状态,剩余位数则是表示线程池的线程数。如下代码所示:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Interger 32位 前三位是线程状态 后29位则是线程数
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (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;
addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// 检查线程状态,如果线程状态是Shutdown、Stop、Tidying、Terminande那么也没必要创建线程了,直接返回false即可
// 如果线程处于shutdown状态且且传入的是null的话,那么表示该线程是传入的是空任务,用于执行队列中的任务,如果workQueue.isEmpty()当然直接返回false
// 如果firstTask不为空表示是直接执行任务的,但是线程池已经shutdown不能再添加任务了,当然直接返回false了。
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
// 如果当前任务为core那么直接根据当前工作线程数与core数进行对比,否则与最大线程数进行对比,如果超了或是等于了,那么表示线程池已经满了,直接返回false即可。
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 如果线程数没有超过那么更新工作线程
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 如果 更新工作线程数失败了则会执行到这里,之后校验线程池状态是否为Shutdown、Stop、Tidying、Terminand,如果不是的话则条出外层循环,进行重试操作
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
}
}
// 以上为第一部分代码,基本作用就是任务添加前的一系列校验功能 之后就是具体添加业务功能的逻辑代码了。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建Worrker,在worker的构造方法中可以看到使用线程工程方法创建线程并赋值到threa成员变量中。
w = new Worker(firstTask);
final Thread t = w.thread;
// 如果t不等于空那么表示线程创建成功了
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 c = ctl.get();
// 再次校验线程池状态 校验没什么问题则将实体添加到works中 并设置添加状态为true
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
// 判断线程池最大大小是否大于works size,如果大于则进行更新
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
// 判断线程是否添加成功,如果添加成功则执行线程
if (workerAdded) {
container.start(t);
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 添加线程失败
addWorkerFailed(w);
}
return workerStarted;
}
```
## Worker类
```java
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 根据工程方法创建对象并赋值到成员变量中 将自身作为线程实例化,之后再Addworker方法中执行线程时则会自动跳到run方法中
this.thread = getThreadFactory().newThread(this);
}
worker类实现了Runnable,这表示worker类本身就是一个线程
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 获取线程任务
Runnable task = w.firstTask;
w.firstTask = null;
// 解锁
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 如果任务不为空 或任务队列中的获取到的任务部位空 如果task为空则去任务队列中获取任务进行执行
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);
try {
// 具体我们的需要线程池执行的业务功能
task.run();
// 线程执行后
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
// 清空 task 否则while会死循环一直执行一个task
task = null;
// 完成的任务数 + 1
w.completedTasks++;
// 解锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 处理线程执行完毕
processWorkerExit(w, completedAbruptly);
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果在执行worker中出现异常 工作现场数 - 1
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 计算所有以完成的线程数
completedTaskCount += w.completedTasks;
// workers - 1
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
// 依旧是一系列判断线程池状态
if (runStateLessThan(c, STOP)) {
// 如果线程在执行的过程中没有抛出异常
if (!completedAbruptly) {
// 获取
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 工作线程数大于等于最小值,直接返回不新增非核心线程
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 添加一个工作线程用于执行下一个任务队列中的任务
addWorker(null, false);
}
}
虽然道路是曲折的,但前途是光明的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律