JUC 소스 코드 분석 - 스레드 풀 부 (B) FutureTask

JUC 소스 코드 분석 - 스레드 풀 부 (B) FutureTask

작업이 그들에 의해 완료된 후 JDK5가 호출 가능하고 미래의 인터페이스 나중에 제공, 당신은 작업의 결과를 얻을 수 있습니다. 소스 코드의 관점에서 여기 특정 구현 원리.

1. 소개 인터페이스

FutureTask 클래스 구조

1.1 호출 인터페이스

다음과 같이 호출 인터페이스를 달성하기 위해 필요한 작업의 경우, 호출 인터페이스 정의는 다음과 같습니다

public interface Callable<V> {
    V call() throws Exception;
}

범용 인터페이스를 볼 수 호출 일반 V는 (메소드의 리턴 형)를 호출한다. 인터페이스 등의 호출과의 Runnable 인터페이스는 다른 스레드에 의해 수행 될 수 있지만, 앞서 말했듯이, 반환하지 Runnable를 데이터는 예외를 던질 수 없습니다.

1.2 미래의 인터페이스

미래의 인터페이스는 비동기 계산의 결과를 나타냅니다, 당신은 방법에 의해 수행 비동기 계산은 미래의 인터페이스 제공 여부를 확인하거나 결과를 기다려 결과를 얻을뿐만 아니라 실행을 취소 할 수 있습니다. 다음과 같이 미래의 인터페이스를 정의한다 :

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • 는 () 취소 : 비동기 작업의 실행을 취소하는 데 사용됩니다. 비동기 작업이 완료 또는 취소 된, 또는 어떤 이유로 취소 할 수없는 경우는 false를 돌려줍니다. 작업이 실행되지 않은 경우, 그것은 사실과 비동기 작업이 실행되지 않습니다 반환합니다. 작업이 이미 시작했지만 mayInterruptIfRunning에 해당하는 경우 아직 완전한를 구현 한 경우 mayInterruptIfRunning이 false 인 경우, 즉시 사실 반환하고 작업 실행 스레드를 방해하지 않고, 작업을 수행하고 true를 반환하는 스레드를 중단됩니다.
  • isCanceled () : 그렇지 않은 경우는 false를 돌려, 작업이 종료 (정상 실행 또는 실행 비정상 종료의 끝 부분)가 true를 돌려 전에 취소 된 경우 작업이 취소되어 있는지 여부를 결정합니다.
  • 의 isDone는 () : 작업이 완료되었는지 여부를, 그렇지 않은 경우는 false를 돌려 완료된 경우는 true를 반환 결정합니다. 그 참고 : 예외가 태스크의 실행 중에 발생, 작업이 완료 된 작업에 속하지도 취소되었다가 true를 돌려줍니다.
  • () GET : 작업이 아직 작업 실행이 완료 될 때까지 기다리는 차단 완료되지 않은 경우, 작업 실행 결과를 가져옵니다. 프로세스가 예외 : InterruptedException 예외를 발생합니다 대기 블록을 중단되면 취소 작업이 CancellationException 예외가 던져 질 경우 예외가 작업을 실행하는 동안 발생하는 경우는 ExecutionException 예외를 발생합니다.
  • GET (롱 타임 아웃, TIMEUNIT 단위) : GET 밴드 타임 아웃 () 버전은 프로세스가 슈퍼 리그를 기다리고 차단 된 경우는 TimeoutException 예외가 발생합니다.

1.3 FutureTask

미래 단순한 인터페이스가 직접 객체를 생성 할 수 없습니다, 그것은 FutureTask 미래 구현 클래스, 구조 FutureTask는 다음입니다 :

FutureTask -> FutureRunnable -> Future/Runnable 

FutureTask RunnableFuture 인터페이스를 달성하는 것을 알 수 있으며,이 인터페이스는 매우 아니라 직접 실행의 Runnable FutureTask 스레드로, 또한 호출 가능 미래 같은 결과를 얻기 위해 사용될 수 RunnableFuture의 Runnable 인터페이스 및 미래의 인터페이스를 확장한다.

