美文网首页IT阔论
关于线程池的思考

关于线程池的思考

作者: 七佰 | 来源:发表于2018-05-20 21:15 被阅读2次

如果频繁使用线程的情况下,每次都使用New thread,有以下几点不好的地方。

  1. 性能低下。每次都需要新建线程,浪费资源。
  2. 极端情况下,一直新建线程,导致占用过多资源。
  3. 缺乏更多定时等功能。

这时,我们引入线程池的概念。可以解决上述问题。
JDK8下线程池支持四种类似。

  • CacheThreadPool
  • FixedThreadPool
  • ScheduedThreadPool
  • SingleThreadExcutor

CacheThreadPool

CacheThreadPool是通过util.concurrent.Executors创建的ThreadPoolExecutor实例,这个实例根据需要,在线程可用时,重用线程池中的线程。适用于大量的短生命周期的异步任务,可以显著提高性能。当调用execute时,重用之前已经构造的可用线程,如果不存在可用的线程,那么就会新建一个线程加入到线程池中。如果线程超过60秒未使用,那么会从缓存中移除。因此,在长时间不使用的情况下,不消耗任何资源。

FixedThreadPool

FixedThreadPool是通过util.concurrent.Executors创建的ThreadPoolExecutor实例,这个实例会复用固定数量的线程。任意时间点,至多有N个(可设置)线程可用。如果有多的任务过来,会自动在队列中等待,一直到有可用线程可以使用。所有的线程都会在线程池中,直到显式的执行 ExecutorService.shutdown()关闭.

ScheduedThreadPool

SingleThreadExcutor

SingleThreadExcutor是通过util.concurrent.Executors创建的ThreadPoolExecutor实例,这个线程只会用单个工作进程执行无边界的队列任务。他与new FixedThreadPool(1)的区别在于,当进程出现错误的时候,SingleThreadExcutor可以有新的进程继续执行,而FixedThreadPool(1)不行。

最佳实践

FixedThreadPoolCachedThreadPool两者对高负载的应用都不是特别友好。
CachedThreadPool 要比 FixedThreadPool 危险很多。
如果应用要求高负载、低延迟,最好不要选择以上两种线程池:

  • 任务队列的无边界:会导致内存溢出以及高延迟
  • 长时间运行会导致 CachedThreadPool 在线程创建上失控

因为两者都不是特别友好,所以推荐使用 ThreadPoolExecutor ,它提供了很多参数可以进行细粒度的控制。

  • 将任务队列设置成有边界的队列
  • 使用合适的 RejectionHandler - 自定义的 RejectionHandler 或 JDK 提供的默认 handler 。
  • 如果在任务完成前后需要执行某些操作,可以重载
    beforeExecute(Thread, Runnable)
    afterExecute(Runnable, Throwable)
    
  • 重载 ThreadFactory ,如果有线程定制化的需求
  • 在运行时动态控制线程池的大小(Dynamic Thread Pool

学习ThreadPoolExecutor

先展示一下 ThreadPoolExecutor的继承关系。ExecutorService是Executor的子接口,增加了一些常用的对线程的控制方法,之后使用线程池主要也是使用这些方法。AbstractExecutorService是一个抽象类。ThreadPoolExecutor就是实现了这个类。

ThreadPoolExecutor.png

四大构造函数

ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) 
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

参数说明

  • corePoolSize
    核心线程数:默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
  • maximumPoolSize
    线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
  • keepAliveTime
    非核心线程的闲置超时时间,超过这个时间就会被回收。
  • unit
    指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
  • workQueue
    线程池中的任务队列.常用的有三种队列,
    SynchronousQueue:同步队列
    LinkedBlockingDeque:
    ArrayBlockingQueue:
  • threadFactory
    线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法
public interface ThreadFactory {
  Thread newThread(Runnable r);
}

通过线程工厂可以对线程的一些属性进行定制。默认的工厂:

static class DefaultThreadFactory implements ThreadFactory {
  private static final AtomicInteger poolNumber = new AtomicInteger(1);
  private final ThreadGroup group;
  private final AtomicInteger threadNumber = new AtomicInteger(1);
  private final String namePrefix;
  DefaultThreadFactory() {
      SecurityManager var1 = System.getSecurityManager();
      this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup();
      this.namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
  }
  public Thread newThread(Runnable var1) {
      Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L);
      if(var2.isDaemon()) {
          var2.setDaemon(false);
      }
      if(var2.getPriority() != 5) {
          var2.setPriority(5);
      }
      return var2;
  }
}
  • RejectedExecutionHandler
    RejectedExecutionHandler也是一个接口,只有一个方法
public interface RejectedExecutionHandler {
  void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}

当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。

场景实践:
当我需要在任务结束的时候,继续执行操作的话,那么我应该怎么办呢?

  • 方法一:使用while(1)方式,不停的轮询任务状态,可以实现功能,但肯定不是我们要的结果,因为这样的主线程并不是挂起,只是不断循环等待的,一样需要耗费资源,如果线程过多会非常浪费资源。
  • 方式二:使用future.get()方法,
  • 方式三:在执行任务的程序的run()方法的最后用wait/notify,唤醒主线程,真正实现了异步,主线程并不多耗费资源。缺点是不够灵活,例如:更改了执行的任务,就必须在新任务的run()方法后加入唤醒操作,不能做到与任务无关。
    -方式四:用wait/notify和挂钩程序及反射机制的应用,实现了线程池间的通信控制。

参考资料:
https://www.cnblogs.com/zhangyasong/p/6929749.html
https://www.cnblogs.com/richaaaard/p/6599184.html
http://wawlian.iteye.com/blog/1315256
https://blog.csdn.net/qq_25806863/article/details/71126867

相关文章

  • java线程池源码解析

    主要介绍线程池相关知识,关于线程池,首先我们思考下为什么要用线程池。如果单纯的使用线程,线程的创建和销毁都是自己来...

  • 关于线程池的思考

    如果频繁使用线程的情况下,每次都使用New thread,有以下几点不好的地方。 性能低下。每次都需要新建线程,浪...

  • 线程通讯详解

    关于子线程能否更新UI的思考线程通讯详解线程池-多线程的高效使用姿势 上文我们说到了关于子线程中能否更新UI的问题...

  • 关于子线程能否更新UI的思考

    关于子线程能否更新UI的思考 线程通讯详解 线程池-多线程的高效使用姿势 当大家被问到这个问题的时候可能就...

  • 线程学习_03线程池

    线程池结构图: 一、关于Executor: 1.1 线程池的顶层接口Executor: 1.2 线程池的第二层接口...

  • 线程池-多线程的高效使用姿势

    关于子线程能否更新UI的思考线程通讯详解线程池-多线程的高效使用姿势 在开始这个话题之前我觉得还是得先谈一谈什么是...

  • 关于线程池常驻线程的实现的思考

    常驻线程Demo[https://github.com/fangjinfeng/MySampleCode] 今天跟...

  • 简单理解java线程池ThreadPoolExecutor

    这里关于如何使用线程池,暂且不说,仅探究其原理。 线程池的构造函数,仅仅是初始化线程池,不会创建任何线程,线程池里...

  • JAVA并发梳理(五)线程池

    关于线程池的实现,各自的特点等稍后再补充。现在先总结下如何合理地设置线程池的大小。 线程池中线程的数目是跟线程池所...

  • java线程状态和线程池

    本节总结线程相关知识:线程状态和线程池。1.线程的五个状态 关于如何终止线程,以下仅供参考: 2.线程池

网友评论

    本文标题:关于线程池的思考

    本文链接:https://www.haomeiwen.com/subject/mkkcrftx.html