线程池
- 业务场景分为IO密集型和CPU密集型
- IO密集型通常 存在线程等待阻塞 线程池最大线程数配置为2×CPU核数
- CPU密集型通常 不存在等待,每个线程过来都会运行的 线程数最大线程数配置为CPU核数
常用线程池的接口和类
- Executor:线程池的根接口 execute()
- ExecutorService:线程池接口,可以通过submit(Runnable task) 提交任务代码
- Executors:工厂类,通过该类来获得一个线程池
- 创建固定线程个数线程池
- 创建缓存线程池,由任务的多少决定
- 创建单线程池
- 创建调度线程池 调度:周期,定时执行 - 通过newFixedThreadPool(int nTheard) 获取固定数量的线程池。参数:指定线程池中线程的数量
- 通过newCachedThreadPool()获得动态数量的线程池(缓存线程池),如果不够则创建新的,没有上限
- 通过newSingleThreadExecutor()创建单线程线程池
- 通过newScheduledThreadPool(corePoolSize)创建调度线程池
executorService的shutdown方法和shutdownNow方法的区别
shutdown方法会等待所有线程执行完毕之前的任务才会关闭线程池,不接收新的任务
shutdownNow方法不会等待执行完毕,会直接关闭
线程池执行原理
执行顺序
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
- 1若线程数小于最大线程数,创建线程
- 2若线程数等于最大线程数,抛出异常,拒绝任务
为什么等待队列要使用阻塞队列
因为使用阻塞队列,线程就可以等待核心线程执行,执行完毕后出列,占用cpu资源执行。如果不使用阻塞队列,在cpu
全部占用的时候出列,直接pass了
核心线程和最大线程的理解:
核心线程可以理解为实际运行的线程
最大线程可以理解为最多可以创建的线程
所以最大线程数一定要大于等于核心线程数
线程池参数的设置
- tasks 程序每秒需要处理的最大任务数量
- tasktime 单线程处理一个任务所需要的时间
- responsetime 系统运行任务最大的相应时间
- ==corePoolSize : tasks/(1/tasktime) ==
- queueCapacity(任务队列的长度) : (corePoolSize/tasktime)*responsetime
- maxPoolSize : CPU密集型 cpu核数 IO密集型 cpu核数×2
- ==keepAliveTime : ==
Callable和Future
- 它的核心思想是异步调用。对于Future模式来说,它无法立即返回你需要的数据,但是它会返回一个契约,将来你可以凭借这个契约去获取你需要的信息。
- 带有返回值(声明的泛型类型),并且可以抛出异常(和Runnable的区别)
- future内部是使用wait()和notify()方法实现的
Future模式的主要参与者:
Main:系统启动,调用 Client发出请求
Client:返回 Data对象,立即返回FutureData,并开启 ClientThread线程装配 RealData
Data:返回数据接口
FutureData:Future数据,构造很快,但是是一个虚拟的数据,需要装配 RealData
RealData:真实数据,其构造是比较慢的
缺点
回调无法放到与任务不同的线程中执行,主线程要等各个异步执行线程返回的结果来做下一步操作,就必须阻塞在future.get()方法等待结果返回,这时其实又是同步了
到Java8时引入了一个新的实现类CompletableFuture
使用步骤:
- new Callable接口并重写call方法
- 把Callable对象转成可执行任务
- futureTask 对象将Callable对象转成可执行任务 FutureTask task=new FutureTask<>(callable); - 创建线程 Thread thread=new Thread(task);
- 启动线程 thread.start();
- 获取结果 task.get();
也可以通过线程池的submit方法执行
转载:详细理解连接