1、什么是线程池?有哪几种创建方式?
答: 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java
中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。Java5+
中的 Executor
接口定义一个执行线程的工具。它的子类型即线程池接口是 ExecutorService
。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类 Executors
面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
newSingleThreadExecutor
: 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。newFixedThreadPool
: 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用newFixedThreadPool
方法来创建线程池,这样能获得更好的性能。newCachedThreadPool
: 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM
)能够创建的最大线程大小。newScheduledThreadPool
: 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
2、线程池有什么优点?
- 降低资源消耗: 重用存在的线程,减少对象创建销毁的开销。
- 提高响应速度: 可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,
- 避免堵塞: 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- 附加功能: 提供定时执行、定期执行、单线程、并发数控制等功能。
3、线程池都有哪些状态?
RUNNING
: 这是最正常的状态,接受新的任务,处理等待队列中的任务。SHUTDOWN
: 不接受新的任务提交,但是会继续处理等待队列中的任务。STOP
: 不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。TIDYING
: 所有的任务都销毁了,workCount
为 0,线程池的状态在转换为TIDYING
状态时,会执行钩子方法terminated()
。TERMINATED
:terminated()
方法结束后,线程池的状态就会变成这个。
4、什么是 Executor
框架?为什么使用 Executor
框架?
答:Executor
框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。
每次执行任务创建线程new Thread()
比较消耗性能,创建一个线程是比较耗时、耗资源的,而且无限制的创建线程会引起应用程序内存溢出。
所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用
Executors
框架可以非常方便的创建一个线程池。
5、线程池中 submit()
和 execute()
方法有什么区别?
- 接收参数:
execute()
只能执行Runnable
类型的任务。submit()
可以执行Runnable
和Callable
类的任务。 - 返回值:
submit()
方法可以返回持有计算结果的Future
对象,而execute()
没有。 - 异常处理:
submit()
方便Exception
处理。
6、什么是线程组,为什么在 Java
中不推荐使用?
答:ThreadGroup
类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。
线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
ThreadPoolExecutor()
是最原始的线程池创建,也是阿里巴巴 Java 开发手册中明确规范的创建线程池的方式。
7、线程池的执行流程
- 提交任务后会首先进行当前工作线程数与核心线程数的比较,如果当前工作线程数小于核心线程数,则直接调用
addWorker()
方法创建一个核心线程去执行任务; - 如果工作线程数大于核心线程数,即线程池核心线程数已满,则新任务会被添加到阻塞队列中等待执行,当然,添加队列之前也会进行队列是否为空的判断;
- 如果线程池里面存活的线程数已经等于核心线程数了,且阻塞队列已经满了,再会去判断当前线程数是否已经达到最大线程数 ****
maximumPoolSize
,如果没有达到,则会调用addWorker()
方法创建一个非核心线程去执行任务; - 如果当前线程的数量已经达到了最大线程数时,当有新的任务提交过来时,会执行拒绝策略。
8、ThreadPoolExecutor
构造函数重要参数分析
8.1、ThreadPoolExecutor
3 个最重要的参数:
corePoolSize
: 核心线程数,线程数定义了最小可以同时运行的线程数量。maxinumPoolSize
: 线程池中允许存在的工作线程的最大数量。workQueue
: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放在任务队列中。
8.2、ThreadPoolExecutor
其他常见参数:
keepAliveTime
: 线程池中的线程数量 大于corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime
才会被回收销毁;unit
:keepAliveTime
参数的时间单位。threadFactory
: 为线程池提供创建新线程的线程工厂;handler
: 线程池任务队列超过maxinumPoolSize
之后的拒绝策略
EagerThreadPoolExecutor
线程池通过自定义队列的这么一种形式,改写了线程池的机制。这种线程池的机制是核心线程数不够了,先起线程,当线程达到最大值后,后面的任务就丢进队列
9、ThreadPoolExecutor
饱和策略?
答: ThreadPoolExecutor
**饱和策略定义:**如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了。
ThreadPoolTaskExecutor
定义一些策略:
ThreadPoolExecutor.AbortPolicy
: 抛出RejectedExecutionException
来拒绝新任务的处理;ThreadPoolExecutor.CallerRunsPolicy
: 调用执行自己的线程运行任务。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。ThreadPoolExecutor.DiscardPolicy
: 不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy
: 此策略将丢弃最早的未处理的任务请求。
举个例子:
Spring
通过ThreadPoolTaskExecutor
或者我们直接通过ThreadPoolExecutor
的构造函数创建线程池的时候,当我们不指定RejectedExecutionHandler
饱和策略的话来配置线程池的时候默认使用的是ThreadPoolExecutor.AbortPolicy
。在默认情况下,ThreadPoolExecutor
将抛出RejectedExecutionException
来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用ThreadPoolExecutor.CallerRunsPolicy
。当最大池被填满时,此策略为我们提供可伸缩队列。
自定义拒绝策略:
public class RejectThreadPoolDemo {
public static class MyTask implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis()
+ ":Thread ID:" + Thread.currentThread().getId());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]) throws InterruptedException {
MyTask myTask = new MyTask();
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(10),
Executors.defaultThreadFactory(),
new RejectedExecutionHandler() {
**@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//打印丢弃的任务
System.out.println(r.toString() + " is discard");
}
}**
);
for (int i = 0; i < 100; i++) {
executorService.submit(myTask);
Thread.sleep(10);
}
}
}