java线程池

线程池是一种池技术,就像连接池一样。线程池本身也是一个对象,这个对象可以管理自己池子中的众多线程,以使他们被高效率的反复利用

  • 为何要使用线程池

  1. 降低资源消耗,防止资源不足。
    线程也是对象,频繁的创建线程对象,就会频繁的去触发内存分配和内存占用,大量消耗性能,内存占用过多可能会导致内存溢出。
    频繁的创建和销毁线程对象即频繁的消耗cpu,影响服务器性能。
    因为有大量的对象产生,就会有大量的GC回收,大量的gc可能会导致gc抖动,卡顿等现象,也会影响服务器性能。
  2. 提高响应速度
    当需要被线程执行的任务到达时,因线程池中已有创建好的线程,所以可以直接用创建好的线程去执行任务,而无需先创建一个线程,然后在执行任务,节省了程序运行时间。
  3. 提高线程的可管理性
    可以控制线程的数量和并法数,也可以防止无限制的创建线程数量导致性能和内存溢出等问题。
  4. 提供更强大的功能,延时定时线程池
    比如,我们需要一个任务每一秒钟执行一次。
  • java线程池种类

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

不过在工作中,如果你使用Executors的方式去创建线程,可能会失业的,原因直接贴上阿里开发手册的说明。

image.png

  • 正确创建线程池的姿势

image.png

  • 线程池创建线程执行和拒绝策略如下:

当一个任务通过 execute(Runnable) 方法欲添加到线程池时,线程池采用的策略如下(即添加任务的策略):

如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。

如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于maximumPoolSize ,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。

  • 使用有界队列时,如果任务队列满了则执行拒绝策略(第二个用的多些)

  1. ThreadPoolExecutor.AbortPolicy 丢弃任务,并抛出 RejectedExecutionException 异常。备注:此种方法必须要捕获线程池添加任务代码,不然添加异常就不会走shutdown关闭线程池代码,会导致线程池一直处于未关闭状态。

  2. ThreadPoolExecutor.CallerRunsPolicy:该任务被线程池拒绝,由调用 execute方法的线程执行该任务。备注:其实就是主线程去执行被线程池拒绝的任务。

  3. ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列最前面的任务,然后重新尝试执行任务。备注:就是移除队列中第一个添加进去的任务,把后来的任务加在队列最后。

  4. ThreadPoolExecutor.DiscardPolicy,丢弃任务,不过也不抛出异常。备注:直接丢弃队列满了之后添加进来的任务。

  • 如何优雅的关闭线程池:

线程池有2种关闭方法:

  1. shutdown():线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。
  2. shutdownNow() :线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行,并能获取未被执行的任务。

关闭线程池的时候,线程池中的线程有4种状态:

  1. 处于空闲状态 — shutdown: 正常退出,shutdownNow: 正常退出
  2. 处于正在处理任务的状态 — shutdown: 任务处理完成正常退出,shutdownNow: 任务处理完成正常退出
  3. 处于正在从任务队列读取任务的状态— shutdown: 继续读取并任务执行,直到所有任务全部执行完毕退出,shutdownNow: 停止读取并退出
  4. 处于阻塞状态— shutdown: 线程继续阻塞,并等待阻塞结束继续执行任务,shutdownNow: 抛出InterruptedException异常

所以,我们该如何正确关闭线程池

  • 当调用shutdownNow()方法时,如果确定可能会有阻塞的任务存在,一定要捕获异常进行处理
  • 当调用shutdown()方法时,一定要确保任务里不会有永久阻塞等待的线程,否则线程池就关闭不了,不行的话可以等待一段时间后调用shutdownNow()方法

如果要在线程池任务执行完关闭之后才能执行其他的主逻辑,那么我们就必须要等到线程池任务全部执行结束,需要注意的是,不管是shutdown还是shutdownNow方法,其实都是发起线程关闭,但是线程池此时并不一定完全关闭了,因为可能有线程还在执行任务或者队列里还有任务等待执行,所以我们需要通过pool.awaitTermination(2, TimeUnit.SECONDS)方法去判断线程池是否真的完全关闭了

  • pool.awaitTermination(2, TimeUnit.SECONDS)
    第一个参数为时间,第二个参数为单位
    这是一个阻塞方法,返回true和false,线程池关闭为true,未关闭为false
    在指定时间内,如果线程池关闭了,此方法结束阻塞,返回true,继续执行之后的代码
    在指定时间内,如果线程池未关闭,会一直阻塞,直到指定时间到了返回false。
    注意:此方法为阻塞方法,如果任务没有结束,子线程会阻塞于此,主线程也会被阻塞等待,当需要线程池的任务全部结束才能执行主线程时,可以用此方法,输入一个时间比较长的参数
    所以,一般shutdown()或者shutdownNow()方法要配合awaitTermination()方法一起使用。

其实关键就在于是否有阻塞的任务,下面是一种参考处理方法

threadPool.shutdown(); // Disable new tasks from being submitted
        // 设定最大重试次数
        try {
            // 等待 60 s
            if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
                // 调用 shutdownNow 取消正在执行的任务
                threadPool.shutdownNow();
                // 再次等待 60 s,如果还未结束,可以再次尝试,或者直接放弃
                if (!threadPool.awaitTermination(60, TimeUnit.SECONDS))
                    System.err.println("线程池任务未正常执行结束");
            }
        } catch (InterruptedException ie) {
            // 重新调用 shutdownNow
            threadPool.shutdownNow();
        }

线程关闭的参考资料:
https://www.cnblogs.com/qingquanzi/p/9018627.html
https://cloud.tencent.com/developer/article/1523115
https://blog.csdn.net/chun_hua_xue_yue/article/details/96475075

你可能感兴趣的:(java线程池)