线程池的一些理解-1

49 阅读5分钟

用线程池及几个重要的类

  • 降低资源的消耗,减少创建和销毁线程的次数,因为创建和销毁都比较耗时,让线程可以重复利用
  • 提高响应的速度及效率 当任务到达试,可不用等待创建线程直接执行
  • 提高线程的管理
  1. Executor 底层接口,只有一个execute(Runnable command)方法
  2. ExecutorService 真正的线程池接口
  3. ScheduledExecutorService 解决那些需要任务重复执行的问题
  4. ThreadPoolExecutor ExecutorService的默认实现
  5. ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现
  6. Executors 创建线程工厂类

ThreadPoolExecutor线程池的构造方法

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

corePoolSize       --  核心池大小,创建的时候不会启动。需要启动可执行prestartAllCoreThreads
maximumPoolSize    --  允许的最大线程数。当核心线程数满且阻塞队列也满才会判断当前线程数是否小于最大线程数,决定是否需要创建新的线程
keepAliveTime      --  超出核心线程数量的线程,maximumPoolSize-corePoolSize空余线程存活的最长时间
unit               --  keepAliveTime参数的时间单位
workQueue          --  超出核心线程数时保存任务所用的队列,有3种:无界队列、有界队列、同步移交
threadFactory      --  执行程序创建新线程时使用的工厂
handler            --  队列满了且线程达到最大线程时采用的策略,有4种:终止、抛弃、抛弃旧的、调用者运行

Executors底层都是调用ThreadPoolExecutor

队列的类型

1. 无界队列

常用的是LinkedBlockingQueue,队列最大长度是Integet.MAX_VALUE,如果有耗时长导致队列堆积就会导致OOM

Executors.newFixedThreadPool和Executors.newSingleThreadExecutor用的就是无界队列LinkedBlockingQueue

2. 有界队列

常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。

使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

实际开发中一般应该使用有界队列。

3. 移交队列

如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

线程池的流程:

  1. 当线程池小于corePoolSize,新提交的任务将创建一个新的线程来执行(即使有空闲线程)。
  2. 当线程池达到corePoolSize,新的任务会提交到workQueue队列,等待线程池空闲来执行
  3. 当workQueue也满了,maximumPoolSize>corePoolSize,新提交的任务将创建一个新的线程来执行
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

image.png

拒绝的策略

1. AbortPolicy中止策略

该策略是默认饱和策略。使用该策略时在饱和时会抛出RejectedExecutionException(继承自RuntimeException),调用者可捕获该异常自行处理。

2. DiscardPolicy抛弃策略

不做任务处理直接丢弃

3. DiscardOldestPolicy抛弃旧任务策略

先将阻塞队列中的头元素出队抛弃,再尝试提交任务。如果此时阻塞队列使用PriorityBlockingQueue优先级队列,将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用。

4. CallerRunsPolicy调用者运行

既不抛弃任务也不抛出异常,直接运行任务的run方法,换言之将任务回退给调用者来直接运行。使用该策略时线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成。

Executors创建线程池的四种方式(实际不推荐)

1. newFixedThreadPool

创建固定大小的线程池,如果线程池小于固定数量,每次提交就会创建一个线程,直至达到设置的固定数量。固定数量后线程池就不会变化了,再提交新任务就会放到队列中等待。

public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

问题:无限大的队列,会导致OOM

2. newSingleThreadExecutor

单线程线程池,一个线程负责所有的任务,相当于串行。此线程池可保证按提交顺序执行

public static ExecutorService newSingleThreadExecutor() 
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

问题:无限大的队列,会导致OOM

3. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程

public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

问题: 无限大的线程池,maximumPoolSize为Integer.MAX_VALUE,会导致OOM

4. newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

问题: 无限大的线程池,maximumPoolSize为Integer.MAX_VALUE,会导致OOM