关于线程池
今天总结一下线程池😘
Java中的线程池相当于把很多线程放到一个池子里,随用随取,避免频繁的创建和销毁线程造成的资源损耗和提高响应速度,而且线程池可以根据需要动态地调整线程数量,以适应不同的工作负载,从而提高系统的灵活性和性能
线程池参数
先讲一下面试爱问的7大线程池参数💕:
arduino
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
- corePoolSize: 线程池中的核心线程数量,如果没有全局设置池内线程的过期时间,池内会维持此数量线程。
- maximumPoolSize: 线程池中的最大线程数量,当核心线程都在运行任务,并且阻塞队列中任务数量已满,此时会创建非核心线程。
- keepAliveTime & unit: 线程池中线程过期时间以及时间单位。
- workQueue: 存放线程池内任务的阻塞队列,如 ArrayBlockingQueue、LinkedBlockingQueue...
- threadFactory: 创建线程池中线程的线程工厂,可以在创建线程时初始化优先级、名称、守护状态...
- handler: 当线程池中全部线程都在运行,阻塞队列也满的时候,会将添加的任务执行拒绝策略,JDK 线程池中实现了四种拒绝策略,默认 AbortPolicy,抛出异常。
简单来讲就是先找核心线程数,如果核心线程数满了,就进去阻塞队列,等着来找核心线程,如果队列都满了,那就使用非核心线程来处理,如果都超过最大线程数了,就使用拒绝策略具体处理
魔改一下?😎
如果我们想要线程池快速消费呢?
那看一下线程池消费的过程,有个排队去找核心线程数的过程,这个排队就造成时间浪费了
那么能不能改一下让请求来了发现核心线程数满了,直接去找非核心线程去执行,是不是相当于速度快了
Dubbo中的实现😶🌫️
其实Dubbo中早有其影子🙌🙌🙌
首先Dubbo中自定义了一个TaskQueue 阻塞队列扩展了LinkedBlockingQueue:
scala
public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
...
// 队列中持有线程池的引用
private EagerThreadPoolExecutor executor;
public TaskQueue(int capacity) {
super(capacity);
}
public void setExecutor(EagerThreadPoolExecutor exec) {
executor = exec;
}
@Override
public boolean offer(Runnable runnable) {
...
// 获取线程池中线程数
int currentPoolThreadSize = executor.getPoolSize();
// 如果有核心线程正在空闲,将任务加入阻塞队列,由核心线程进行处理任务
if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
return super.offer(runnable);
}
/**
*【重点】当前线程池线程数量小于最大线程数
* 返回false,根据线程池源码,会创建非核心线程
*/
if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
return false;
}
// 如果当前线程池数量大于最大线程数,任务加入阻塞队列
return super.offer(runnable);
}
}
慢慢来看😊,这段代码中,意思是线程来了先去找核心线程,如果核心满了,看是否小于最大线程数,小于的话,为了实现快速消费的目的,就直接失败,大于最大线程数的话,就说明当前要处理的线程比较多,就都放入阻塞队列慢慢消费
另一方面:
Dubbo中的EagerThreadPoolExecutor 封装了线程池,对原有的ThreadPoolExecutor类进行了扩展:
java
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
/**
* task count
*/
private final AtomicInteger submittedTaskCount = new AtomicInteger(0);
/**
* @return current tasks which are executed
*/
public int getSubmittedTaskCount() {
return submittedTaskCount.get();
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
submittedTaskCount.decrementAndGet();
}
@Override
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
// do not increment in method beforeExecute!
submittedTaskCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
// retry to offer the task into queue.
final TaskQueue queue = (TaskQueue) super.getQueue();
try {
if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.", rx);
}
} catch (InterruptedException x) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
} catch (Throwable t) {
// decrease any way
submittedTaskCount.decrementAndGet();
throw t;
}
}
}
我知道你很急,但是你先别急🙌🙌🙌
慢慢看,它主要是重写了其中的execute方法,它在里面定义了一个原子整数,用于记录已提交的任务数量
首先会对任务数量进行递增,然后尝试执行任务。如果任务执行被拒绝,会尝试将任务重新放入队列中。无论任务执行成功与否,已提交的任务数量都会递减
这样就能保证在高并发情况下 TaskQueue#offer(Runnable runnable) 做出逻辑处理
OK!完美,总的来说,不是很难💕💕💕