심층 분석의 Sike 자바 스레드 풀 스레드 시리즈 - 다음 작업 실행 과정

threadpool_futuretask

(수평 스크린 휴대 전화가 볼 수있는 소스 코드가 더 편리합니다)


참고 : 자바 소스 코드 분석 섹션을 특별한 지시가 java8 버전을 기반으로하지 않은 경우.

참고 : 소스 스레드 풀 섹션을 특별한 지시가있는 ThreadPoolExecutor 클래스를 참조하지 않는 경우.

간략한 소개

앞서 우리가 수영장에서 일반적인 작업 과정 스레드를 수행하기 위해 함께 배웠지 만 실제로는 스레드 풀에서 수행하는 작업의 결과를 얻을하는 데 사용할 수 있습니다, 작업이 미래 (미래 작업) 호출되는 작업이, 그것을 달성하는 방법은?

이 장을 이해하는 데 도움이 될 것입니다, 그리고 코드의 다른 쪽이 장 형제 통을 읽기 전에 권장 상대적 처음 방문 전에 배우고, 상대적으로 짧다 "자신에게 (계속) 스레드 풀을 쓰기의 Sike 자바 스레드 시리즈", 쓴 쉽게.

문제

(1) 스레드 풀 다음 작업은 그것을 구현하는 방법은?

(2) 무엇보다 디자인 패턴 우리가 배울 수?

(3) 다른 프레임 워크를 배우고 미래에 우리를 도와있을 것입니다?

밤나무로

우리는 장의 내용을 설명하기 위해, 예를 들어 시작합니다.

우리는 스레드 풀을 정의하고 미래의 어떤 시점에서, 다섯 작업 0,1,2,3,4를 반환 다섯 개 가지 작업을 제출하는 데 사용하고, 우리는 자신의 리턴 값에 대한 액세스 권한이 누적 작업을 할 .

public class ThreadPoolTest02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 新建一个固定5个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        List<Future<Integer>> futureList = new ArrayList<>();
        // 提交5个任务,分别返回0、1、2、3、4
        for (int i = 0; i < 5; i++) {
            int num = i;

            // 任务执行的结果用Future包装
            Future<Integer> future = threadPool.submit(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("return: " + num);
                // 返回值
                return num;
            });

            // 把future添加到list中
            futureList.add(future);
        }

        // 任务全部提交完再从future中get返回值,并做累加
        int sum = 0;
        for (Future<Integer> future : futureList) {
            sum += future.get();
        }

        System.out.println("sum=" + sum);
    }
}

여기서 우리는 두 가지 질문을 고려 :

(1) 일반적인 작업을 사용할 수있는 경우, 어떻게 얼마나 많은 시간에 대해, 쓰기?

당신이 일반 작업을 사용하는 경우 총 시간은 1 초보다 조금 더 아마 다음 작업을 축적 작업에 투입,하지만 너무 잘 (최종판) 작성. 그러나, 이것은 단점이있다 그 자체가 서로 연결하고 번성 피곤하면 다시 변경뿐만 아니라 작업의 내용을 수정하는 작업과 작업의 축적 된 내용.

(2) 얼마나 많은 시간에 대한 for 루프로하는 Future.get ()가 존재하는 경우?

우리가 먼저이 질문에 대답 소스 코드 분석을 보지 보자.

() 메소드를 제출

방법을 제출,이 작업의 값을 반환하는 방법을 가지고 제출, 내부 (FutureTask) 패키지에 대한 다음 다음에 실행할 작업 ()을 수행하고, 마지막으로 다음 작업 자체를 반환합니다.

public <T> Future<T> submit(Callable<T> task) {
    // 非空检测
    if (task == null) throw new NullPointerException();
    // 包装成FutureTask
    RunnableFuture<T> ftask = newTaskFor(task);
    // 交给execute()方法去执行
    execute(ftask);
    // 返回futureTask
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    // 将普通任务包装成FutureTask
    return new FutureTask<T>(callable);
}

여기에 디자인은 사실,이 두 가지 방법이 AbstractExecutorService에서 템플릿을 사용하는 방법이 추상 클래스를 완료, 매우 영리하다.

의는 상속 시스템 FutureTask 살펴 보자 :

threadpool_futuretask

FutureTask는 RunnableFuture 인터페이스 및 조합의 Runnable 인터페이스와 미래 인터페이스를 RunnableFuture 인터페이스 할 수있는 능력을 실현하고, 미래의 인터페이스는 작업의 반환 값을 얻을 수있는 기능을 제공합니다.

문제 : (가) 제출 () 대신 RunnableFuture 인터페이스 또는 클래스 FutureTask 그 방법은 왜 미래의 인터페이스를 반환?

