并发编程(二):线程池

时间:2022-10-29 18:33:54

  相比于线程池,我们可能接触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