这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战
JDK的线程池的标准实现一般有3种:
- java.util.concurrent.ForkJoinPool (”窃取“任务,一定场景下高吞吐量)
- java.util.concurrent.ScheduledThreadPoolExecutor (定时任务,异步的线程定时执行)
- java.util.concurrent.ThreadPoolExecutor (普通的线程池)
线程池的创建可以通过ThreadPoolExecutor的方法实现。
线程池的几个参数
- corePoolSize :核心线程数;
- maximumPoolSizeL:最大线程数;
- keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程,也即空闲线程的存活时间;
- TimeUnit:等待时间的单位;
- BlockingQueue:堵塞队列,用于存储等待执行的任务;
- ThreadFactory:线程工厂,用于生产线程;
- RejectedExecutionHandler:拒绝策略,当线程达到最大线程数时有新的任务进来,此时所采用的拒绝策略;
执行流程
当有新的任务进来时,线程数小于核心线程数,则创建一个新的线程去处理;当线程数等于核心线程数时,会将新的任务丢到堵塞队列中等候;当堵塞队列已满,且线程数小于最大线程数,创建新的线程;当线程数等于最大线程数了,还有新任务进来,则使用拒绝策略;
堵塞队列
一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。
线程工厂
线程工厂,顾名思义是为生成线程的地方,采用工厂模式封装了创建线程的细节;
默认是使用Executors.DefaultThreadFactory 实现的。
/**
* The default thread factory
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1); // 原子类,线程池编号
private final ThreadGroup group; // 线程组
private final AtomicInteger threadNumber = new AtomicInteger(1); // 线程数量
private final String namePrefix; // 线程名的前缀
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
// 获取线程组
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
// 创建线程,设置线程组,名字,任务
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
// 设置为非守护线程
if (t.isDaemon())
t.setDaemon(false);
// 设置为默认的优先级
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
也可以自己实现ThreadFactory,构建创建线程的方法。
拒绝策略
JDk自带了几种默认的拒绝策略实现。
- 默认拒绝策略(丢弃任务且抛异常):
ThreadPoolExecutor.AbortPolicy - 最旧淘汰策略(丢弃队列中最前面的任务):
ThreadPoolExecutor.DiscardOldestPolicy - 丢弃任务但不抛出异常:
ThreadPoolExecutor.DiscardPolicy - 由调用线程处理该任务:
ThreadPoolExecutor.CallerRunsPolicy
AbortPolicy 策略
可以看出,丢弃任务且抛出异常;
DiscardOldestPolicy 策略
如果线程池没关,则出队队首的任务,将下一个任务提交;
DiscardPolicy 策略
do nothing... 啥事不干,单纯丢弃任务
CallerRunsPolicy 策略
如果线程池没关,则在该线程上直接调用,即交给调用线程去处理了。
如果JDK提供的默认拒绝策略不符合业务场景,则可以直接实现RejectedExecutionHandler,重写rejectedExecution方法即可。
线程池提交任务的两种方式
Java中的线程池在进行任务提交时,有两种方式:execute和submit方法。
区别是:
- execute只能提交Runnable类型的任务,无返回值。submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。
- execute在执行任务时,如果遇到异常会直接抛出,而submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常。
使用示例
测试当线程数和队列已满时,实现自定义的拒绝策略,从结果中可以看出,c任务提交时执行了拒绝策略实现的方法。如果是采用默认的拒绝策略实现,则提交c任务时会抛出异常,注意任务a和b是会继续执行的。
参考资料
深入线程池原理:zhuanlan.zhihu.com/p/157116692