多线程学习笔记-深刻理解ThreadPoolExecutor

  java多线程中,线程池的最上层接口是Executor,ExecutorService实现了Executor,是真正的管理线程池的接口,ThreadPoolExecutor间接继承了ExecutorService,提供了多种具体的线程池实现,在平常开发中通常直接使用Executors工具类提供的几种经常使用ThreadPoolExecutor,下面详细介绍下ThreadPoolExecutor.java

ThreadPoolExecutor基本参数

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1000L, 
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(20),
new ThreadPoolExecutor.CallerRunsPolicy());

  corePoolSize:核心线程数的大小,也有说线程池最小线程数缓存

  maximumPoolSize:线程池最大线程数多线程

  keepAliveTime:当没有任务执行时,线程能存活的最大时间,这里说的线程是指大于corePoolSize,小于maximumPoolSize的线程工具

  timeunit:keepAliveTime的时间单位spa

  workQueue:用来存放task的堵塞队列,队列的选择和size的大小对线程池的运行有直接影响,默认有几种实现,后面详说.线程

  rejectHandler:拒绝策略,当队列已满而且线程数达到maximumPoolSize时,再有新任务进来时会执行拒绝策略,默认集中实现,后面详说.code

ThreadPool模型初始化

  ThreadPool线程池初始化时,不会建立corePoolSIze的线程,也就是说在没有task进来的时候,线程池是空的,当有task进来的时候,开始建立线程,而且线程执行完task后不会销毁,而是驻留内存,直至达到corePoolSize,那何时线程数会再度增长达到maxPoolSize呢,这就取决于存放task的queue的size了,若是task的数量一直不超过指定的size那么就不会建立新的线程出来,反之,则会建立新的线程去执行task,那么新建出来的线程执行完task也会一直驻留内存吗?答案是不会,这时候就要看设置的keepAliveTime,若是在超过了这个时间后仍是没有task去使用这个线程,则线程销毁,直至线程数等于corePoolSize.那么若是queue也满了,线程数也达到maxPoolsize,这时候怎么办呢,这时rejectHandler就会发挥做用,会根据咱们指定的拒绝策略去处理这种场景.blog

Executors的几个默认实现

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
    }

  1.newSingleThreadExecutor:建立一个单线程的线程池.若是这个线程在执行霍城中由于异常结束,则会建立一个新的线程来代替它,这个线程池保证全部任务的执行顺序是按照任务提交顺序进行.继承

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
    }

  2.newFixedThreadPool:建立一个固定大小的线程池.看源码可知,该实现建立了一个corePoolSize等于maximumPoolSize的线程池.接口

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
    }

  3.newCachedThreadPool:建立一个可缓存的线程池.看源码可知corePoolSize=0,即当线程空闲时会被回收,当线程忙碌时又能够"源源不断"的建立新线程来执行task.

  默认线程池实现中还有ScheduledThreadPoolExecutor,能够实现定时的一些功能,可用来代替Timer或者TimeTask.这里再也不展开说.

线程池的堵塞队列

  如何选择线程池的堵塞队列,取决咱们的业务场景,即咱们但愿以一种什么样的排队策略来处理任务.排队一般有3种策略,对应下面几种queue.

  直接提交(SynchronousQueue):不排队,这个队列不会存储task,会将调用方的task直接提交给线程,指定了SynchronousQueue的线程池一般会把maximumPoolSize配置的比较大,不然可能会致使没有足够的线程来执行task,而致使task没法放入queue而被丢弃或拒绝.

  有界队列(ArrayBlockingQueue):经过指定ArrayBlockingQueue的size能够设置队列的最大存储个数,当超出这个个数时就会新建线程去执行task直至线程数达到maximumPoolSize,有界队列size的设置和maximumPoolSize的设置息息相关.会影响CPU的使用率以及系统吞吐量.

  无界队列(不设定size的LinkedBlockingQueue):当线程数达到corePoolSize的仍有task进来时,会源源不断进队列,因为无解,maximumPoolSize参数会失效,线程数最大只能达到corPoolSize.

线程池拒绝策略

  CallerRunsPolicy:使用调用方的线程来执行task,一般状况下调用方线程就是指咱们所说的主线程,这样的好处是不会丢弃task,可是缺点也很明显,使用这种策略会堵塞主线程,进而拖慢主线程的整个调用时长.而基于此,该策略同时也减缓了新的task提交进来的速度(由于主线程原本只须要用来提交task就行了,如今直接去执行task,后面的task进来的速度就慢了).

  AbortPolicy:这个策略简单粗暴,直接抛出异常,不跟你多BB,须要注意的是,这个是jdk的默认策略.

  DiscardPolicy:这个和AbortPolicy差很少,区别是不会抛出异常,直接丢弃.

  DiscardOldestPolicy:这也是一种丢弃策略,不过和上面的DiscardPolicy恰好相反,她丢弃的不是新进来的task而是在堵塞队列中存在时间最久的那个task,即丢弃最先进入队列而且尚未被执行的task.

相关文章
相关标签/搜索