- 方式1:继承Thread
执行方式:
- 方式2:实现Runnable接口
执行方式:
- 方式3:实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
执行方式:
*注:以上三种启动线程的方式,在以后的业务代码里我们都不使用,讲所有的多线程异步任务交给线程池执行
- 方式4:线程池
可以通过如下两种方式初始化线程池
使用Executors创建线程池:
使用ThreadPoolExecutor创建线程池
七大核心参数:
- corePoolSize - 保留在池中的线程数,即使它们是空闲的,除非设置allowCoreThreadTimeOut
- maximumPoolSize – 池中允许的最大线程数 控制资源
- keepAliveTime – 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。 若线程等待指定的时间(keepAliveTime)后仍为被分配线程则释放线程(maximumPoolSize - corePoolSize 即 线程池支持最大线程数 - 保留在线程池中的线程数 才是可以被释放的线程)
- unit – keepAliveTime参数的时间单位
- workQueue – 用于在执行任务之前保存任务的队列。此队列将仅保存由execute方法提交的Runnable任务。
- threadFactory – 执行器创建新线程时使用的工厂
- handler – 由于达到线程边界和队列容量而阻塞执行时使用的处理程序 (如果队列满了,按照我们指定的拒绝策略执行任务)
使用Excutors创建线程池
1.newCachedThreadPool
创建一个可缓存线程池如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
2.newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程将会在队列中等待
3.newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务
面试问题示例:
一个线程池 core 7 ;max 20 ;queue 50,100个并发进来怎么分配?
答: 7个线程会立即得到执行,50个会进入队列,在打开13个进行执行,剩下的30个使用拒绝策略
拒绝策略:
在分析 JDK 自带的线程池拒绝策略前,先看下 JDK 定义的 拒绝策略接口,如下:
CallerRunsPolicy(调用者运行策略)
功能:
当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。
使用场景:
一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。
AbortPolicy(中止策略)
功能:
当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程
使用场景:
这个就没有特殊的场景了,但是一点要正确处理抛出的异常。ThreadPoolExecutor 中默认的策略就是AbortPolicy,ExecutorService 接口的系列 ThreadPoolExecutor 因为都没有显示的设置拒绝策略,所以默认的都是这个。但是请注意,ExecutorService 中的线程池实例队列都是无界的,也就是说把内存撑爆了都不会触发拒绝策略。当自己自定义线程池实例时,使用这个策略一定要处理好触发策略时抛的异常,因为他会打断当前的执行流程。
DiscardPolicy(丢弃策略)
功能:
直接静悄悄的丢弃这个任务,不触发任何动作
使用场景:
如果你提交的任务无关紧要,你就可以使用它 。因为它就是个空实现,会悄无声息的吞噬你的的任务。所以这个策略基本上不用了
DiscardOldestPolicy(弃老策略)
功能:
如果线程池未关闭,就弹出队列头部的元素,然后尝试执行
使用场景:
这个策略还是会丢弃任务,丢弃时也是毫无声息,但是特点是丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。基于这个特性,我能想到的场景就是,发布消息,和修改消息,当消息发布出去后,还未执行,此时更新的消息又来了,这个时候未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。因为队列中还有可能存在消息版本更低的消息会排队执行,所以在真正处理消息的时候一定要做好消息的版本比较。
*区别:
方式1、2不能得到返回值,方式3可以得到返回值
方式1、2、3都不能控制资源
方式4可以控制资源,性能稳定
通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。
execute和submit的区别:
开发中为什么使用线程池
- 降低资源的消耗
- 通过重复利用已将创建好的线程降低线程的创建和销毁带来的损耗
- 提高响应速度
- 因为线程池中的线程数没有超过线程池最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行;
- 提高线程的可管理性
- 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配