线程池

spring的线程池技术使用的是ThreadPoolTaskExecutor,当我们点进ThreadPoolTaskExecutor这个类的时候,可以看见里面是对于JDK中的ThreadPoolExecutor的封装:

1、为什么要使用线程池?

创建和销毁线程的花销是很大的,处理时间可能比处理业务的时间还长。

频繁创建和销毁线程,可能导致系统资源不足

2、线程池有什么作用?

1)提高效率:创建好一定数量的线程放在池中,需要的时候就去池中拿一个,比需要的时候再去创建一个线程对象要快的多

2)便于管理:程序启动时,管理代码去创建100个线程,有请求的时候就分配一个线程去工作,若有101个请求,则第101个请求 排队等候,避免频繁创建线程,导致系统崩溃

3、常见线程池创建及其使用场景

java里面的线程池的顶级接口是Executor,Executor并不是一个线程池,而只是一个执行线程的工具,而真正的线程池是ExecutorService

1)newCachedThreadPool创建一个可缓存线程池线程数量不定,最大线程数为Integer.MAX_VALUE。空闲线程超时60秒,会被回收。若无可回收,会新建线程。若线程可用的话,调用execute方法将重用以前构造的线程。此类线程池比较适合执行大量的耗时较少的任务整个线程池都处于闲置状态时,线程池中的线程都会超时被停止

实例代码:

public Class CachedThreadPoolTest{

  public static void main(String[] args) Throws InterruptedException{

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

    for(int i=0;i<7;i++){

      final int index = i;

      try{

        Thread.sleep(2000);

      }catch(InterruptedException e){

        e.printStackTrace();

      }

      cachedThreadPool.execute(new Runnable(){

        @Override

        public void run(){

          sysout(“第”+index+"个线程"+Thread.currentThread().getName());

        }

      });

    }

  }

}

创建可缓存线程池的具体实现

public static ExecutorService newCachedThreadPool(){

  return new ThreadPoolExecutor( 0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

}

输出结果是:

0个线程pool-1-thread-1

1个线程pool-1-thread-1

2个线程pool-1-thread-1

3个线程pool-1-thread-1

4个线程pool-1-thread-1

5个线程pool-1-thread-1

6个线程pool-1-thread-1

从结果可以看出,执行第二个任务的时候第一个任务已经完成,会复用执行第一个任务的线程,不用每次都新建线程

2)newFixedTheadPool创建一个定长线程池指定线程数量,每当提交一个任务就创建一个工作线程当线程处于空闲状态,不会被回收,除非线程池被关闭了。若工作线程数量达到指定的初始化线程数大小,则将提交的任务存入到池队列中,当有线程可用时,任务开始执行。由于newFixedTheadPool只有核心线程并且这些核心线程不会被回收,这样它更快响应外界请求

实例代码:

public Class FixedThreadPoolTest{

  public static void main(String[] args) Throws InterruptedException{

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//设置最大线程数5个

    for(int i=0;i<7;i++){

      final int index = i;

      fixedThreadPool .execute(new Runnable(){

        @Override

        public void run(){

          sysout(“时间是:”+System.currentTimeMillis()+"第"+index+"个线程"+Thread.currentThread().getName());

          try{

            Thread.sleep(2000);

          }catch(InterruptedException e){

            e.printStackTrace();

          }

        }

      });

    }

  }

}

创建定长线程池的具体实现:

public static ExecutorService newFixedThreadPool( int nThreads){

  return new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

}

输出结果是:

时间是:1533382000965第0个线程pool-1-Thread-1

时间是:1533382000966第4个线程pool-1-Thread-5

时间是:1533382000965第3个线程pool-1-Thread-4

时间是:1533382000965第1个线程pool-1-Thread-2

时间是:1533382000965第2个线程pool-1-Thread-3

时间是:1533382002966第5个线程pool-1-Thread-3

时间是:1533382002966第6个线程pool-1-Thread-5

同一时间5个线程同时执行任务,故前五个打印时间是相同的,线程执行顺序随机。等待两秒后,再执行后两个任务

3)newScheduledThreadPool创建一个定长线程池核心线程数量固定而非核心线程数是没有限制的,且当非核心线程空闲时会被立即回收,它可安排给定延迟后运行命令或者定期地执行。此类线程池主要用于执行定时任务和具有固定周期的重复任务

延迟执行代码示例:

public Class ScheduledThreadPoolTest{

  public static void main(String[] args) Throws InterruptedException{

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);//设置池中核心线程数量是2

    sysout("现在的时间是:"+System.currentTimeMillis());

    scheduledThreadPool .schedule(new Runnable(){

      @Override

      public void run(){

        sysout("现在的时间是:"+System.currentTimeMillis());

      }

    }, 4, TimeUnit.SECONDS);//设置延迟4秒执行

  }

}

执行结果是:

现在的时间是:1533384023005

现在的时间是:1533384027007

忽略误差,实际结果确实延迟了4秒执行

定期执行代码示例:

public Class ScheduledThreadPoolTest{

  public static void main(String[] args) Throws InterruptedException{

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);//设置池中核心线程数量是2

    sysout("现在的时间是:"+System.currentTimeMillis());

    scheduledThreadPool .scheduleAtFixedRate(new Runnable(){

      @Override

      public void run(){

        sysout("现在的时间是:"+System.currentTimeMillis());

      }

    }, 2, 3,  TimeUnit.SECONDS);//设置延迟2秒后每3秒执行一次

  }

}

创建定时及周期性执行任务的定长线程池的具体实现

public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize){

  return new ScheduledThreadPoolExecutor( corePoolSize );

}

执行结果是:

