Java并发编程笔记6:线程池的使用

405 阅读5分钟

ThreadPoolExecutor

在 Executoors 中的 newChachedThreadPool, newFixedThreadPool, newScheduledThreadExecutor 的工厂方法返回的都是 ThreadPoolExecutor 对象。如果默认的执行策略不能满足需求,可以通过 ThreadPoolExecutor 的构造函数进行实例化,根据自己的需求定制。

)

ThreadPoolExecutor(int corePoolSize,//线程池基本大小
                   int maximumPoolSize, //线程池最大尺寸
                   long keepAliveTime, //线程空闲后的存活时间
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, //任务队列
                   ThreadFactory threadFactory, //线程工厂  
                   RejectedExecutionHandler handler) //饱和策略

线程创建和销毁

  • corePoolSize是线程池的基本大小,即在没有任务执行时线程池的大小。
    • 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务。
    • 可以调用prestartAllCoreThreads()或者prestartCoreThread()方法预创建线程,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。
    • 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
    • 在工作队列满了的情况下才会创建超出这个数量的线程。
  • maximumPoolSize是线程池的最大尺寸,表示可以同时活动的线程数量的上限。
  • keepAliveTime 和 unit 共同表示了线程空闲后的存活时间
    • 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。也就是当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。
    • 如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0

newFixedThreadPool 工厂方法将线程池的基本大小和最大大小都设置为参数中指定的值,而且创建的线程池不会超时。

newCachedThreadPool工厂方法将线程池的最大大小设置为Integer.MAX_VALUE,将基本大小设置为0,并将超时设置为1分钟,这种方法创建出来的线程池可以被无限扩展,并且当需求降低时会自动收缩。

管理队列任务

使用BlokcingQueue来保存等待执行的任务。基本的队列有无界队列,有界队列和同步移交。队列的选择和线程池的其他配置参数有关

newFixedThreadPool 和 newSingleThreadExecutor 在默认情况下使用一个无界的LinkedBlockingQueue。如果所有工作者线程都处于忙碌状态, 那么任务将在队列中等待。

有界队列包括ArrayBlockingQueue以及有界的LinkedBlockingQueue和PriorityBlockingQueue。在使用有界队列时,队列的大小需要和线程池的大小一起调节。线程池小而队列较大,有助于减少内存使用量,降低CPU的使用率,同时还可以减少上下文切换,但是可能会限制吞吐量。

对于非常大或无界的线程池,可以通过SynchronousQueue来避免任务排队,以及直接将任务从生产者移交给工作者线程。这个队列的put方法会阻塞,直到有线程准备从队列里面take,所以本质上SynchronousQueue并不是Queue,它不存储任何东西,它只是在移交东西,是一种在线程之间进行移交的机制。要将一个任务放到其中,必须有另一个线程正在等待接受这个元素。如果没有线程正在等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor将创建一个新的线程,否则这个任务奖杯拒绝。在 newCachedThreadPool中采用了SynchronousQueue。

饱和策略

当有界对垒被填满后,饱和策略开始发挥作用。通过setRejectedExecutionHandler来修改。有以下四种饱和策略。

  • AbortPolicy: 饱和策略,使用这种策略的线程池,将在无法继续接受新任务时,给任务提交方抛出RejectedExecutionException,让他们决定要如何处理
  • DiscardPolicy:抛弃策略,直接丢弃掉新来的任务
  • CallerRunsPolicy:调用者运行策略,这个策略,顾名思义,将把任务交给调用方所在的线程去执行
  • Discard-OldestPolicy: 抛弃最旧的策略,抛弃下一个奖杯执行的任务,然后尝试重新提交新的任务。(如果是优先级队列,会抛弃优先级最高的任务,最好不要一起使用)

线程工厂

线程池在创建线程时,通过线程工厂方法来完成。在默认的ThreadFactory接口中只定义了一个方法newThread,每当创建新线程时都会调用这个方法。可以自己创建一个类实现默认的ThreadFactory接口来定制自己的线程工厂。

扩展ThreadPoolExecutor

ThreadPoolExecutor是可以扩展的,它提供了几个可以在子类中改写的方法:beforeExecute, afterExecute, terminated。在执行任务的线程中将调用beforeExecute和afterExecute方法。无论是正常返回还是抛出异常,afterExecute都被调用。如果beforeExecute抛出一个RuntimeException,任务将不被执行,afterExecute也不会调用。

参考资料