线程复用:线程池笔记

    xiaoxiao2021-03-25  68

    线程复用:线程池

    线程池总概

    什么是线程池?

    接触过JDBC的人,一定听说过数据库连接池(比如,c3p0、Druid等)。其实在我的理解中,两者是差不多的。不过线程池中放的是线程而已。 线程是一种轻量级工具,但其创建与关闭都需要花费一定的时间。而且大量的线程会抢占内存资源。盲目的大量资源会对系统造成极大的压力。 线程池,中有一定数量的活跃线程。创建线程变成了从线程池中获得空闲线程;关闭线程变成了向线程池归还线程。

    JDK对于线程池的支持

    Java通过Executors提供五种线程池,分别为:

    newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。newSingleThreadScheduledExcutor创建单线程化的线程池,支持定时及周期性任务执行。

    线程池的使用

    首先是简单使用,这个没有什么特殊之处。 只需记得newFixedThreadPool创建的是定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待。 而newCachedThreadPool创建的线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

    定时任务

    newScheduledThreadPool支持定时及周期性任务执行,查看了其源码,主要有以下三种方法:

    schedule():在给定时间,对任务进行调度;scheduleAtFixedRate() 和 scheduleWithFixedDelay():对任务进行周期性调度,但两者有所区别。
    scheduleAtFixedRate() 和 scheduleWithFixedDelay() 的区别
    两种调度的区别: FixedRate 方式:以上一个任务开始执行时间为起点,在之后的延迟时间后,调用下一次任务。FixedDelay 方式:上一个任务结束后,再经过延迟时间进行任务调度。若任务执行时间超过调度时间, FixedRate 方式:若调度时间过短,那么任务会在上一个任务结束后立刻调用(不会出现任务堆叠的现场)。FixedDelay 方式:会严格按照任务间隔时间 = 调度时间 + 任务执行时间。

    如果任务遇到异常,那么后续的所有子任务都会停止调度。因此必须保证,异常被及时处理,为周期性任务的稳定调度提供条件。

    关于线程池的记录

    拒绝策略

    创建线程池的核心类 ThreadPoolExecutor 有一个参数指定了拒绝策略。拒绝策略,是系统超负荷运行时的补救措施,通常是由于压力太大而引起的,也就是线程池中的线程已经用完了且等待队列已经排满了。 JDK 提供了四种拒绝策略:

    AbortPolicy 策略:直接抛出异常,阻止系统正常工作。CallerRnsPolicy 策略:只要线程池未关闭,将直接在调用者线程中运行被丢弃的任务。这种做法不会真的丢弃任务,但是任务提交线程的性能将急剧下降。DiscardOldestPolicy 策略:丢弃最老的一个请求,也就是即将被执行的任务(处于等待队列的队头),并尝试再次提交当前任务。DiscardPolicy 策略:直接丢掉无法处理的任务。自定义策略:自己扩展 RejectedExecutionHandler 接口。

    线程扩展

    ThreadPoolExecutor是一个可扩展的线程池,有beforeExecute()、afterExecute()和terminated()能够对线程进行控制。

    protected void beforeExecute(Thread t, Runnable r) { } protected void afterExecute(Runnable r, Throwable t) { } protected void terminated() { }

    这是三个protected的空方法,摆明了可以让子类扩展。 * 在执行任务的线程中将调用beforeExecute和afterExecute等方法,在这些方法中还可以添加日志、计时、监视或者统计信息收集的功能。 * 无论是正常运行,还是抛出异常,都会调用afterExecute。但是,如果抛出Eorror,将不会调用该方法;或者beforeExecute抛出一个RuntimeException,则任务将不被执行,即该方法也不会被调用。 * 关于terminated,在线程池完成关闭时(就是在所有任务已经完成且所有工作者线程已经关闭),用来释放Executor在生命周期里分配的各种资源,此外还能执行信息通知、日志记录等功能。

    补充

    使用线程池被”吃”掉了异常堆栈信息 在使用线程池提交线程时,可能会发生异常堆栈信息被”吃”掉的现象,而解决方法:

    放弃submit(),改用execute()。

    获取submit()方法返回类的get()方法。

    Future future = pools.submit(new Thread()); future.get();

    扩展 ThreadPoolExecutor 线程池,让其在调度任务前,先保存提交任务线程的堆栈消息(就是重写线程池线程的调用方法)。

    自定义线程:ThreadFactory 这个接口只有一个方法 newThread(Runnable r),主要是由线程池调用新建线程。

    优化线程池线程数量 在《Java Concurrency in Practice》书中给了一个估算线程池大小的经验公式(同时,在Java中,可以通过Runtime.getRuntime().availableProcessors获取可用的CPU数量。)。

    Ncpu = CPU数量 Ucpu = 目标CPU的使用率,0 <= Ucpu <= 1 W/C = 等待时间与计算时间的比率 所以,最优的线程池大小为: Nthreads = Ncpu * Ucpu * ( 1 + W/C )

    参考资料

    《实战Java高并发程序设计》(葛一鸣 郭超 著)
    转载请注明原文地址: https://ju.6miu.com/read-37661.html

    最新回复(0)