【总结】线程池

该文章为面试精华版,如果是初学者,建议学习专栏:Java并发专栏

Java并发需要结合JVM以及操作系统的相关知识,建议先学习这两个部分:JVM专栏操作系统专栏

一、ThreadPoolExecutor参数含义

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize:线程池始终线程数,即使有些是空闲的。设置allowCoreThreadTimeOut参数为true,才会进行回收。
  • maximumPoolSize:线程池最大线程数,表示在线程池中最多能创建多少个线程。如果当线程池中的数量到达这个数字时,新来的任务会抛出异常。
  • keepAliveTime:表示线程没有任务执行时最多能保持多少时间会回收,然后线程池的数目维持在corePoolSize。
  • unit:参数keepAliveTime的时间单位
  • workQueue:一个阻塞队列,所有的任务都会先放在这里,务;如果对当前对线程的需求超过了corePoolSize大小,会用来存储等待执行的任。
  • threadFactory:线程工厂,主要用来创建线程,比如指定线程的名字。
  • handler:如果线程池已满,新的任务处理方式。

注意一点:<mark>初始化线程池时,线程数为0</mark>

1. 什么时候创建新的线程?

  • 线程初始化时线程数为0
  • 当前线程数小于corePoolSize时,提交任务会直接创建新的线程
  • 当前线程数大于等于为corePoolSize时,提交任务会放到阻塞队列中,当阻塞队列满时会创建线程

2. 如何关闭线程池?

shutdown(高安全低响应)

  • 本质上执行的是interrupt方法

  • 阻止新来的任务提交,会将线程池的状态改成SHUTDOWN,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则执行拒绝策略,从而达到阻止新任务提交的目的。

  • 对已经提交了的任务不会产生任何影响,当已经提交的任务执行完后,它会将那些闲置的线程进行中断,这个过程是异步的,也就是说只会打断空闲线程,如果当前还有任务队列还有任务未执行,线程将继续把任务执行完。

shutdownNow(低安全高响应)

  • 阻止新来的任务提交,将线程池的状态改成STOP,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的.

  • 会中断空闲进程,同时也会中断当前正在运行的线程,即workers中的线程。

  • 另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。

  • 如遇已经激活的任务,并且处于阻塞状态时,shutdownNow()会执行1次中断阻塞的操作,此时对应的线程报InterruptedException,如果后续还要等待某个资源,则按正常逻辑等待某个资源的到达。例如,一个线程正在sleep状态中,此时执行shutdownNow(),它向该线程发起interrupt()请求,而sleep()方法遇到有interrupt()请求时,会抛出InterruptedException(),并继续往下执行。在这里要提醒注意的是,在激活的任务中,如果有多个sleep(),该方法只会中断第一个sleep(),而后面的仍然按照正常的执行逻辑进行。

高安全低响应体现在shutdown等待任务执行完成再关闭,可以保证任务一定被执行,但是关闭线程池需要等待较长的时间

低安全高响应体现在shutdownNow会关闭正在执行任务的线程,任务可能并没有执行完毕,也不会回退到任务队列中,将会消失,但是关闭线程池不需要等待较长的时间

如果一个任务执行时间很长,导致线程池长时间关闭不了,可以在创建线程的时候将其设置为守护线程,此时被守护的线程是主线程,只要主线程执行完成,线程池就会强制关闭,可以配合awaitTermination使用

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,
        30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), r -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
},new ThreadPoolExecutor.AbortPolicy());

public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor threadPoolExecutor = bulidThreadPoolExecutor();
    threadPoolExecutor.shutdown();
    threadPoolExecutor.awaitTermination(5,TimeUnit.SECONDS);
    System.out.println("强制关闭");
}

二、拒绝策略

线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也
塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK 内置的拒绝策略如下

  1. AbortPolicy : 直接抛出异常,阻止系统正常运行。
  2. CallerRunsPolicy : 导致该方法直接在调用者的主线程中执行,而不是在线程池中执行。从而导致主线程在该任务执行结束之前不能提交任何任务。从而有效的阻止了任务的提交。
  3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,会直接出队,并尝试再
    次提交当前任务。
  4. DiscardPolicy :默认情况下它将丢弃被拒绝的任务

以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际
需要,完全可以自己扩展 RejectedExecutionHandler 接口。

三、线程池的状态

  • RUNNING:能够接收新任务,以及对已添加的任务进行处理。
  • SHUTDOWN:不接收新任务,但能处理已添加的任务。
  • STOP:程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
  • TIDYING:当所有的任务已终止
  • TERMINATED:线程池彻底终止,就变成TERMINATED状态。

四、线程池分类

Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而
只是一个执行线程的工具。真正的线程池接口是 ExecutorService

摘自阿里巴巴开发手册:

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

newCachedThreadPool

  • 线程池的参数:coreSize线程数0,最大线程数无限制,线程的允许空闲时间是60s,阻塞队列是SynchronousQueue
  • 这个线程池适用情况是短任务情况。
  • 采用SynchronousQueue,每当提交一个任务,都会超过阻塞队列的长度,导致创建线程,所以说:每当提交一个任务,都会创建一个线程,可能造成OOM。
  • 当线程空闲1分钟就会,销毁,所以该线程池会频繁的创建和销毁线程,最终会线程池会自己销毁

newFixedThreadPool

  • coreSize和最大线程数都是用户输入的,阻塞队列用的LinkedBlockingQueue,线程的允许空闲时间是0s
  • 该线程池的线程数是用户自定义的,不会增加,不会减少,线程池不会自己销毁,但是并不是刚开始就会直接创建coreSize的线程
  • 阻塞队列是无限大的,不会执行拒绝策略。
  • 可能会堆集无限的请求,导致OOM

newSingleThreadExecutor

  • 相当于线程数为1的newFixedThreadPool,缺点和newFixedThreadPool一样

和一个线程的区别?

newSingleThreadExecutor Thread
任务执行完成后,不会自动销毁,可以复用 任务执行完成后,会自动销毁
可以将任务存储在阻塞队列中,逐个执行 无法存储任务,只能执行一个任务

newScheduledThreadPool

  • 可以创建定时的任务,以固定周期执行或者固定的延迟时间执行
  • 如果任务时间超过了定时时长,就无法按照预定的时间执行
  • 而Linuxcrontab定时处理器为了确保时间的正确性,会重新启一个线程

newWorkStealingPool

  • 采用的ForkJoin框架,可以将任务进行分割,同时线程之间会互相帮助
  • 阻塞队列采用的LinkedBlockingDeque,可以进行任务窃取

五、使用线程池的好处

  • 线程重用:线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
  • 控制线程池的并发数:线程不是并发的越多,性能越高,反而在线程并发太多时,线程的切换会消耗系统大量的资源,可以通过的设置线程池最大并发线程数目,维持系统高性能
  • 线程池可以对线程进行管理:虽然线程提供了线程组操控线程,但是线程池拥有更多管理线程的API
  • 可以储存需要执行的任务:当任务提交过多时,可以将任务储存起来,等待线程处理
全部评论

相关推荐

斑驳不同:还为啥暴躁 假的不骂你骂谁啊
点赞 评论 收藏
分享
10-07 23:57
已编辑
电子科技大学 Java
八街九陌:博士?客户端?开发?啊?
点赞 评论 收藏
分享
评论
1
收藏
分享
牛客网
牛客企业服务