一线程池的好处
1 池化技术包括:线程池,数据库连接池,Http连接池,主要目的:减低获取资源的消耗,提高资源利用率
2 线程池的好处:1)降低资源损耗 2)提高响应速度(核心线程已经创建好) 3)提高线程的可管理性,分配,调优,监控
二 Executor框架
1 Executor框架是Java5之后引入的,和原生Thread相比,更易管理,效率更好,还提供了队列,工厂,以及拒绝策略。
以及避免了this逃逸问题(当构造函数还未返回实例是就被别的线程获取到了引用,this逃逸中的构造函数将this赋值给了外部实例对象)
2 Executor 框架结构(主要由三大部分组成)
2.1)执行任务需要实现的 Runnable 接口 或 Callable接口。Runnable 接口或 Callable 接口 实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。
2.2)ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。
2.3)异步计算的结果:Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。
当我们把 Runnable接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。
(调用 submit() 方法时会返回一个 FutureTask 对象)
三 (重要)ThreadPoolExecutor 类简单介绍
1 ThreadPoolExecutor 类分析
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
) {。。。}
ThreadPoolExecutor 3 个最重要的参数:.
corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
ThreadPoolExecutor其他常见参数:
keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁;
unit : keepAliveTime 参数的时间单位。
threadFactory :executor 创建新线程的时候会用到。
handler :饱和策略,不指定时默认:ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。保障每个任务都被执行
四 创建线程池
1 通过ThreadPoolExecutor构造函数实现(推荐)
2 通过 Executor 框架的工具类 Executors 来实现:FixedThreadPool,SingleThreadExecutor,CachedThreadPool
五 executor.execute(worker) 逻辑:
六 几个常见的对比
1 Runnable vs Callable
如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口,这样代码看起来会更加简洁。
2 execute() vs submit()
2.1 execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
2.2 submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,
通过这个 Future 对象可以判断任务是否执行成功 ,并且可以通过 Future 的 get()
方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
3 shutdown()VSshutdownNow()
shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。
shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
4 isTerminated() VS isShutdown()
isShutDown 当调用 shutdown() 方法后返回为 true。
isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
七 几个常见的线程池详解
1 FixedThreadPool 不推荐,因为FixedThreadPool 使用无界队列 LinkedBlockingQueue(队列的容量为 Integer.MAX_VALUE)
2 SingleThreadExecutor 只有一个线程的线程池
3 CachedThreadPool 是一个会根据需要创建新线程的线程池,CachedThreadPool 的corePoolSize 被设置为空(0),maximumPoolSize被设置为 Integer.MAX.VALUE
4 ScheduledThreadPoolExecutor 主要用来在给定的延迟后运行任务,或者定期执行任务,其他方案选择比如quartz或者 Spring Boot 中 Schedule Tasks
八 线程池大小确定
1 上下文切换:任务从保存到再加载就是一次上下文切换,上下文切换通常是计算密集型的,事实上,可能是操作系统中时间消耗最大的操作。
2 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行
如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率
3 如何确定线程池核心线程数量
CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1
I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理
4 如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。单凡涉及到网络读取,文件读取这类都是 IO 密集型