9. 线程池《JUC包下》-----面试重点
1.使用线程池的三个优点如下:(1.省资源2.提速度3.好管理)
1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁带来的消耗。
2. 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
3. 提高线程的可管理性:使用线程池可以统一进行线程分配、调度和监控。
2. 线程池继承关系:
0. Executors(线程池工具类)--内置的四大线程池
1.Executor-execute(Runnable command)
2.ExecutorService(普通调度池核心接口)
Submit(Callable或Rennable):Future<T>
3.ScheduleExecutorService(定时调度池核心接口)
schedule(Runnable或Callable command,
long delay, TimeUnit unit):延迟delay个时间单位后开始执行
scheduleAtFixedRate(Runnable command,
long initialDelay, long period,(周期)TimeUnit unit):延迟initialDelay个时间单位后开始执行,并且每隔period个单位就执行一次
4 .ThreadPoolExecutor
ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService
3.线程池执行任务流程:
当一个Runnable或callable对象到达线程池时,执行策略如下:
1)首先判断核心线程池里的线程是否都在执行任务。(创建线程需要获得全局锁(同步处理))
如果是,再次查看核心线程池是否已满,如果未满,创建新的线程执行任务,
如果核心线程池有空闲线程,则将任务直接分配给空闲线程执行,否则执行第二步
2)线程池判断工作队列(阻塞队列)是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3)线程池判断线程池的线程是否都处于工作状态(创建线程需要获得全局锁(同步处理))。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
线程池的使用
4.手工创建线程池(重点)
通过创建ThreadPoolExecutor来创建一个线程池:
public ThreadPoolExecutor(int corePoolSize,(核心线程的大小)
int maximumPoolSize,(线程池最大的线程数量)
long keepAliveTime,(线程保存活动时间)
TimeUnit unit,( keepAliveTime参数的时间单位)
BlockingQueue<Runnable> workQueue,(工作队列)
RejectedExecutionHandler handler)(饱和策略)
- corePoolSize,(核心线程池的大小):当提交任务到线程池时,线程池会创建一个新的线程来执行任务,即核心线程池中有其他空闲线程也会创建新线程,一直到线程数达到了核心池的大小为止
调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程
- workQueue,(工作队列):用于保存等待执行任务的阻塞队列。可以选择以下几个阻塞队列
- ·ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序
- ·LinkedBlockingQueue:一个基于链表结构的无界阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool(内置线程池,固定大小线程池)使用了这个队列
- ·SynchronousQueue:一个不存储元素的阻塞队列(无界队列)每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 Linked-BlockingQueue ,静态工厂方法Executors.newCachedThreadPool(内定的缓冲线程池)使用了这个队列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列
- maximumPoolSize,(线程池最大的线程数量):线程池允许创建的最大线程数。如果队列满并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。否则,调用饱和策略,处理值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。
- keepAliveTime,(线程保存活动时间):线程池的工作线程空闲后,保持存活的时间。如果任务很多,并且每个任务执行的时间比较短,可以调大此参数来提高线程的利用率。
- TimeUnit( keepAliveTime参数的时间单位)
- RejectedExecutionHandler handler)(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,JDK1.5 中 Java 线 程 池 框 架 提 供 了 以 下 4 种 策 略 。
- AbortPolicy : 表示无法处理新任务时抛出异常,直 接 抛 出 异 常 。 ( 默 认 采 用 此 策 略 )
- CallerRunsPolicy:使用调用者所在线程来运行任务
- DiscardOldestPolicy:丢弃阻塞队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉,也不报异常
FutuerTask类执行任务只执行一次,并且会阻塞其他线程。
Future.get()会阻塞其他线程,一直等待当前Callable线程执行完毕拿到返回值为止
JDK7新增fork-join框架将大任务拆分子任务执行,最终结果由各个子任务汇总得来
- JDK内置四大线程池
普通调度池:
- 缓存线程池:Executors. newCachedThreadPool():根据需要创建新线程
当提交任务速度快于执行任务速度,缓存线程池会不断创建新的线程
适用于大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器
- 创建固定大小的线程池: Executors. newFixedThreadPool(int nThreads)
适用于为了满足资源管理的需求而需要限制当前线程数量的应用场合,适用于负载比较重的服务器。
- 单线程池: Executors.newSingleThreadPool()
适用于需要保证顺序的执行各个任务,并且在任意时间点,不会有多个线程活动的场景
定时调度池
- 定时调度池ScheduledThreadPool(int nThread)
需要定时执行任务的应用场景
研究:
JDK1.7 Fork--Join框架关于多线程部分,将大任务拆分为子任务,然后把多个子任务的结果汇总
线程池中的线程可以循环执行,空闲之后再次被调度(线程被封装为worker类,再进行执行),研究一下Worker的底层实现原理