美文网首页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

    相关文章

      网友评论

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

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