2. FutureTask 소스 코드 분석

2.1 FutureTask 수명주기

private volatile int state;
private static final int NEW          = 0;  // 初始状态
private static final int COMPLETING   = 1;  // 任务已经执行完(正常或者异常),准备赋值结果
private static final int NORMAL       = 2;  // 任务已经正常执行完,并已将任务返回值赋值到结果
private static final int EXCEPTIONAL  = 3;  // 任务执行失败,并将异常赋值到结果
private static final int CANCELLED    = 4;  // 取消
private static final int INTERRUPTING = 5;  // 准备尝试中断执行任务的线程
private static final int INTERRUPTED  = 6;  // 对执行任务的线程进行中断(未必中断到)

도 FutureTask 상태 변화 :

FutureTask 상태

  • NEW는 : 완료되지 않았 수행 할 수있는 새로운 작업이나 작업을 나타냅니다. 이는 초기 상태입니다.
  • 완료 (예외가 발생했을 경우, 예외가 저장에 대한 이유를 작업 실행 결과를 저장하는 데 사용되는 결과 필드) 예외가 작업을 수행하는 데 발생하면 작업이 완료되거나 실행 된,하지만 작업 실행 결과 나 틀에 얽매이지 않는 결과가 현장에 저장되지 않은 시간 , 상태는 완료하기 NEW 변경됩니다. 그러나 상태가 상대적으로 짧은 것 이번에는 중간 상태입니다.
  • 정상 (NORMAL) : 작업이 실행하고 실행 결과가 결과 필드에 저장하는 작업을 완료, 상태가 정상으로 완료 전환됩니다. 이 최종 상태입니다.
  • 예외적 : 태스크 실행 예외가 발생하고, 이상 원인이 결과 필드에 저장 한 후, 상태가 예외적으로 완료 전환됩니다. 이 최종 상태입니다.
  • 취소됨 : 작업이 시작되지 않았거나 시작되었지만 아직 실행 시간을 완료하지, 사용자가이 작업을 취소 (거짓) 메소드를 취소하고 작업 실행 스레드를 방해하지 않는 호출 상태에서이 시간이 취소됨 NEW 상태로 전환 될 것이다. 이 최종 상태입니다.
  • 중단 : 작업이 시작되지 않은 아직 수행되지 않았거나 실행이 완료되면, 사용자의 호출이 작업을 취소하고 작업 실행 스레드를 중단 (사실) 메소드를 취소하지만 스레드가 작업을 중단하기 전에, 상태가 NEW에서 중단으로 변환 될 것입니다 실행되지 않았습니다 . 이 중간 상태이다.
  • INTERRUPTED () 인터럽트를 호출 작업 실행 스레드 상태가 방해로 중단에서 변환 된 후 중단 :. 이 최종 상태입니다.

일시적으로 완료하고 중간 상태를 차단하면서 NEW는 시작 상태 뛰어난 NORMAL, 이러한 상태의 종료, 취소 중단 상태로 볼 수있다. 왜 완료하고이 개 중간 상태를 중단 소개? 완료 함 -> 한 INTERRUPTED t.interrupt 과정 -을 중앙> 중단 결과 = V는 중단, 할당 과정이있다. 이 과정은 다른 스레드 간섭 있도록하는 것입니다. (개인의 이해)

한가지 유의할 점이다 모든 국가가 완료 함보다 큰 값은 작업이 실행 (취소 이상 수행 할 정상적으로 실행 작업, 작업 또는 작업)을 완료되었음을 나타냅니다 있습니다.

2.2 내부 구조

