• 并发编程(二):线程池


      相比于线程池,我们可能接触new Thread更多一点,既然有了new Thread我们为什么还要使用线程池呢?

      

      new Thread的弊端

        a、每次new Thread新建对象,性能差

        b、线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM(OutOfMemory)

        c、缺少更多功能,如更多执行、定期执行、线程中断

      线程池的优势

         a、重用存在的线程,减少对象创建、消亡的开销,性能好

         b、可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞

         c、提供定时执行、定期执行、单线程、并发数控制等功能

      线程池类图

      由类图可以看出,ThreadPoolExecutor类是线程池中最核心的一个类。我们来做重点分析

      ThreadPoolExecutor构造参数

         a、corePoolSize:核心线程数量,默认情况下(可预创建线程)线程池后线程池中的线程数为0,当有任务后当有任务后就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列中

         b、maximumPoolSize:线程最大线程数

         c、workQueue:阻塞队列,存储等待执行的任务,有三种取值,ArrayBlockQueue(基于数组的先进先出队列,创建时必须指定大小)、LinkedBlockingQueue(基于链表的先进先出队列,如果没有指定此队列大小,默认为Integer.MAX_VALUE)、SynchronousQueue(不会保存提交的任务,直接新建一个线程来执行新的任务)

         d、keepAliveTime:线程没有任务执行时最多保持多久时间终止,默认情况只有当线程池中的线程数大于corePoolSize时,keepAliveTIme才会起作用,当线程池中的线程数大于corePoolSize,如果一个线程的空闲时间达到keepAliveTime,则会终止。如果调用allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时keepAliveTime参数也会起作用,直到线程池的线程数为0

         e、unit:keepAliveTime的时间单位,有7中取值,如:TimeUnit.DAYS; 天,可具体到纳秒

         f、threadFactory:线程工厂,用来创建线程

         g、rejectHandler:当拒绝处理任务时的策略,通常有四种取值,ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常;ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,重新尝试执行任务(重复此过程);ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

      参数之间的关系如下:

       a、如果当前poolsize小于corePoolSize,创建新线程执行任务

       b、如果当前poolsize大于corePoolsize,且等待队列未满,进入等待队列

       c、如果当前poolsize大于corePoolsize且小于maximumPoolSize,且等待队列已满,创建新线程执行任务

       d、如果当前poolsize大于corePoolSize且大于maximumPoolSize,且等待队列已满则用拒绝策略来处理该任务

       e、线程池中的线程执行完任务后不会立刻退出,而是去检查等待队列是否有新的线程去执行,如果在keepAliveTime里等不到新任务,线程就会退出

      ThreadPoolExecutor状态

      图中需要注意的是shutdown()和shutdownNow()方法的区别,执行前者后,还在执行的线程会执行完再关闭,执行后者后,线程池会立刻关闭,正在执行的线程不再执行

     ThreadPoolExecutor方法

      a、execute():提交任务,交给线程池执行

      b、submit():提交任务,能够返回执行结果 execute+Future

      c、shutdown():关闭线程池,等待任务都执行完

      d、shutdownNow():关闭线程池,不等待任务执行完

      e、getTaskCount():线程池已执行的和未执行的任务总数

      f、getCompletedTaskCount():已完成的任务数量

     g、getPoolSize():线程池当前线程数量

     h、getActiveCount():当前线程池正在执行任务的线程数量

      Executors类

        由上面的类图可以看出,Executors类为Executor,ExecutorService,ScheduledExecutorService提供了一些工具方法,并且Executors封装了ScheduledThreadPoolExecutor类和ThreadPoolExecutor类,所以我们倡导在实际使用中使用此类,Executors类提供了四个静态方法:

       a、newCachedThreadPool:创建可缓存线程池,灵活回收空闲线程,如果没有可回收线程,则创建新线程

      由上图可看出,newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用SynchronousQueue,就是说有新任务就创新线程运行,线程空闲超过60秒,则销毁线程。demo代码如下:

     public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            for (int i = 0; i < 10; i++) {
                final int index = i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        log.info("task:{}", index);
                    }
                });
            }
            executorService.shutdown();
        }

       b、newFixedThreadPool:创建定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

      newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值相等,线程空闲后直接销毁,使用的是LinkedBlockingQueue。demo代码如下:

    public static void main(String[] args) {
            ExecutorService executorService = Executors.newFixedThreadPool(3);
    
            for (int i = 0; i < 10; i++) {
                final int index = i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        log.info("task:{}", index);
                    }
                });
            }
            executorService.shutdown();
        }

       c、newScheduledThreadPool:创建大小无限制线程池(最大为Integer.MAX_VALUE),支持定时及周期性任务执行

      newScheduledThreadPool的特点是可以进行任务调度,最常用的方法是ScheduleAtFixedRate(基于固定时间间隔进行任务调度)和ScheduleWithFixedDelay(基于不固定时间间隔进行任务调度,主要取决于任务执行时间长短), demo代码如下:

     public static void main(String[] args) {
    
            ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    log.warn("schedule run");
                }
            },3,TimeUnit.SECONDS);
    
            executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    log.warn("schedule run");
                }
            },1,3,TimeUnit.SECONDS);
            
        }

      

       d、newSingleThreadExecutor:创建一个单线程化的线程池,只会用唯一的线程来执行任务

      newSingleThreadExecutor将corePoolSize和maximumPoolSize都固定为1,线程空闲时直接销毁,使用的LinkedBlockingQueue.demo代码如下:

     public static void main(String[] args) {
            ExecutorService executorService = Executors.newSingleThreadExecutor();
    
            for (int i = 0; i < 10; i++) {
                final int index = i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        log.info("task:{}", index);
                    }
                });
            }
            executorService.shutdown();
        }

      线程池合理配置

        线程池的具体配合需要按照实际情况进行调整,以下为两条参考原则,可先按此原则设置再根据系统负载,资源利用情况进行调整。

        a、cpu密集型任务,需要尽量压榨cpu,参考值可以设置为ncpu+1;

        b、io密集型任务,参考值可以设置为2*ncpu

        

          

      

       

  • 相关阅读:
    每周进度条(第九周)
    团队项目最后更改版
    项目需求分析与建议 NABCD模型
    课堂练习找水王
    问题账户需求分析
    2016年秋季个人阅读计划
    学习进度条
    用户体验
    程序员修炼之道——从小工到专家阅读笔记03
    程序员修炼之道——从小工到专家阅读笔记02
  • 原文地址:https://www.cnblogs.com/sbrn/p/8974566.html
Copyright © 2020-2023  润新知