Executor框架

1.Executor概述

1.1 概述

Executor框架是线程池的实现,通常Java多线程将应用程序的任务分解若干个,由Executor进行任务与线程关系的映射,再通过底层通过操作系统的内核将这些线程进行与硬件处理器的映射,所以在底层的任务调度不受应用程序的控制。Executor由三个部分组成:

  • 任务
  • 执行者
  • 执行结果

1.2 主要类与接口

主要关注ThreadPoolExecutor和ScheduleThreadPoolExecutor两个类。

  • ThreadPoolExecutor:线程池的核心实现类,任务的执行器。
  • ScheduleThreadPoolExecutor:线程池的实现类,任务的执行器,与ThreadPoolExecutor不同的是可以实现延迟或定期执行任务。

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

  • Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行

Executor是使用流程如下:

主线程首先要创建实现Runnable或者Callable接口的任务对象。

然后可以把Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnablecommand));或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callabletask))。

如果执行ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象。由于FutureTask实现了Runnable,也可以创建FutureTask,然后直接交给ExecutorService执行。最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。

主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

2.Executor详解

2.1 ThreadPoolExecutor

ThreadPoolExecutor是Executor最核心的实现类,与其相关的属性主要有:

  • corePool:核心线程池的大小。
  • maximumPool:最大线程池的大小。
  • BlockingQueue:用来暂时保存任务的工作队列。
  • RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。

它可以创建三种线程池,分别是:

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool
  • WorkStealingPool
2.1.1 FixedThreadPool

即可重用固定线程数的线程池。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • corePoolSize和maximumPoolSize都被设置为nThreads,即线程数量。
  • 阻塞队列使用的是LinkedBlockingQueue
  • 当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。

execute运行示意:

  1. 如果当前运行的线程数少于corePoolSize,线程池则会创建新的线程来处理任务。
  2. 完成线程池的预热后,把任务提交给LinkedBlockingQueue来等待执行。
  3. LinkedBlockingQueue为无界队列,(最大为Integer.MAX_VALUE)。
  4. 可以看出FixedThreadPool线程池中,最大的线程数量不会多于corePoolSize,且maximumPoolSize无效。
  5. 为了保证corePoolSize的作用,keepAliveTime参数也是无效的,默认为0L。
2.1.2 SingleThreadExecutor

SingleThreadExecutor顾名思义,只有一个线程的worker的Executor。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • corePoolSize和maximumPoolSize都被设置为nThreads,即只有一个线程。
  • 同FixedThreadPool一致,阻塞队列也使用的是LinkedBlockingQueue

execute运行示意:

不多说了,很好理解。

2.1.3 CachedThreadPool

CachedThreadPool是一个根据需要创建新线程的线程池。

    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
  • corePoolSize为0,即corePool为空,不会有线程池预热的过程。
  • maximumPoolSize为Integer.MAX_VALUE,因此maximumPool是无界的。
  • keepAliveTime设置为60s。
  • 阻塞队列使用的是SynchronousQueue。可以理解为提交一个任务就会创建线程来执行(如果提交任务的速度大于任务处理的速度),所以可能会造成内存资源消耗严重的问题。

execute运行示意:

  1. 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,我理解的就是说在SynchronousQueue中有空闲的线程可以供offer方法使用。
  2. 当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1)将失败。此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。
  3. 在步骤2)中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程推送到SynchronousQueue中,并且最多在SynchronousQueue中等待60秒钟。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1)),那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。
2.1.4 WorkStealingPool

即抢占式线程池,JDK1.8加入。

    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
  • ForkJoinPool 实现任务窃取算法。
  • 理解为哪个线程抢占到任务,就由哪个线程来执行。
  • 无法保证任务执行的有序性。
  • 由于能够合理的使用CPU进行对任务操作(并行操作),所以适用于一些耗时的操作。
2.2 ScheduledThreadPoolExecutor
2.2.1 ScheduledThreadPoolExecutor原理

同ThreadPoolExecutor相比,ScheduledThreadPoolExecutor最重要的地方在于他实现了给定的延迟之后运行任务,或者定期的执行任务 。

  1. 使用了DelayQueue作为任务队列,且DelayQueue为无界队列,所以maximumPoolSize参数将无效。
  2. 当scheduleAtFixedRate()方法或scheduleWithFixedDelay()方法会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了
    RunnableScheduledFutur接口的ScheduledFutureTask。
  3. 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
2.2.2 ScheduledThreadPoolExecutor实现

DelayQueue队列用于保存待执行的任务(ScheduledFutureTask),其中ScheduledFutureTask主要包含三个变量:

  • time:表示任务将要被执行的具体时间。
  • sequenceNumber:表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
  • period:表示任务执行的间隔周期。

DelayQueue队列会把time小的任务优先处理,如果两个任务的time相同,则比较sequenceNumber和period。即优先处理先提交的任务。

主体执行流程:

  1. 通过DelayQueue.take() 方法从DelayQueue中获取到期(即time大于等于当前时间)的任务。
  2. 由线程1进行任务处理。
  3. 处理完成后修改time属性。
  4. 将ScheduledFutureTask任务放到DelayQueue队列中,等待下次执行。
  5. 在获取任务和添加任务的过程中需要进行加锁操作
2.3 FutureTask

FutureTask和Future代表异步任务执行的结果。根据FutureTask.run()方法被执行的时机,FutureTask可以处于下面3种状态:

  • 未启动
  • 已启动
  • 已完成(包括FutureTask.run()正常结束,取消结束或是异常结束)

在三个状态的切换过程中主要有两个方法贯穿其中

  • get():获取任务执行结果
  • cancel():中断线程

两个方法的示意:

在JDK1.8之前,FutureTask的实现基于AbstractQueuedSynchronizer(即为AQS)。而JDK1.8之后,则不再依赖AQS来实现,而是通过一个volatile变量state以及CAS操作来实现。即:

     /**
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    private volatile int state;
    private static final int NEW          = 0;//初始状态
    private static final int COMPLETING   = 1;//
    private static final int NORMAL       = 2;//任务正常完成,结果被set
    private static final int EXCEPTIONAL  = 3;//任务抛出异常
    private static final int CANCELLED    = 4;//任务已被取消
    private static final int INTERRUPTING = 5;//线程中断状态被设置ture,但线程未响应中断
    private static final int INTERRUPTED  = 6;//线程已被中断

接下来看下get()方法:

    /**
     * 通过状态的比较来决定等待还是获取执行结果
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

阻塞方法:

	/**
     * 等待任务完成,正常结束或是取消,异常结束
     *
     * @param timed true if use timed waits
     * @param nanos time to wait, if timed
     * @return state upon completion
     */
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
        	//如果线程已经中断,移除等待节点
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
            	//初始化当前节点
                q = new WaitNode();
            else if (!queued)
            	//WaitNode组成一个单向的链表
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }

Cancel取消方法:

public boolean cancel(boolean mayInterruptIfRunning) {
		//任务是取消还是中断?
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    
        	// 中断线程,修改状态,唤醒其他等待的线程
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

源码我就不分析,我比较菜。而且我觉得接下来还是多实战一下比较好,后期有机会在分析源码。

参考:《Java并发编程的艺术》

posted @ 2020-08-26 15:43  水晶马桶盖  阅读(164)  评论(0)    收藏  举报