精彩文章免费看

线程池基础知识整理

本文为 Crocutax 原创 , 转载请注明出处 http://www.crocutax.com

1.为什么需要线程池

在面向对象编程中 ,创建和销毁对象是很耗时的,因为创建一个对象要获取内存资源或者其他更多资源.所以在日常编程中才会有意的避免过多的创建并不必要的对象.

线程的创建和销毁也是同样,而且相比于普通的对象更为消耗资源.线程池技术的引入,就是为了解决这一问题.

1.1 线程池简介

线程池 是指在初始化一个多线程应用程序过程中创建的一个线程集合,线程池在任务未到来之前,会创建一定数量的线程放入空闲队列中.这些线程都是处于睡眠状态,即均未启动,因此不消耗CPU,只是占用很小的内存空间.当请求到来之后,线程池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理.

当预先创建的线程都处于运行状态时,线程池可以再创建一定数量的新线程,用于处理更多的任务请求.

如果线程池中的最大线程数使用满了,则会抛出异常,拒绝请求.当系统比较清闲时,也可以通过移除一部分一直处于停用状态的线程,线程池中的每个线程都有可能被分配多个任务,一旦任务完成,线程回到线程池中并等待下一次分配任务.

使用线程池可以提升性能,减少CPU资源的消耗,同时还可以控制活动线程,防止并发线程过多,避免内存消耗过度.