A :이 외부 발신자가 바로 GET () 기능 (미래 인터페이스)를 노출 할에 대한) 결과 (반환 제출 예정이다, 그러나 그것의 실행 () 기능 (Runaable 인터페이스)를 노출하고 싶지 않았다.

FutureTask 클래스를 실행 () 메소드

마지막 장을 학습 한 후, 우리는 그 실행 () 메서드 호출이 실행 () 메소드의 마지막 작업입니다 알고, 우리는 위의 작업을 전달, 마지막으로 FutureTask 포장되었다, 그 실행이다 () 메소드는 마지막 FutureTask 호출 실행 () 메소드, 그래서 우리는 거기에이 직접적인 방법을 봐주세요.

public void run() {
    // 状态不为NEW,或者修改为当前线程来运行这个任务失败,则直接返回
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    
    try {
        // 真正的任务
        Callable<V> c = callable;
        // state必须为NEW时才运行
        if (c != null && state == NEW) {
            // 运行的结果
            V result;
            boolean ran;
            try {
                // 任务执行的地方【本文由公从号“彤哥读源码”原创】
                result = c.call();
                // 已执行完毕
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                // 处理异常
                setException(ex);
            }
            if (ran)
                // 处理结果
                set(result);
        }
    } finally {
        // 置空runner
        runner = null;
        // 处理中断
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

사용자는, 제 1 상태가 검출 할 코드가 비교적 간단 참조 태스크의 최종 처리 결과 또는 예외를 수행 할 수있다.

미션 여기에 문제를 부족의 코드 또는 비정상적인 처리 결과를 살펴 보자.

protected void setException(Throwable t) {
    // 将状态从NEW置为COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 返回值置为传进来的异常(outcome为调用get()方法时返回的)
        outcome = t;
        // 最终的状态设置为EXCEPTIONAL
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        // 调用完成方法
        finishCompletion();
    }
}
protected void set(V v) {
    // 将状态从NEW置为COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 返回值置为传进来的结果(outcome为调用get()方法时返回的)
        outcome = v;
        // 最终的状态设置为NORMAL
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        // 调用完成方法
        finishCompletion();
    }
}

얼핏 보면,이 두 가지 방법이 유사한 것, 차이 상태에서 해제의 결과는 결국 finishCompletion () 메소드를 호출 동일 및 동일하지 아니된다.

private void finishCompletion() {
    // 如果队列不为空(这个队列实际上为调用者线程)
    for (WaitNode q; (q = waiters) != null;) {
        // 置空队列
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            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;
        }
    }
    // 钩子方法,子类重写
    done();
    // 置空任务
    callable = null;        // to reduce footprint
}

전체 run () 메소드는 결론 :

(1) FutureTask 처리 동작 상태가 종료 상태 새로운 -> COMPLETING-> NORMAL, 새로운 -> COMPLETING-에서 비정상적인 동작 상태 단부> EXCEPTIONAL에서 실행 상태 제어 태스크있다;

(2)이 풀에서 스레드, 작업을 실행하는 스레드 주자를 저장 FutureTask;

그것은 그것으로 설정되어있는 경우 (3) 호출 스레드 웨이터 큐에 저장되고,?

(4) 작업이 상태 변경의 상태를 설정하는 것뿐만 아니라, 완료, 또한 발신자의 스레드를 깨워해야한다.

(웨이터)의 FutureTask 할 때 호출 스레드가 저장됩니다? 보기 생성자 :

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

호출자가 GET () 메서드를 호출하지 않는 경우에는 관련 정보를 찾을 수 없습니다, 우리는 그 다음이 미래의 일이 아니다 아니오가 일반 작업과 다른, 상상? 실제로, 하, 그래서 유일한 방법은 필요 FutureTask에 발신자의 스레드를 저장 한 () 얻을 호출합니다.

자, 방법은 무엇 유령 () 얻을 보자.

얻을 FutureTask 클래스 () 메소드

작업이 완료되지 않은 경우의 get () 메소드가 호출 될 때, 그것은 임무가 끝날 때까지 차단됩니다.

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    // 如果状态小于等于COMPLETING,则进入队列等待
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    // 返回结果(异常)
    return report(s);
}

작업 상태가 완료 동일한보다 작은 경우, 프로세스는 큐를 기다리는 진행, 매우 명확하지 않다.

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();
        }
        // 4. 如果状态大于COMPLETING了,则跳出循环并返回
        // 这是自旋的出口
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        // 如果状态等于COMPLETING,说明任务快完成了,就差设置状态到NORMAL或EXCEPTIONAL和设置结果了
        // 这时候就让出CPU,优先完成任务
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        // 1. 如果队列为空
        else if (q == null)
            // 初始化队列(WaitNode中记录了调用者线程)
            q = new WaitNode();
        // 2. 未进入队列
        else if (!queued)
            // 尝试入队
            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);
        }
        // 3. 阻塞当前线程(调用者线程)
        else
            // 【本文由公从号“彤哥读源码”原创】
            LockSupport.park(this);
    }
}

여기에서 우리는 () 작업이 실행되지 않은 가져 오기를 호출 할 때, 즉, 상태가 NEW, 우리는 위의 다시 논리를 이동 1,2,3,4 표시하려고한다고 가정 :

