• Java并发编程(十三)-- 线程池


    什么是线程池?

      线程池就是以一个或多个线程循环执行多个应用逻辑的线程集合.

    为什么用线程池?

    • 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率

      例如:

      记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3

      如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!

      正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销

    • 线程并发数量过多,抢占系统资源从而导致阻塞

      我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况

      运用线程池能有效的控制线程最大并发数,避免以上的问题

    • 对线程进行一些简单的管理

      比如:延时执行、定时循环执行的策略等

      运用线程池都能进行很好的实现

    线程池 -- ThreadPoolExecutor

    在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类。

    对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置,看看构造函数的各个参数吧。

    ThreadPoolExecutor提供了四个构造函数

    public class ThreadPoolExecutor extends AbstractExecutorService {
        .....
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
        ...
    }

    从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

    • int corePoolSize => 该线程池中核心线程数最大值

      核心线程:

      线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程

      核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

      如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉

    • int maximumPoolSize

      该线程池中线程总数最大值

      线程总数 = 核心线程数 + 非核心线程数。核心线程在上面解释过了,这里说下非核心线程:

      不是核心线程的线程(别激动,把刀放下…),其实在上面解释过了

    • long keepAliveTime

      该线程池中非核心线程闲置超时时长

      一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉

      如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

    • TimeUnit unit

      keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:

      1. NANOSECONDS : 1微毫秒 = 1微秒 / 1000
      2. MICROSECONDS : 1微秒 = 1毫秒 / 1000
      3. MILLISECONDS : 1毫秒 = 1秒 /1000
      4. SECONDS : 秒
      5. MINUTES : 分
      6. HOURS : 小时
      7. DAYS : 天
    • BlockingQueue workQueue

      该线程池中的任务队列:维护着等待执行的Runnable对象

      当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

      常用的workQueue类型:

      1. SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

      2. LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

      3. ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

      4. DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

    • ThreadFactory threadFactory

      创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法

      小伙伴应该知道AsyncTask是对线程池的封装吧?那就直接放一个AsyncTask新建线程池的threadFactory参数源码吧:

      new ThreadFactory() {
          private final AtomicInteger mCount = new AtomicInteger(1);
      
          public Thread new Thread(Runnable r) {
              return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
          }
      }
    • RejectedExecutionHandler handler

      这玩意儿就是抛出异常专用的,比如上面提到的两个错误发生了,就会由这个handler抛出异常,你不指定他也有个默认的

    上面我们了解到ThreadPoolExecutor继承了AbstractExecutorService类,接下来一块看看AbstractExecutorService类的实现吧

    public abstract class AbstractExecutorService implements ExecutorService {
     
         
        protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
        protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
        public Future<?> submit(Runnable task) {};
        public <T> Future<T> submit(Runnable task, T result) { };
        public <T> Future<T> submit(Callable<T> task) { };
        private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                                boolean timed, long nanos)
            throws InterruptedException, ExecutionException, TimeoutException {
        };
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException {
        };
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                               long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
        };
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException {
        };
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                             long timeout, TimeUnit unit)
            throws InterruptedException {
        };
    }

    通过源码,可以看出AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。接下来一起看看ExecutorService接口

    public interface ExecutorService extends Executor {
     
        void shutdown();
        boolean isShutdown();
        boolean isTerminated();
        boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;
        <T> Future<T> submit(Callable<T> task);
        <T> Future<T> submit(Runnable task, T result);
        Future<?> submit(Runnable task);
        <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException;
        <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                      long timeout, TimeUnit unit)
            throws InterruptedException;
     
        <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException;
        <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                        long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

    而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:

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

    看完源码,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了吧。

      Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;

      ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

      AbstractExecutorService抽象类实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

      ThreadPoolExecutor继承了类AbstractExecutorService。

       ThreadPoolExecutor类中有几个非常重要的方法:

    • execute(Runnable Command)
    • submit() -- ExecutorService中的方法
    • shutdown()
    • shutdownNow()

        execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

      submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。

      shutdown()和shutdownNow()是用来关闭线程池的。

    线程池工作流程

    流程分析

    线程池的主要工作流程如下图:

    Java线程池主要工作流程

    从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下:

    1. 首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
    2. 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
    3. 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

    源码分析

    上面的流程分析让我们很直观的了解的线程池的工作原理,让我们再通过源代码来看看是如何实现的。线程池执行任务的方法如下:

    public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            //如果线程数小于基本线程数,则创建线程并执行当前任务
            if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
                //如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队列中。
                if (runState == RUNNING && workQueue.offer(command)) {
                    if (runState != RUNNING || poolSize == 0)
                        ensureQueuedTaskHandled(command);
                }
                //如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,则创建一个线程执行任务。
                else if (!addIfUnderMaximumPoolSize(command))
                    //抛出RejectedExecutionException异常
                    reject(command); // is shutdown or saturated
            }
        }

    工作线程

    线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会无限循环获取工作队列里的任务来执行。我们可以从Worker的run方法里看到这点:

    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);
        }
    }

    线程池实现原理

    下面是一个简单的线程池实现

    public class PoolThread extends Thread {
    
        private BlockingQueue<Runnable> taskQueue = null;
        private boolean isStopped = false;
    
        public PoolThread(BlockingQueue<Runnable> queue) {
            taskQueue = queue;
        }
    
        public void run() {
            while (!isStopped()) {
                try {
                    Runnable runnable = taskQueue.take();
                    runnable.run();
                } catch (Exception e) {
                    // 写日志或者报告异常,
                    // 但保持线程池运行.
                }
            }
        }
    
        public synchronized void toStop() {
            isStopped = true;
            this.interrupt(); // 打断池中线程的 dequeue() 调用.
        }
    
        public synchronized boolean isStopped() {
            return isStopped;
        }
    }

    线程池的实现由两部分组成。类 ThreadPool 是线程池的公开接口,而类 PoolThread 用来实现执行任务的子线程。

    为了执行一个任务,方法 ThreadPool.execute(Runnable r) 用 Runnable 的实现作为调用参数。在内部,Runnable 对象被放入阻塞队列 (Blocking Queue),等待着被子线程取出队列。

    一个空闲的 PoolThread 线程会把 Runnable 对象从队列中取出并执行。你可以在 PoolThread.run() 方法里看到这些代码。执行完毕后,PoolThread 进入循环并且尝试从队列中再取出一个任务,直到线程终止。

    调用 ThreadPool.stop() 方法可以停止 ThreadPool。在内部,调用 stop 先会标记 isStopped 成员变量(为 true)。然后,线程池的每一个子线程都调用 PoolThread.stop() 方法停止运行。注意,如果线程池的 execute() 在 stop() 之后调用,execute() 方法会抛出 IllegalStateException 异常。

    子线程会在完成当前执行的任务后停止。注意 PoolThread.stop() 方法中调用了 this.interrupt()。它确保阻塞在 taskQueue.dequeue() 里的 wait() 调用的线程能够跳出 wait() 调用,因为执行了中断interrupt,它能够打断这个调用。并且抛出一个 InterruptedException 异常离开 dequeue() 方法。这个异常在 PoolThread.run() 方法中被截获、报告,然后再检查 isStopped 变量。由于 isStopped 的值是 true, 因此 PoolThread.run() 方法退出,子线程终止。

    几种常见线程池

    如果你不想自己写一个线程池,那么你可以从下面看看有没有符合你要求的(一般都够用了),如果有,那么很好你直接用就行了,如果没有,那你就老老实实自己去写一个吧。

    Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的,下面我都会贴出这四种线程池构造函数的源码,各位大佬们一看便知!

    CachedThreadPool()

    可缓存线程池:

    1. 线程数无限制
    2. 有空闲线程则复用空闲线程,若无空闲线程则新建线程
    3. 一定程序减少频繁创建/销毁线程,减少系统开销

    创建方法:

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

    源码:

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

    FixedThreadPool()

    定长线程池:

    1. 可控制线程最大并发数(同时执行的线程数)
    2. 超出的线程会在队列中等待

    创建方法:

    //nThreads => 最大线程数即maximumPoolSize
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
    
    //threadFactory => 创建线程的方法
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);

    源码:

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

    ScheduledThreadPool()

    定长线程池:

    1. 支持定时及周期性任务执行。

    创建方法:

    //nThreads => 最大线程数即maximumPoolSize
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
    

    源码:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor():
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
    

    SingleThreadExecutor()

    单线程化的线程池:

    1. 有且仅有一个工作线程执行任务
    2. 所有任务按照指定顺序执行,即遵循队列的入队出队规则

    创建方法:

    ExecutorService singleThreadPool = Executors.newSingleThreadPool();

    源码:

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

    参考资料

      http://tutorials.jenkov.com/java-util-concurrent/executorservice.html

      http://ifeve.com/java-threadpool/

  • 相关阅读:
    __str__
    __call__
    私有成员
    @property
    静态方法
    静态字段
    cut qcut
    hive 函数大全
    sklearn 中的Countvectorizer/TfidfVectorizer保留长度小于2的字符方法
    numpy教程:随机数模块numpy.random
  • 原文地址:https://www.cnblogs.com/JackpotHan/p/9687852.html
Copyright © 2020-2023  润新知