• Java线程池及Executor框架的理解


    声明:本文参考自:https://www.cnblogs.com/liconglong/p/13167227.html

    Java中线程池的实现原理及流程:

      

       如上图所示,当一个线程提交到线程池时(execute()或submit()),先判断核心线程数(corePoolSize)是否已满,如果未满,则直接创建线程执行任务;如果已满,则判断队列(BlockingQueue)是否已满,如果未满,则将线程添加到队列中;如果已满,则判断线程池(maximumPoolSize)是否已满,如果未满,则创建线程池执行任务;如果线程池已满,则交给饱和策略(RejectedExecutionHandler.rejectExcution())来处理。

      可以看下线程池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();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }

      对其入参释义如下:

    参数 描述 作用
    corelPoolSize 线程核心线程数 当一个任务提交到线程池时,线程池会创建一个线程来执行任务,即使其他的核心线程足够执行新任务,也会创建线程,直到需要执行的任务数大于核心线程数后才不再创建;如果线程池先调用了preStartAllCoreThread()方法,则会先启动所有核心线程。
    maximumPoolSize 线程池最大线程数 如果队列满了,并且已创建的线程数小于该值,则会创建新的线程执行任务。这里需要说明一点,如果使用的队列是无界队列,那么该值无用。
    keepAliveTime 存活时间 当线程池中线程超过超时时间没有新的任务进入,则停止该线程;只会停止多于核心线程数的那几个线程。
    unit 线程存活的时间单位 可以有天、小时、分钟、秒、毫秒、微妙、纳秒
    workQueue 任务队列

    用于保存等待执行任务的阻塞队列。可以选择如下几个队列:数组结构的有界队列ArrayBlockingQueue、链表结果的有界队列LinkedBlockingQueue、不存储元素的阻塞队列SynchronousQueue、一个具有优先级的无界阻塞队列PriortyBlockingQueue

    threadFactory 创建线程的工厂

    可以通过工厂给每个线程创建更有意义的名字。使用Guava提供的ThreadFactoryBuilder可以快速的给线程池里的线程创建有意义的名字,代码如下

    new ThreadFactoryBuilder().setNameFormat("aaaaaaaa").build();

    handler 饱和策略

    当队列和线程都满了,说明线程池处于饱和状态,那么必须采取一种策略来处理新提交的任务。

    AbortPolicy(默认),表示无法处理新任务时抛出异常。

    CallerRunsPolicy:只有调用者所在线程来运行

    DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务

    DiscardPolicy:不处理,直接丢弃

      上面说到,向线程池提交任务有两种方法,分别是execute()和submit(),两者的区别主要是execute()提交的是不需要有返回值的任务,而submit提交的是需要有返回值的任务,并且submit()会返回一个Furure对象,并且可以使用future.get()方法获取返回值,并且get方法会阻塞,直到有返回值。

      线程池的关闭有shutdown()和shutdownNow两个方法,他们的原理是遍历线程池中的工作线程,然后逐个调用interrupt方法来中断线程,所以无法中断的线程可能永远无法终止;但是二者也有区别,shutdownNow是将线程池的状态设置为STOP,然后尝试停止所有正在执行或者暂停的线程,并返回等待执行任务列表;而shutdown只是将线程池的状态设置成SHUTDOWN,然后中断所有没有正在执行的任务。当调用这两个方法中的任何一个后,isShutdown方法就会返回true,当所有任务都已经关闭后,调用isTerminaed方法会返回true。

      使用线程池时,需要从任务的性质(IO密集型还是CPU密集型或是混合型)、任务的优先级、任务的执行时常、任务的依赖性(是否依赖其他系统资源,如数据库连接等)来综合判断,比如说,CPU密集型,就可以就可以配置N+1个线程个数,其中N为CPU核数,如果是IO密集型,则可以配置2*N个线程数;如果是混合型的任务,可以将其拆分成IO密集型和CPU密集型,但是如果两个任务的执行时间相差较大,则没有必要进行拆分;优先级不同的任务可以使用优先级队列PriortyBlockingQueue来处理;依赖数据出等其它资源的线程池,比如说依赖数据库,那么就可以加大线程数量,因为在等待sql执行的时候,线程是处于空闲状态;另外,最好使用有界队列,因为有界队列可以增加系统的稳定性和预警能力。

      对于线程的监控,还有以下几个方法可以使用:

    方法 描述
    taskCount() 线程池需要执行的任务数量
    completedTskCount 线程池运行过程中已经执行完毕的任务数量
    IarestPoolSize 线程池中曾经创建过的最大线程数
    getPoolSize 线程池的线程数量
    getActiveCount 获取活动的线程数

    Exector框架:

      在java中,是用线程来异步执行任务,java线程的创建与销毁需要一定的开销。如果我们为每一个任务创建一个线程的话,这些线程就会消耗大量的计算资源,会使处于高负荷的应用崩溃。

      在HotSpot虚拟机中,JAVA线程被一对一的映射为本地操作系统线程。JAVA线程启动时会创建一个本地操作系统线程,当该JAVA线程终止时,这个操作系统线程也会被收回,操作系统会调用多余线程并将他们分配给可用的CPU。

      

      Executor框架的两级调度模型如上图所示,应用程序通过Executor控制上层的调度,而下层的调用由操作系统内核控制,将线程映射到硬件处理器上,下层的调用不受应用程序的控制。

      关于Executor的组成部分如下所示:

    元素 描述
    任务 包括被执行任务需要实现的接口Runnable和Callable接口
    任务的执行 包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor接口有两个关键的实现类实现了ExecutorService接口:ThreadPoolExecutor和ScheduledThreadPoolExecutor
    异步计算的结果 包括接口Future和实现Future接口的FurureTask类

      Executor框架使用示意图如下:

      

       如上图所示,主线程首先创建实现Runnable或Callable接口的任务对象,然后把任务对象提交给ExecutorService执行,如果使用的是submit提交,执行完毕后将返回一个实现Future接口的对象,最后,主线程可以执行FutureTask.get()方法来获取返回值;主线程也可以调用FutureTask.cancel()方法来取消此任务的执行。

      Executor框架的成员如下:

    成员 描述 子类 描述
    ThreadPoolExecutor

    通常使用工厂类Executors来创建,Executors可以创建三种类型的ThreadPoolExecutor

    固定线程数的FixedThreadPool

    适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的应用。
    单一线程的SingleThreadPool 适用于需要保证顺序的执行各个任务,并且在任意时间点都不会有多个线程活动的场景。
    根据需要创建线程的CacheThreadPool 这是一个无界的线程池,适用于执行很多短期异步任务的小程序,或者是负载比较轻的服务器。
    ScheduledThreadPoolExecutor 通常使用工厂类Executors创建,Executors可以创建两种类型的ScheduledThreadPoolExecutor 包含若干线程的ScheduledThreadPoolExecutor 适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程数量的应用场景。
    只包含一个线程的SingleThreadScheduledExecutor                         适用于需要单个后台线程执行周期任务,同时需要保证顺序的执行各个任务的场景。                                               
    ForkJoinsPool

    newWorkStealingPool适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中 

       
    Future Future接口和实现了该接口的FutureTask类来表示异步计算的结果    
    Runnable和Callable接口

    Runnable和Callable接口的实现类,都可以被ThreadPoolExecutor、ScheduledThreadPool、ForkJoinThred执行;除了可以自己实现Callable接口外,我们还可以使用工厂类Executors来把一个Runnable包装成一个Callable

       

    ThreadPoolExecutor详解:

      1、ThreadPoolExecutor

       (1)FixedThreadPool

        构造函数如下:

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

      构造函数中,核心线程数和最大线程数一致,keepAliveTime为0,队列使用的是无界阻塞队列LinkedBlockingQueue(最大值是Integer.MAX_VALUE);

      核心线程数和最大线程数保持一致,表明:如果队列满了之后,不会再创建新的线程;

      keepAliveTime为0,表明:如果运行线程数大于核心线程数时,如果线程执行完毕,空闲线程立刻被终止;

      使用无界阻塞队列,表明:当运行线程到达核心线程数时,不会再创建线程,只会将任务加入阻塞队列;因此最大线程数参数无效;因此keepAliveTime参数无效;且不会拒绝任务(既不会执行包和策略)

       (2)SingleThreadExecutor

        构造函数如下:

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

      构造函数中,核心线程数和最大线程数均为1,keepAliveTime为0,队列使用的是无界阻塞队列LinkedBlockingQueue(最大值是Integer.MAX_VALUE)

      除了固定了核心线程数和最大线程数为1外,其余的参数均与FixedThreadPool一致,那么就是只有一个线程会反复循环从阻塞队列中获取任务执行

       (3)CacheThreadPool

        构造函数如下:

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

      构造函数中,核心线程数为0,最大线程数为Integer.MAX_VALUE,意味着无界,keepAliveTime为60秒,阻塞队列使用没有存储空间的SynchronousQueue,核心线程数为0,最大线程数为无界,表明:只要队列满了,就会创建新的线程放入线程池,使用没有存储空间的SynchronousQueue表明:线程提交的速度高于线程被消费的速度,那么线程会被不断的创建,最终会因为线程创建过多而耗尽CPU和内存资源.

      2、ScheduledThreadPoolExecutor

        ScheduledThreadPoolExecutor的运行机制如下:

        (1)当调用ScheduledTreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask

        (2)线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

      ScheduledFutureTask主要包含以下三个成员变量

    成员变量 描述
    long time 表示这个任务要被执行的时间
    long sequenceNumber 表示该任务被添加到ScheduledThreadPoolExecutor中的序号
    long period 表示任务执行的间隔周期

      DelayQueue封装了一个PriorityQueue,当添加任务时,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序,time最小的在最前面(最先被执行),如果time一致,就比较sequenceNumber,sequenceNumber小的排在前面。

      当线程执行任务时,先从DelayQueue队列中获取已经到期的任务(time大于当前时间),然后执行该任务,执行完毕后,根据任务的执行周期,修改任务下次的执行时间time,并重新将任务添加到DelayQueue

    FutureTask详解:

      Future接口和实现该接口的FutureTask类,代表异步计算的结果。

      FutureTask的使用方法是将其交给Executor执行,也可以通过ExecutorService.submit()方法返回一个FutureTask,然后执行FutureTask.get()方法或FutureTask.cancel()方法。

      FutureTask有三种状态:未启动(FutureTask.run()没有被执行之前的状态)、已启动(FutureTask.run()方法执行过程中)、已完成(FutureTask.run()方法执行完成或被取消),这三种状态的流转如下图所示:

      

       FutureTask的实现是基于AQS(AbstractQueuedSynchrouizer)来实现的,之前已经说过,每一个基于AQS实现的同步器都会至少包含一个acquire操作和至少一个release操作。AQS被作为模板方法模式的基础类提供给FutureTask的内部子类Sync实现了AQS的tryAcquireShared(int)方法和tryReleaseShared(int)方法,Sync通过这两个方法来检查和更新同步状态。

      FutureTask涉及示意图如下图所示:

      

       如上图所示,FutureTask.get()方法会调用AQS的acquireSharedInterruptibly(int)方法,该方法首先会回调在子类Sync中的tryAcquireShared()方法来判断acquire操作是否成功(state状态状态是否为执行完成RAN或取消状态CANCELED&runner不为null),如果成功则get()方法立刻返回,如果失败则到线程等待队列中去等待其他线程执行release操作;当其他线程执行release操作(比如FutureTask.run()或FutureTask.cancel())唤醒当前线程后,当前线程再次执行tryAcquireShared()将返回正值1,当前线程将离开线程等待队列并唤醒它的后继线程。

      Run方法执行过程如下:

      执行在构造函数中指定的任务(Callable.call()),然后以原子方式来更新状态(调用AQS.compareAndSetState(int expect, int update),设置state的状态为RAN),如果这个原子操作成功,就设置代表计算结果的变量result的值为Callable.call()的返回值,然后调用AQS.release(int)。

      AQS.rease首先会调用子类Sync中实现的tryReleaseShared方法来执行release操作(设置运行任务的线程为null,然后返回false),然后唤醒等待队列中的第一个线程。

      最后调用Future.done()方法。

  • 相关阅读:
    lr参数化取值规则总结
    LoadRunner中InvokeMethod failure: 外部组件发生异常解决办法
    Error: Exception was raised when calling event-notify Vuser function in extension parameng.dll: System Exceptions: EXCEPTION_ACCESS_VIOLATION
    loadrunner场景执行出现:Failed to Initialize. Reason: TimeOut
    loadrunner场景中按scenario和group执行的区别
    lr关联抓有相同左右边界的动态值
    lr中用strtok函数分割字符串
    LoadRunner11设置场景百分比模式完成多台客户端压力测试
    lr如何屏蔽全局变量的影响
    Oracle in与exist条件分析
  • 原文地址:https://www.cnblogs.com/wk-missQ1/p/13204381.html
Copyright © 2020-2023  润新知