// 内部封装的 Callable 对象。如果是 Runnable 则会通过 Executors#callable 封装成 Callable
private Callable<V> callable;
private Object outcome;             // 用于保存计算结果或者异常信息。
private volatile Thread runner;     // 用来运行 Callable 的线程
private volatile WaitNode waiters;  // FutureTask中用了 Trieber Stack 来保存等待的线程
                                    // 这个队列使用 Treiber stack(可以理解为基于 CAS 的无锁的栈,先进后出)

와 FutureTask Treiber에 스택 / 다시 이상 취소 / 작업, 작업 완료 FutureTask 완료되기를 기다립니다을 유지 finishCompletion 후크 방식에서 대기 스레드 스택을 깨어 있습니다.

생성자 2.3

보이는 FutureTask의 생성자로 시작 :

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

볼 수있는, 웨이터 초기 값은, 초기 상태 NEW null입니다.

스레드와 일반 시작 다음 작업 스레드는 작업을 취소, 작업 실행의 결과를 얻기 위해 작업 실행 스레드 (일반적으로 스레드 풀)에서 작업 실행 상태를 검토 할 수 있습니다 스레드를 시작, 동일한 스레드의 작업을 수행 할 수있는 시간이되지 않습니다 일반적으로 작업을 수행 할 수 등을 작업 한 후 이러한 작업을 분석 할 수 있습니다.

2.4 작업 실행 스레드

2.4.1 핵심 방법 실행

executor.submit (작업)이 스레드 풀 작업에 제출 될 것입니다 후, 의심의 여지 FutureTask는 실행 방법은 설정 결과 값을 완료 실행할 때, 원래의 포장 방법을 실행하지 않으며, 모든 대기 스레드를 깨울 것입니다.

/**
 * run 方法执行有两个条件:1. state=NEW; 2. runner=null
 * 1. 执行前 state=NEW & runner=null
 * 2. 执行中 state=NEW & runner=Thread.currentThread()
 * 3. 执行后 state!=NEW & runner=null,根据是否有异常执行 set(result) 或 setException(ex),
 *    set/setException 都会更新 state 状态,之后线程的状态就不是 NEW
 * 因此,多个线程同时调用 run 方法的情况 callable 也只会执行一次
 */
public void run() {
    // 1. state为 NEW 且对 runner 变量 CAS 成功。 对 state 的判断写在前面,是一种优化。 
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            // 2. 是否成功运行的标志位,而不是把 set 方法写在 try 中,是为了不想捕获 set 的异常。
            //    比如:子类覆写 FutureTask#done 方法(set->finishCompletion >done) 抛出异常,
            //    然而实际上提交的任务是有正常的结果的。
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // 3. 如果A/B两个线程同时执行到步骤①,当A线程重新将runner设置为 null 时,B线程是否可能会重新执行呢?
        //    实际上A线程一旦已经调用 set/setException 方法,整个流程已经结束了,所以不可能重复执行
        runner = null;

        // 4. 等待调用 cancel(true) 的线程完成中断,防止中断操作逃逸出 run 或者 runAndReset 方法
        //    以至于线程在执行后续操作时被中断
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

요약 : RUN 방법은 포장의 층이 될 호출하고, 실행 한 후 결과를 반환이 완료, 모든 대기 스레드를 깨워.

  1. 작업의 현재 상태가 다음 작업 NEW하지 않거나 수행 된, 또는 직접 수익을 취소되었습니다 결정합니다.
  2. 상태가 다음 CAS 러너 필드에 저장된 작업 실행 스레드 참조 될 것입니다 안전하지 않은 클래스 NEW 다음 경우가 하나이며, 하나의 스레드 만이 배타적 잠금에 해당하는 성공이 될 수 있습니다.
  3. 작업 실행이 완료되면, 호출 설정하거나 setException 방법 및 모든 대기 스레드를 깨워.

2.4.2 설정 결과 집합 / setException

// 设置返回结果,唤醒等待线程。思考为什么需要 COMPLETING 这个中间状态
protected void set(V v) {
    // 可能任务已经取消,state 的状态就不再是 NEW
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

이전의 분석에 따르면, 정상적으로 실행 작업을 수행하거나, 작업을 취소하거나 비정상 작업이며, 마지막 finishCompletion () 메소드를 호출할지 여부를 다음과 같은 방법이 구현된다 :

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        // 1. 必须将栈顶 CAS 为 null,否则重读栈顶并重试。
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            // 2. 遍历并唤醒栈中节点对应的线程。
            for (;;) {
                Thread t = q.thread;        // 从头节点开始唤醒所有的等待线程
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);  // 唤醒等待线程
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }

    // 3. ExecutorCompletionService#QueueingFuture 中把结果加到阻塞队列里。
    //    CompletionService 谁用谁知道,奥秘全在这。
    done();     // 子类覆盖

    // 4. callable 置为 null 主要为了减少内存开销,更多可以了解 JVM memory footprint 相关资料
    callable = null;        // to reduce footprint
}

