线程池
程序员文章站
2022-05-01 14:19:00
...
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
这就出现了线程池。
线程池
线程池:
当并发线程多了之后,每个线程的频繁创建于销毁会导致性能降低,不好管理,为了解决这样的问题,就出现了线程池,线程池就是管理线程,我们不需要关心的创建与销毁,大大增加了我们开发的效率和程序的性能。
作用:
1、 降低资源的消耗。降低线程创建和销毁的资源消耗。
2、 提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间。
3、 提高线程的可管理性。
线程池的创建:
ThreadPoolExecutor,jdk所有线程池实现的父类。
其中各个参数含义:
int corePoolSize:线程池中核心线程数小于corePoolSize(线程池大小),就会创建新线程去执行任务。
等于corePoolSize,这个任务就会保存到BlockingQueue。
如果调用prestartAllCoreThreads方法就会一次性的启动corePoolSize个数的线程。
int maximumPoolSize:允许的最大线程数,当BlockingQueue也满了,并且小于maximumPoolSize时候就会再次创建新的线程。
keepAliveTime:线程空闲下来后存活的时间,这个参数只在大于corePoolSize才有用。
TimeUnit unit:存活时间的单位值。
BlockingQueue<Runnable> workQueue:保存任务的阻塞队列。
ThreadFactory threadFactory;创建线程的工厂,给新建的线程赋予名字.
RejectedExecutionHandler handler(饱和策略,四种):
1,AbortPolicy :直接抛出异常,默认。
2,CallerRunsPolicy:用调用者所在的线程来执行任务,不用线程池执行了。
3,DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务。
4,DiscardPolicy :当前任务直接丢弃。
饱和策略:即是当线程数大于maximumPoolSize的时候,已经饱和的了,提供一个这四种处理的策略。
实现自己的饱和策略,需要实现RejectedExecutionHandler接口。
工作机制:
1)线程池判断核心线程池里的线程是否全都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断任务阻塞队列是否已经满。如果任务阻塞队列没有满,则将新提交的任务存储在这个任务阻塞队列里。如果任务阻塞队列满了,则进入下个流程。
3)线程池判断线程池中的工作的线程数是否小于最大线程池数。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
方法:
提交任务:
execute(Runnable command) :不需要返回
Future<T> submit(Callable<T> task) :需要返回
关闭线程池:
shutdown(),shutdownNow();
shutdownNow():设置线程池的状态,还会尝试停止正在执行和没有执行任务的线程。
shutdown():设置线程池的状态,只会中断所有没有执行任务的线程。
合理配置线程池:
根据任务的性质区分:
计算密集型(CPU密集型),IO密集型,混合型
计算密集型:加密,大数分解,正则…….,这种类型,在使用线程池的时候,设置线程数适当小一点,最大推荐:机器的Cpu核 心数+1。(注意:这是逻辑核心数)
1,为什么+1,?
为了防止页缺失(页缺失:数据未全部从磁盘读取到内存中,数据丢失了),(机器的Cpu核心=Runtime.getRuntime().availableProcessors();)
2,为什么不是大于CPU核心数+1?
因为如果线程数大于太多CPU核心数,就会出现不必要的上下文切换,造成性能消耗。
IO密集型:读取文件,数据库连接,网络通讯, 线程数适当大一点,机器的Cpu核心数*2,
混合型:尽量拆分,IO密集型>>计算密集型,拆分意义不大,IO密集型~计算密集型
注意:队列的选择上,应该尽量使用有界,*队列可能会导致内存溢出,发生OOM。
Executor框架:
1,一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor,是线程池的定级接口,ThreadPoolExecut or是它的实现类。
2,ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务 执行状况返回Future的方法。
3,可以通过Executors这个工厂类获取到一些其他的预定义线程池,这些线程池都是Executor的实现类。
预定义的线程池:
FixedThreadPool:
创建固定线程数量的,适用于负载较重的服务器,其中使用了*的阻塞队列(LinkedBliockingQueue)。
SingleThreadExecutor:
创建单个线程,可以让任务按顺序执行,不会有多个线程活动,使用了*队列。
CachedThreadPool:
会根据需要来创建新线程的,执行很多短期异步任务的程序,使用了SynchronousQueue。
WorkStealingPool(JDK7以后):
基于ForkJoinPool实现,是一个工作密取的线程池。
ScheduledThreadPoolExecutor:
需要定期执行周期任务,Timer不建议使用了。
newSingleThreadScheduledExecutor:
只包含一个线程,只需要单个线程执行周期任务,保证顺序的执行各个任务。
newScheduledThreadPool:
可以包含多个线程的,线程执行周期任务,适度控制后台线程数量的时候。
方法说明:
schedule:只执行一次,任务还可以延时执行
scheduleAtFixedRate:提交固定时间间隔的任务,间隔是上一次开始的时刻到下一次开始的时刻的间隔。
scheduleAtFixedRate任务超时:
假设:规定60s执行一次,第一个任务 时长 80s,第二个任务20s,第三个任务50s,则:
第一个任务第0秒开始,第80S结束;
第二个任务第80s开始,在第100秒结束;
第三个任务第120s秒开始,170秒结束;
第四个任务从180s开始;
scheduleWithFixedDelay:提交固定延时间隔执行的任务,间隔是上一次完成的时刻到下一次开始执行的时刻的间隔。
注意:当任务执行异常,如果直接抛出,会阻塞住当前任务,其他任务不会影响,所以,尽量用try,catch处理整个任务代码块,防止任务异常的时候阻塞。
CompletionService:
问题:当线程池中的任务是有返回值的时候,放入线程池的任务是有顺序的,所以当这些任务执行完成之后,从阻塞队列中拿执行的任务的结果的时候,也只能是按照放入的顺序,先进先出的规则拿去结果,这样可能会造成CPU的浪费,比如我只想拿第三个任务执行的结果,这时候我必须得等到前两个任务执行完才能拿到,同时,也不能让先完成的任务先去执行下一个任务,所以这样由此产生了CompletionService来解决。
CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。
上一篇: Spring Boot 异步任务
下一篇: Spring boot多线程异步查询