现在的时间是:1533384668068

现在的时间是:1533384070070

现在的时间是:1533384073070

现在的时间是:1533384076070

现在的时间是:1533384079070

确实是延迟2秒后每隔3秒执行一次,程序不退出就会一直执行打印下去

4)newSingleThreadExecutor创建一个单线程化的线程池只有一个核心线程,以无界队列(无界队列在最末尾有解释)方式来执行该线程,任务间不需要处理线程同步的问题,它确保所有的任务都在同一个线程中按顺序执行,且在任意给定时间不会有多个线程是活动的

实例代码:

public Class SingleThreadPoolTest{

  public static void main(String[] args) Throws InterruptedException{

    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

    for(int i=0;i<7;i++){

      final int index =i;

      singleThreadPool.execute(new Runnable(){

        @Override

        public  void run(){

          sysout("现在的时间是:"+System.currentTimeMillis()+"第"+index+“个线程”);

          try{

            Thread.sleep(2000);

          }catch(InterruptedException e){

            e.printStackTrace();

          }

        }

      });

    }

  }

}

创建单线程池的具体实现

public  static ExecutorService newSingleThreadExecutor(){

  return new FinalizableDelegatedExecutorService( new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));

}

执行结果是:

现在的时间是:1533385933444第0个线程

现在的时间是:1533385935445第1个线程

现在的时间是:1533385937445第2个线程

现在的时间是:1533385939445第3个线程

现在的时间是:1533385941445第4个线程

现在的时间是:1533385943445第5个线程

现在的时间是:1533385945445第6个线程

单个线程顺序执行上面7个线程(可以将线程名打印出来,可以发现是同一个线程)

 4、使用Executors各个方法的弊端

newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至out of memory;

newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至out of memory;

5、线程池的四种工作队列

1)(常用)LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO先进先出排序元素,吞吐量高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列;

2)(常用)SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列;

3)(不常用)ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,此队列按FIFO先进先出原则对元素进行排序;

4)(不常用)PriorityBlockingQueue:一个具有优先级的无限阻塞队列;

6、线程池ThreadPoolExecutor的几种重要的参数

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

  if(corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize  < corePoolSize  || keepAliveTime < 0) throw new IllegalArgumentException();

  if(workQueue == null ||  threadFactory == null || handler == null) throw new NullPointerException();

}

corePoolSize :核心线程数大小。默认情况下创建线程池后,池中是没有任何线程的(线程数为0),而是等待有任务到来才去创建一个线程执行任务。若调用了prestartAllCoreThreads()或者prestartCoreThread()方法,在没有任务到来之前就预创建corePoolSize个线程或者一个线程。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列中去;

maximumPoolSize:线程池最大线程数,表示线程池最多能创建多少个线程;

keepAliveTime表示线程没有任务执行时最多保持多久时间会终止。当线程池中的线程数大于corePoolSize时,keepAliveTime才会生效使一个线程空闲时间达到它时终止,直到线程池中的线程数不大于corePoolSize。若调用了allowCoreThreadTimeOut(boolean)方法,在线程池中额线程数不大于corePoolSize时,keepAliveTime参数也会起作用,知道线程池中线程数为0;

unit:参数keepAliveTime的时间单位,7种取值是TimeUnit类中的7中静态属性:

TimeUnit.DAYS  /  TimeUnit.HOURS  /  TimeUnit.MINUTES  / TimeUnit.SECONDS  / TimeUnit.MILLISECONDS  /  TimeUnit.MICROSECONDS  / TimeUnit.NANOSECONDS纳秒

workQueue:一个阻塞队列,用来存储等待执行的任务,种类在上一部分5中有详解。线程池的排队策略与blockingQueue有关;

threadFactory:用于设置创建线程的工厂,可以通过线程工厂给线程设置deamon和优先级等

handler:表示当拒绝处理任务时的策略,有四种取值:ThreadPoolExecutor.AbortPolicy(直接丢弃任务并抛出RejectedExecutionException异常)、ThreadPoolExecutor.CallerRunsPolicy(只用调用者所在线程来运行任务)、ThreadPoolExecutor.DiscardOldestPolicy(丢弃队列里最前面的一个任务,并执行当前任务)、ThreadPoolExecutor.DiscardPolicy(丢弃任务但不抛出异常)

还可根据应用场景需要来实现RejectedExeutionHandler接口自定义策略;

7、有界队列和无界队列

有界队列

(1)初始的poolSize< corePoolSize,提交的runnable任务,会直接作为new一个thread的参数,立马执行;

(2)当提交的任务数超过了corePoolSize,会将当前的runnable提交到一个block queue中;

(3)有界队列满了之后,如果poolSize < maximumPoolSize时,会尝试new一个thread的进行救急处理,立马执行对应的runnable任务;

(4)如果3中也无法处理了,就会走到第四步执行reject操作;

无界队列

除非系统资源耗尽,否则无界队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加,若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。

 

合理配置线程池大小:https://www.zhihu.com/question/38128980

 

8、如果提交任务时,线程池队列已满,会发生什么?

如果一个任务不能被调度执行,那么ThreadPoolExecutor的submit()方法会抛出一个RejectedExecutionException异常;

9、线程池的submit()和execute()方法有什么区别?

两个方法都可以向线程池提交任务。

execute方法是void型的,定义在Executor接口中;

submit方法可以返回持有计算结果的future对象,定义在ExecutorService接口中,扩展了Executor接口;

ThreadPoolExecutor和ScheduledThreadPoolExecutor线程池都有这些方法。

 

 

 

原文地址:https://www.cnblogs.com/blackdd/p/12492679.html