- 为什么要使用线程池?
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用 线程池,必须对其实现原理了如指掌。
- Executors提供的四种线程池:
newSingleThreadExecutor,newFixedThreadPool,newCachedThreadPool,newScheduledThreadPool ,请说出他们的区别以及应用场景
newSingleThreadExecutor 只会创建一个线程,适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景
newFixedThreadPool创建固定的线程,使用于需要限定当前线程数量的应用场景,适用于负载比较重的服务器。
newCachedThreadPool创建一个会根据需要创建新线程的线程池,是大小无界的,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器。
newScheduledThreadPool 创建一个可以指定线程的数量的线程池,但是这个线程池还带有
延迟和周期性执行任务的功能,适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
- 线程池有哪几种工作队列?
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原 则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通 常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用 移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工 厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
- 线程池默认的拒绝策略有哪些
·AbortPolicy:直接抛出异常。
·CallerRunsPolicy:只用调用者所在线程来运行任务。
·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
·DiscardPolicy:不处理,丢弃掉。
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录
日志或持久化存储不能处理的任务
- 如何理解有界队列和无解队列
有界和无界不仅仅指在创建线程时线程的个数是很大的值,比如newCachedThreadPool中maximumPool的大小是Integer.MAX_VALUE,这就可以看成是无界的,而newSingleThreadExecutor,newFixedThreadPool线程池虽然在创建时限制了线程的数理,但是用到的是LinkedBlockingQueue这是一个无界队列,根据线程池的工作原理,当核心线程用完后任务会添加的队列中
- 线程池是如何实现线程回收的? 以及核心线程能不能被回收?
非核心线程,在使用完一定时间后就会被回收。核心线程一般不会回收设置核心线程设置允许超时后,核心线程也会被回收如:
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,3,1000, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<Runnable>(20));
executor.allowCoreThreadTimeOut(true);//设置核心线程允许超时后,核心线程也会被回收
线程池会动态回收(maximumPool-corePool)这部分线程,线程池创建的时候默认是不创建线程的,可以通过调用executor.prestartAllCoreThreads();在启动创建核心线程
- FutureTask是什么
FutureTask是一个实现RunnableFuture接口的一个类,而RunnableFuture 是一个接口,它继承了 Runnable 和 Future 这两个接口可以监控任务的运行状态
Future 表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获
取任务的结果和取消任务等。
- Thread.sleep(0)的作用是什么
触发操作系统立刻重新进行一次CPU竞争,让其他线程有机会执行,防止一个线程长时间占用CPU资源
9. 如果提交任务时,线程池队列已满,这时会发生什么
会交给饱和策略来处理
- 如果一个线程池中还有任务没有执行完成,这个时候是否允许被外部中断?
可以通过FutureTask来控制
如何合理配置线程池的大小
如何合理配置线程池大小,也是很多同学反馈给我的问题,我也简单说一下。线程池大小不
是靠猜,也不是说越多越好。
在遇到这类问题时,先冷静下来分析
1. 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
2. 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉
及到网络传输以及底层系统资源依赖有关系
如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu
的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行
线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上
下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1
如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,
导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等
待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。
一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程 CPU 时间)/
线程 CPU 时间 )* CPU 数目
这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner
测试大量运行次数求出平均值)线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实 际中如果需要 线程池创建之 后立即创建线 程,可以通过 以下两个方法 办到:
prestartCoreThread():初始化一个核心线程; prestartAllCoreThreads():初始化所有核心线
程
ThreadPoolExecutor tpe=(ThreadPoolExecutor)service;
tpe.prestartAllCoreThreads();
线程池的关闭
ThreadPoolExecutor 提 供 了 两 个 方 法 , 用 于 线 程 池 的 关 闭 , 分 别 是 shutdown() 和
shutdownNow(),其中:shutdown():不会立即终止线程池,而是要等所有任务缓存队列中
的任务都执行完后才终止,但再也不会接受新的任务 shutdownNow():立即终止线程池,并
尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
线程池容量的动态调整
ThreadPoolExecutor 提 供 了 动 态 调 整 线 程 池 容 量 大 小 的 方 法 : setCorePoolSize() 和
setMaximumPoolSize(),setCorePoolSize:设置核心池大小 setMaximumPoolSize:设置线
程池最大能创建的线程数目大小
任务缓存队列及排队策略
在前面我们多次提到了任务缓存队列,即 workQueue,它用来存放等待执行的任务。
workQueue 的类型为 BlockingQueue,通常可以取下面三种类型:
1. ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2. LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默
认为 Integer.MAX_VALUE;
3. SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个
线程来执行新来的任务。