1.2 线程池优点总结

  • 复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
  • 能有效控制线程池的最大并发数量,避免大量线程之间因互相抢占系统资源而导致的阻塞现象.
  • 能够对线程进行简单的管理,并提供定时执行,指定间隔,循环执行等功能.
  • 2.Executor的继承关系图

    黄色为接口 , 蓝色为类

    * @param command Runnable任务 * @throws RejectedExecutionException 如果任务无法继续执行,则抛出此异常 * @throws NullPointerException 如果传入的Runnable为null,则抛出此异常 void execute(Runnable command);

    ExecutorService
    Executor的扩展接口,用于定义一些Runnable管理相关的方法,比如

    void shutdown(); 有序关闭已经提交的任务,但是不再接受新的任务,重复shotdown无效.而且此方法不会等待已提交任务的执行完毕. List<Runnable> shutdownNow(); 尝试停止所有正在执行的任务,终止所有处于等待队列中的任务,并将这些等待被执行的任务返回给调用者 boolean isShutdown(); 判断线程池是否已关闭 boolean isTerminated(); 当调用了showdown()方法后,所有任务是否已执行结束.注意:如果不事先调用showdown()方法,则此方法永远返回false. boolean awaitTermination(long timeout, TimeUnit unit); 当调用shotdown()方法后,调用此方法可以设置等待时间,等待执行中的任务全部结束,全部结束返回true.如果超时,或线程中断导致未全部结束则返回false. <T> Future<T> submit(Callable<T> task); 提交有返回值的Runnable任务. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) 执行传入的任务,当所有任务执行结束或超时后,返回持有任务状态和结果的Future集合. 注意:一个任务结束有两种情况:1.正常执行完成;2.抛出异常. <T> T invokeAny(Collection<? extends Callable<T>> tasks); 执行传入的任务,只要有任何一个任务成功执行完成(不抛出异常),一旦有结果返回,或抛出异常,则其他任务取消.
  • ScheduledExecutorService
    ExecutorService的子接口,定义了延迟或周期性执行Runnable任务的方法.

  • AbstractExecutorService
    ExecutorService的默认抽象实现类,对ExecutorService进行了简单实现,开发者可以参考并重写这些方法.

  • SerialExecutorService
    ExecutorService的子接口,标记型接口,该类型的线程池会以队列(先进先出)的顺序执行提交的任务.

  • ThreadPoolExecutor
    AbstractExecutorService子接口的默认实现类,可以使用这个类自定义线程池使用.系统提供的几种常用线程池,最终都是通过此类来创建.

  • ScheduledThreadPoolExecutor
    ScheduledExecutorService子接口的实现类,继承ThreadPoolExecutor,用于延迟或周期性执行Runnable任务.

  • 3.如何创建线程池

    Executor是Java中的一个接口,其默认实现类是ThreadPoolExecutor,构造方法如下

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

    ThreadPoolExecutor一共有4个重载的构造方法,上述代码中的后三位参数都是可选参数.
    通过构造方法即可自定义线程池.然后通过execute()来执行Runnable任务.

    涉及到的几个参数解释如下:

  • corePoolSize
  • 线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态.如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的[核心线程]在等待新任务到来时会有超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止.

  • maximumPoolSize
  • 线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞.

  • keepAliveTime
  • 非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收.当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时, keepAliveTime同样会作用于核心线程.

    这个[保活时间]设计的巧妙之处在于:当线程处于闲置状态时,并不马上销毁,而是在指定时间段内将其缓存在线程池中,以方便在限定的时间段内如果再有任务来临,能够快速的重新启用等待中的线程.处于闲置状态的空闲线程并不会占用多少内存,而且这样就能显著减少频繁的创建,销毁线程造成的内存消耗及性能下降.

    用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.NANOSECONDS(毫秒),TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分钟)等

  • workQueue
  • 线程池中等待被执行的任务队列,这个队列仅持有通过execute方法提交的Runnable任务.

  • threadFactory
  • 线程工厂,ThreadFactory是个接口,它只有一个方法,·Thread newThread(Runnable r),executor创建新线程时调用

    RejectedExecutionHandler 当由于线程阻塞,任务队列容量已满等因素导致无法成功执行任务时,这个handler会调用rejectedExecution方法来通知调用者.

    4.系统封装的4种线程池

    这里要使用到Executors类,它是Executor体系的静态工厂类,类中封装了一些创建线程池的方法.
    Executor与其子接口及其实现类负责定义和规范线程池,而Executors负责创建线程池.

    4.1 SingleThreadExecutor

    SingleThreadExecutor内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行.
    它的意义在于统一所有的外界任务到一个线程中,使得在这些任务之间不需要处理线程同步的问题.

    内部实现如下:

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

    代码测试:

    private void singleThreadExecutorTest() {
        //单一线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        //开启5次线程
        for (int i = 0; i < 5; i++) {
            singleThreadExecutor.execute(new MyThread());
        //关闭线程池
        singleThreadExecutor.shutdown();
    

    Log输出:

    System.out: pool-1-thread-1 执行
    System.out: pool-1-thread-1 执行
    System.out: pool-1-thread-1 执行
    System.out: pool-1-thread-1 执行
    System.out: pool-1-thread-1 执行
    

    4.2 FixedThreadPoolExecutor

    FixedThreadPoolExecutor是一种线程数量固定的线程池,只有核心线程,没有超时机制,任务队列没有大小限制.
    当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来.
    当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了.这意味着它能够更加快速的响应外界的请求.

    //构造方法
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    

    代码测试:

    /**自定义一个线程类,继承Thread,打印Log*/
    class MyThread extends Thread{
        public void run() {
            System.out.println(Thread.currentThread().getName()+" 执行");
    private void fixedThreadPoolTest() {
        //容量为2的固定线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
        //创建5条线程
        for (int i = 0; i < 5; i++) {
            fixedThreadPool.execute(new MyThread());
        //关闭线程池
        fixedThreadPool.shutdown();
    

    Log输出:

    System.out: pool-1-thread-1 执行
    System.out: pool-1-thread-2 执行
    System.out: pool-1-thread-2 执行
    System.out: pool-1-thread-2 执行
    System.out: pool-1-thread-1 执行
    

    可以发现,虽然new了5个Thread,但是系统只用两条线程来执行5个任务.

    4.3 CachedThreadPool

    CachedThreadPool线程数量不定,只有非核心线程,并且其最大线程数为Integer.MAX_VALUE(相当于无限大)
    当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务.

    空闲线程都有超时时机,这个超时时长为60秒,限制超60秒就会被回收.

    和FixedThreadPoolExecutor不同的是,CachedThreadPool的任务队列其实相当一个空集合,这将导致任何任务都会被立即执行,因为此时SynchronousQueue是无法插入任务的.
    CachedThreadPool线程池比较适合执行大量+耗时较少的任务.当整个线程池都处于闲置状态时,会因超时而被停止,此时没有线程的线程池几乎不占用任何系统资源.

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

    代码测试:

    private void cachedThreadPoolTest() {
        //缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        //创建5条线程
        for (int i = 0; i < 5; i++) {
            cachedThreadPool.execute(new MyThread());
        //关闭线程池
        cachedThreadPool.shutdown();
    

    Log输出:

    System.out: pool-1-thread-1执行
    System.out: pool-1-thread-1执行
    System.out: pool-1-thread-2执行
    System.out: pool-1-thread-3执行
    System.out: pool-1-thread-4执行
    

    4.4 ScheduledThreadPoolExecutor

    ScheduledThreadPoolExecutor核心线程数固定,非核心线程数没有限制,并且非核心线程闲置时会被回收
    主要用于执行定时任务 和 具有固定周期的重复任务.

    //Executors静态方法
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    //继续追查ScheduledThreadPoolExecutor源码,会发现最终还是通过ThreadPoolExecutor的构造来创建
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    

    常用方法:

    schedule(Runnable command,long delay, TimeUnit unit)XXX时间之后,执行指定的任务 scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
    XXX时间后,执行指定任务,每隔XXX时间执行一次

    代码测试:

    private void scheduledThreadPoolTest() {
        //定时任务线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
        //3秒后打印一次Log
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("~~~~~~~~~3秒之后露个脸~~~~~~~~~");
        },3000,TimeUnit.MILLISECONDS);
        //0秒初始化延迟,每秒打印Log
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("每隔一秒打印一次");
        },0,1000, TimeUnit.MILLISECONDS);
    

    Log输出:

    System.out: 每隔一秒打印一次
    System.out: 每隔一秒打印一次
    System.out: 每隔一秒打印一次
    System.out: ~~~~~~~~~3秒之后露个脸~~~~~~~~~
    System.out: 每隔一秒打印一次
    System.out: 每隔一秒打印一次
    System.out: 每隔一秒打印一次
    

    可以发现,系统提供的4种线程池,最终都是通过配置ThreadPoolExecutor的不同参数,来巧妙的达到不同的线程管理效果.

    以上只是关于线程池的一些基础认知,下一篇进行 线程池运行原理分析.