线程池原理

  • 为什么需要线程池?线程池的作用?
  • 几个比较重要的类?
  • new Thread的弊端?
  • 线程池的核心参数?
  • 四种线程池以及特点,哪些会发生OOM?
  • 线程池的四种拒绝策略?
  • 阿里规范怎么建议创建线程池?为什么?
  • 线程池的图示?
  • 初始化线程池时线程数怎么选择?
  • 线程池都有哪几种工作队列?

线程池的作用

创建线程和销毁线程的资源损耗。因为线程其实也是一个对象,创建一个对象,需要经过类加载过程,销毁一个对象,需要走GC垃圾回收流程,都是需要资源开销的。

提高响应速度。

重复利用。

比较重要的类

  • Executor,任务接口
  • ExecutorService,线程池接口,带生命周期管理
  • ThreadPoolExecutor,线程池默认实现
  • ScheduledThreadPoolExecutor,带任务计划的线程池

new Thread的弊端

1.每次new Thread新建对象性能差。 2.线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。 3.缺乏更多功能,如定时执行、定期执行、线程中断。 相比new Thread,Java提供的四种线程池的好处在于: 1.重用存在的线程,减少对象创建、消亡的开销,性能佳。 2.可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。 3.提供定时执行、定期执行、单线程、并发数控制等功能。

线程池的核心参数

  • corePoolSize,保持在pool内的线程数,尽管是空闲的,除非设置了allowCoreThreadTimeOut。
  • maxPoolSize,池内最大的线程数。
  • keepAliveTime,如果max>=core,等待任务的最大时间。终止前多余的空闲线程等待新任务的最长时间
  • unit,时间的单位
  • workQueue,存储task。
  • threadFactory,线程工厂。默认实现:DefaultThreadFactory。用于创建线程
  • handler,拒绝策略。

线程池的图示

当产品提个需求,正式员工(核心线程)先接需求(执行任务) 如果正式员工都有需求在做,即核心线程数已满),产品就把需求先放需求池(阻塞队列)。 如果需求池(阻塞队列)也满了,但是这时候产品继续提需求,怎么办呢?那就请外包(非核心线程)来做。 如果所有员工(最大线程数也满了)都有需求在做了,那就执行拒绝策略。 如果外包员工把需求做完了,它经过一段(keepAliveTime)空闲时间,就离开公司了。

四种线程池以及特点,哪些会发生OOM

  • newSingleThreadExecutor。会发生OOM。 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
    public static ExecutorService newSingleThreadExecutor() {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()));
      }
    
  • newFixedThreadPool。会发生OOM。创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
    public static ExecutorService newFixedThreadPool(int nThreads) {
          return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>());
      }
    
  • newCachedThreadPool。创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

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

public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

线程池的四种拒绝策略

当请求任务不断的过来,而系统此时又处理不过来的时候,我们需要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
  • CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
  • DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
  • DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。

线程池都有哪几种工作队列

1、ArrayBlockingQueue

是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

2、LinkedBlockingQueue

一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列

3、SynchronousQueue

一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

4、PriorityBlockingQueue

一个具有优先级的无限阻塞队列。

初始化线程池时线程数的选择

如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。

如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。

Ref: https://blog.csdn.net/qq_39662660/article/details/95983382

results matching ""

    No results matching ""