이 방법의 구현은 웨이터, 연결리스트를 탐색, 스레드가 다음 노드를 깨어 호출 빈, 비교적 간단하다. 스레드는 순환의 새로운 라운드를 실시 다음 () 각을 잠 awaitDone () 메소드의 LockSupport.park을 차단에서 반환 될 것입니다. 루프의 새로운 라운드가 결과를 반환합니다 또는, 더 정확하게, 작업의 반환 상태입니다.

2.5 작업 호출 스레드

2.5.1 결과를 얻을 획득

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)    // NEW 或 COMPLETING
        s = awaitDone(false, 0L);
    return report(s);
}

public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException(); // 超时了,任务还未执行结束
    return report(s);
}

요약 : 작업이 현재 스레드의 끝에서 실행되지 않은 경우 통화가 끝이 여기 완료 정상 작업, 비정상적인 작업 실행을 포함 awaitDone 차단 작업이 취소되었습니다. 작업 실행 (통화 보고서를 종료하면) 결과를 반환합니다.

// report 根据任务最终的状态,返回结果或抛出异常
private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)    // 1. 正常执行完计算任务
        return (V)x;
    if (s >= CANCELLED) // 2. 取消
        throw new CancellationException();
    throw new ExecutionException((Throwable)x); // 3. 异常
}

임무의 종료에 대한 2.5.2 대기 awaitDone

당신이 얻을를 호출 할 때 () 작업의 결과를 얻을 수 있지만, 작업이 실행 시간을 완료하지 않은, 호출 스레드는 awaitDone을 ​​() 메소드는 다음과 같이 정의되는, 대기 차단 호출합니다 :

// 阻塞等待线程执行完成(正常、异常、取消)后返回
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        // 1. 调用 get 的线程是否被其他线程中断
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        // 2. 任务已经执行完成,无论是正常、异常、取消
        if (s > COMPLETING) {   // 已经执行完成
            if (q != null)      // help GC
                q.thread = null;
            return s;
        // 3. 结果正在赋值,让出 CPU 等待
        } else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        // 4. 初始化节点,重试一次循环
        else if (q == null)
            q = new WaitNode();
        // 5. queued 记录是否已经入栈,此处准备将节点压栈
        //    节点入队失败,自旋直至成功
        else if (!queued)
            // 这是 Treiber Stack算法入栈的逻辑。 Treiber Stack 是一个基于 CAS 的无锁并发栈实现
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        // 6. 挂起线程
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);    // 超时直接删除节点后返回
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this); // 只有任务取消(cancel)和任务执行结束(run),才能唤醒线程
                                    // 此时再一次for循环时state的状态肯定大于COMPLETING
    }
}

