Java线程池详解(JDK1.8)

为什么使用线程池?

       首先,一个线程的线程的创建和销毁都需要消耗资源,特别是当线程中执行的是比较简单的任务时,大部分系统资源花费在线程的创建和销毁上,把过多的资源都用在了准备工作上,这显然不是我们想要的。可见,“每任务每线程”的做法并不合理。从另一个角度看,每个线程的创建和销毁过程都是一样的,同样的过程为什么我们要重复做那么多次呢?于是就产生了线程池的思想。

        线程池把线程复用,一个线程执行完当前任务后并不马上销毁,而是从任务队列中取出一个任务继续运行。这种做法提高了线程的利用率,也减少了系统开销。

线程池原理

       1.8中有一个线程池的顶级接口Executor,该接口很接单,只有一个execute()函数,接受Runnable参数

public interface Executor {
    void execute(Runnable command);
}

 关于线程池相关类和接口的关系,可见下图:

 

Java线程池详解(JDK1.8)_第1张图片

Executor:一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command),

ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法

AbstractExecutorService:ExecutorService执行方法的默认实现

ScheduledExecutorService:一个可定时调度任务的接口

ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池

ThreadPoolExecutor:线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。

ForkJoinPool:将大任务分解成若干个小任务,当小任务均执行结束后,将任务做一个整合。

先来看看线程池的工作流程:

Java线程池详解(JDK1.8)_第2张图片

(图片取自https://www.cnblogs.com/superfj/p/7544971.html)

对于线程池ThreadPoolExecutor,其构造函数中包含了多个参数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
  • corePoolSize   线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
  • workQueue  用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列: 
    (1) ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务; 
    (2) LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene; 
    (3) SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene; 
    (4) priorityBlockingQuene:具有优先级的无界阻塞队列;
  • maximumPoolSize   线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,直到当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.
  • keepAliveTime   多于corePoolSize数目的线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;
  • unit  keepAliveTime的单位
  • threadFactory  创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory
  • handler  线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
  1. AbortPolicy:直接抛出异常,默认策略;
  2. CallerRunsPolicy:用调用者所在的线程来执行任务;
  3. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  4. DiscardPolicy:直接丢弃任务;  

     当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

    综合上面的流程图和构造函数,线程池的工作原理是:当前线程数小于corePoolSize时,每提交一个任务都会创建一个新的线程来执行这个任务。当创建的线程数达到corePoolSize时如果再次提交任务,在任务将会放入到阻塞队列中。当阻塞队列中的任务已满时,如果再向线程池提交任务,则先检查corePoolSize和线程池最大允许线程数maximumPoolSize,如果核心线程数小于最大线程数,则继续创建线程执行任务;如果当前线程数已经大于等于最大线程数,则根据handler采取相应的饱和处理策略。

线程池种类

根据不同的应用场景,JDK1.8提供了下面几种不同的线程池,线程池主要通过一个Executors类的静态方法获得。

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

   newFixedThreadPool是一个具有固定线程数的线程池,传入一个nThreads参数,并且令核心线程数等于最大线程数,即corePoolSize=maximumPoolSize=nThreads。这样每当线程数达到核心线程数后就不会再继续创建线程,也不会有因为空闲而被终止的线程,此时的keepAliveTime参数不起作用。当前线程都在运行时,新提交的任务会一直提交到阻塞队列中,此时使用的是LinkedBlockingQueue,存储的任务数目可达到Integer.MAX_VALUE,可视为无界队列。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。

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

       缓存线程池,核心线程数corePoolSize=0,最大线程数为int最大值Integer.MAX_VALUE,线程空闲时间为60秒。阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列,他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时有大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

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

    固定线程数目始终只有1的线程池,使用LinkBlockingQueue阻塞队列,单一的线程可保证任务按提交的顺序运行。

  • newScheduleThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

一个具有基础线程数corePoolSize且具有缓存功能的线程池,多余的线程一旦空闲下来就会被终止,能在指定时间执行任务。

关闭线程池

       ExecutorService提供了shutDown()和shutDownNow()两个函数来关闭线程池,底层还是通过逐个调用线程的interrupt()函数来实现中断线程从而关闭线程池的。

  • shutdown()   
public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//上锁
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

shutdown函数会把线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

  • shutdownNow()
public List shutdownNow() {
        List tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();//队列中还未执行的任务
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;//返回还未执行的任务
    }

shutdownNow方法会先将线程池状态修改为STOP,然后调用线程池里的所有线程的interrupt方法,并把工作队列中尚未来得及执行的任务清空到一个List中返回,getTask()方法返回null,从而线程退出 。但是ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

参考:

https://www.cnblogs.com/MOBIN/p/5436482.html

https://www.cnblogs.com/superfj/p/7544971.html

https://blog.csdn.net/programmer_at/article/details/79799267

你可能感兴趣的:(Java)