직접 (1) (1) 첫 번째 사이클 NEW 상태는 큐와 WaitNode에서 호출 쓰레드 패키지를 초기화하고;

(2) 두 번째 사이클은 상태가 큐가 호출자 스레드를 포함 팀에 WaitNode하자, 2에서 비어 있지 않은, 새로운;

(3) 세 번째 사이클 상태는 블록, 대기열이 비어 있지 않은, 3시에, 팀에있다 호출자 스레드 NEW이고;

(4) 작업이 완료되는 동안, 분석의 실행에 따라 () 메소드가 호출자의 스레드를 언 파크 결국 것이다 후, 즉, 세 가지가 깨어날 것이라고 가정;

(5) 상태를 완료보다 확실히 더 네번째 사이클, 상기 루프를 종료하고 복귀;

질문 : 당신이 for 루프에서 전체 프로세스를 제어하려는 이유는 무엇입니까, 여기 모든 단계 괜찮 분리 해 작성하는 것입니다?

A : 모든 작업을 작성하는 것입니다 당신이 올 수 있다면 국가의 상태가 변경되지 않았습니다 재검토 할 필요가 있지만, 코드가 매우 긴 것 때문입니다. 여기 분석 경우에만 얻을 수 () 상태 NEW는 다른 국가가 독립적으로 확인 될 수있다, 그들은 올바른을 위해, 또는 두 개의 스레드가 (기술 브레이크 포인트)를 건너 실행할 수 있습니다.

확인 후 반환, 여기에 대처 최종 결과입니다하는 방법을 살펴.

private V report(int s) throws ExecutionException {
    Object x = outcome;
    // 任务正常结束
    if (s == NORMAL)
        return (V)x;
    // 被取消了
    if (s >= CANCELLED)
        throw new CancellationException();
    // 执行异常
    throw new ExecutionException((Throwable)x);
}

이전 분석을 실행할 때 예외가 사용하는 내부 결과,에 이상이있는 경우, 작업 실행을 기억하십시오.

정상적인 실행이 완료되면 (1), 작업이 반환 값을 반환한다;

예외가 완료되는 경우 (2) 포장 ExecutionException 예외가 발생되고;

이러한 방법으로, 이상이 나타나는 스레드가 호출자 스레드에 반환하고, 어떤 성공도 없다 결국 작업 실행을 모르는 발신자와 같은 일반적인 작업을 수행 좋아하지 않을 수 있습니다.

다른

FutureTask 반환 값이 작업에 추가로 얻을 수 있고, 또한 작업의 실행을 취소 할 수 있습니다.

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();
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion();
    }
    return true;
}

작업이 처리 할 수있는 실행 스레드에 의해 중단 둡니다, 관심있는 학생들은 자신을 분석 할 수 있습니다.

답변 시작

얼마나 많은 시간에 대한 for 루프로하는 Future.get ()이 있다면?

A는 : 아마 각 작업을 제출하기 때문에, 우리는 작업이 완료 될 때까지, 각 작업 실행이 1 초 이상, 그래서 총 시간 5 초 더 많은 포인트입니다 호출 스레드를 차단해야 5 초보다 조금 더 될 것입니다.

개요

(1) 미래의 작업은 FutureTask로 일반적인 작업을 포장하여 수행됩니다.

(2)뿐만 아니라 FutureTask뿐만 아니라인지 비정상 태스크의 실행에 의해 수행되는 작업의 결과를 획득하고, 심지어 작업을 취소하는 단계;

매우 중요한 설계 패턴 템플릿 인 방법의 다수의 정의 (3) AbstractExecutorService;

이 디자인 개념을 볼 때 (4) FutureTask 실제로 비정상적인 통화의 전형적인 구현, 우리는 나중에 인 Netty, 두보에 배웁니다.

달걀

RPC 프레임 워크 비동기 호출 방법을 달성하는 것입니다?

A : RPC 프레임 워크 동기 호출을 호출하는 일반적인 방법은 비동기 호출 사실, 그들은 본질적으로 달성하기 위해 FutureTask 방법을 사용하는 비동기 호출입니다.

일반적으로, 스레드에 의해 원격 인터페이스를 호출 (우리는 원격 스레드를 호출)이 동기 호출 인 경우, 직접 스레드 호출자 결과 다시 복귀 할 때까지 원격 호출 스레드의 결과 기다리고 블록에 그것은 비동기 호출이면 원격 결과 () 메소드가 같은 호출자 스레드와 차단 반환하기 전에이 FutureXxx가 호출되는 경우 미래에 최초의 반환은 물론, 원격 FutureXxx 것들에 결과를 얻을 수 있습니다.

비동기 (RpcContext의 미래에 슬로우됩니다) 두보를 호출에 대해 관심있는 학생들은 예비 학교에 갈 수 있습니다.


나는 대중 번호의 관심을 환영보기 소스 코드를 더 시리즈, 바다의 형제 통 소스 "통 형제 소스 읽기는"함께 수영.

QR 코드

추천

출처www.cnblogs.com/tong-yuan/p/11795216.html