요약 : 다음과 같은 상황을 awaitDone :

  1. 스레드를 얻을 수있는 호출 여부 다른 스레드, 직접 던져 예외 : InterruptedException에 의해 중단됩니다.
  2. 임무의 반환 결과의 끝, 여기에 결과가 작업의 상태입니다. 한은이 종료 작업을 완료보다 큰 것을 의미한다.
  3. 팀의 스핀 실에 들어가 전화를 걸어 스레드는 계기를 기다리고 중단됩니다. 만 finishCompletion 스레드를 깨워하지만,이 방법 만 작업 취소 또는 최종 실행 작업 실행 메소드가 호출됩니다 취소 할 수 있습니다. 물론, 작업 시간 제한, 직접 반환하는 경우,이 상태는 NEW 할 수있다.

2.5.3 삭제 노드 removeWaiter

/**
 * 清理用于保存等待线程栈里的无效节点,所谓节点无效就是内部的 thread 为 null(类比 ThreadLocalMap)
 *   
 * 一般有以下几种情况:
 * 1. 节点调用 get 超时。
 * 2. 节点调用 get 中断。
 * 3. 节点调用 get 拿到 task 的状态值(> COMPLETING)。
 *
 * 此方法干了两件事情:
 * 1. 置标记参数 node 的 thread 为 null
 * 2. 清理栈中的无效节点
 *
 * 如果在遍历过程中发现有竞争则重新遍历栈。
 */
private void removeWaiter(WaitNode node) {
    if (node != null) {
        node.thread = null;
        retry:
        for (;;) {          // restart on removeWaiter race
            // pre(前继节点) -> current(当前节点) -> next(后继节点),对应下面的 pre -> q -> s
            // 1.1 q.thread!=null 则更新 pre 节点继续遍历
            // 1.2 pre!=null && q.thread==null 时 current 不时头节点,直接删除 current
            // 1.3 pre==null && q.thread==null 时 current 是头节点,更新头节点为 next
            for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                s = q.next;
                // 1.1 保存当前节点 q 的前继节点 pre
                if (q.thread != null)
                    pred = q;
                // 1.2 q 节点已经失效,此时根据 q 是否是头节点分两种情况
                //     q 不是头节点,直接删除,为什么不需要原子性操作?
                else if (pred != null) {
                    pred.next = s;              // 踢除当前节点 q
                    if (pred.thread == null)    // check for race
                        continue retry;
                // 1.3 q 是头节点且失效了,原子性更新头节点
                } else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
                    continue retry;
            }
            break;
        }
    }
}

2.5.4 작업이 취소 취소

/**
 * mayInterruptIfRunning:false 时不允许在线程运行时中断,true 时允许运行时中断线程,但不保证一定会中断线程。
 * 1. true 时,将状态修改成 INTERRUPTING,执行 thread.interrupt()
 * 2. false 时,将状态修改成 CANCELLED
 */
public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();  // interrupt 不一定能中断线程
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion(); // 唤醒所有等待线程
    }
    return true;
}

요약 : 취소 방법은 다음과 같은 일을 할 것입니다 :

  1. 작업의 상태를 수정합니다. 이후 mayInterruptIfRunning라는 매개 변수 이름은 다음 작업이 인터럽트에 의해 중지하지 못할 수도 있습니다. 비즈니스 응답 스레드가 중단 여부에 따라 달라집니다. 조건이 true로 변경하는 동안 그런 다음 코드로 작업을 중단 할 수 단순히 취소 호출합니다.

    // !Thread.interrupted() 改为 true 后无法中断任务
    Future<?> future = executorService.submit(() -> { 
        while (!Thread.interrupted()) System.out.println("1"); 
    });
    future.cancel(true);
  2. 모닝콜 finishCompletion 모든 작업 스레드의 결과를 기다리고.

참조 :

  1. 이 문서에서는 "심층 연구 FutureTask를"을 재현 : http://www.importnew.com/25286.html
  2. "FutureTask 소스 해석" https://www.cnblogs.com/micrari/p/7374513.html

매일 조금 기록의 의도. 아마도 내용이 중요하지 않지만 습관이 매우 중요합니다!

추천

출처www.cnblogs.com/binarylei/p/10958885.html