Sike java thread pool thread series of in-depth analysis - the next task execution process

threadpool_futuretask

(Horizontal screen phone to see the source code is more convenient)


Note: java source code analysis section if no special instructions are based on java8 version.

Note: the source thread pool section if no special instructions refer ThreadPoolExecutor class.

Brief introduction

Earlier we learned together to perform common tasks process threads in the pool, but in fact there is a task, the task is called future (future task), you can use it to get the results of tasks performed in the thread pool, it is how to achieve it?

Wrote, "Sike java thread series of write himself a thread pool (Continued)" will help to understand this chapter, and the other side of the code is relatively short, relatively to learn before the first visit is recommended before reading this chapter brother Tong easier.

problem

(1) thread pool next task is how to implement it?

(2) What better design patterns that we can learn?

(3) there is going to help us in the future to learn other frameworks?

To a chestnut

We start with an example, to explain the contents of the chapter.

We define a thread pool, and use it to submit five tasks, which five tasks are returned 0,1,2,3,4, at some point in the future, and we have access to their return value, do a cumulative operation .

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);
    }
}

Here we consider two questions:

(1) If there is use common tasks, how to write, about how much time?

If you use an ordinary task, then put into the task which accumulate operations, but not so well written (final issue), the total time is probably a little more than 1 second. However, this has the disadvantage that the accumulated contents of the operation with the task itself coupled together, and changed back if tired multiply, but also to modify the contents of the task.

(2) If there is the future.get () into the for loop, about how much time?

Let us not answer this question, first look at the source code analysis.

submit () method

submit method, it is submitted to have a way to return the value of the task, the next task for internal use (FutureTask) package, and then to the execute () to perform, and finally return the next task itself.

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);
}

The design here is very clever, in fact, these two methods are done in AbstractExecutorService this abstract class, which is a method of using a template.

Let's look at the inheritance system FutureTask:

threadpool_futuretask

FutureTask realized RunnableFuture interface, and the ability to interface combinations RunnableFuture Runnable interface and Future interfaces, and Future interface provides the ability to get the return value of the task.

Problem: the Submit () method returns Why Future interface instead RunnableFuture interface or class FutureTask it?

A: This is due to submit the results returned (), for external callers just want to expose the get () capability (Future interfaces), but did not want to expose its run () capability (Runaable Interface).

run FutureTask class () method

After learning the last chapter, we know that execute () method call is the last task of the run () method, we pass in the above tasks, finally became FutureTask packaging, that is execute () method calls to the last FutureTask the run () method, so we look at this direct method on it.

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);
    }
}

You can see the code is relatively simple, do first state is detected, perform the task, the final processing result or exception.

Mission here lacks the problem, let's look at the code or abnormal processing results.

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();
    }
}

At first glance, these two methods seem similar, the difference is the result of out of state is not the same and not the same, eventually called finishCompletion () method.

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
}

The entire run () method conclude:

(1) FutureTask process operation state has a state control task, running from end state NEW-> COMPLETING-> NORMAL, the abnormal operation state end from NEW-> COMPLETING-> EXCEPTIONAL;

(2) FutureTask save the thread runner to run the task, it is a thread in the pool;

(3) the caller thread is stored in the waiters queue, and when it is set into it?

(4) the task is completed, in addition to setting the state of state changes, should also wake up the caller's thread.

The caller thread is saved when the FutureTask in (waiters) do? View constructor:

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

Found no relevant information, we then imagine if the caller does not call the get () method, then this is not the task of the future is no different with the ordinary tasks? Indeed, ha, so only call get () method have saved the caller's thread to FutureTask in need.

So, let's see get () method is what a ghost.

get FutureTask class () method

When the get () method is called if the task is not finished, it will be blocked until the end of the mission.

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

Is not very clear, if the task state is smaller than the COMPLETING equal, the process proceeds to wait queue.

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);
    }
}

Here we assume that when you call the get () task has not been executed, that is, its status is NEW, we try the above marked 1,2,3,4 go again logic:

(1) first cycle, NEW state, directly to the 1, initializes the queue and the caller thread package in the WaitNode;

(2) the second cycle, the status is NEW, the queue is not empty, at the 2, let WaitNode into the team include caller thread;

(3) the third cycle, the status is NEW, the queue is not empty, and has been into the team, to at 3, blocks the caller thread;

(4) assume that after a while the task is completed, according to the analysis run () method will eventually unpark the caller's thread, that is, three will be awakened;

(5) the fourth cycle, certainly larger than COMPLETING state, and exit the loop and return;

Question: Why do you want to control the entire process in a for loop, every step here is to write separate out okay?

A: Because every action needs to re-examine the status of state has not changed, if you can come up with is to write, but the code will be very lengthy. When analyzed here only get () status NEW, other states can be independently verified, they are able to ensure the correct, or even run two threads cross (skills breakpoint).

OK, then returned, then look at how to deal with here is the final result.

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);
}

Remember, when you run the previous analysis, task execution when an exception is the anomaly on the outcome inside, where it uses.

(1) if the normal execution is completed, the task returns the return value;

(2) If an exception is completed, the packaged ExecutionException exception is thrown;

In this way, the thread that appear abnormal may be returned to the caller thread, and not like performing common tasks like caller does not know the task execution in the end there is no success.

other

FutureTask return value can be obtained in addition to the task, it also can cancel the execution of the task.

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;
}

Leave a task is interrupted by execution threads to handle, interested students can analyze their own.

Answer begins

If there is the future.get () into the for loop, about how much time?

A: Probably would be a little more than five seconds, because each submit a task, we must block the caller thread until the task is finished, each task execution is more than one second, so the total time is 5 seconds and more points.

to sum up

(1) Future task is accomplished by packaging common tasks into FutureTask.

(2) not only to obtain the results of tasks performed by FutureTask, as well as perceived abnormal task execution, and even cancel the task;

(3) AbstractExecutorService defined in a number of template method, which is a very important design patterns;

(4) FutureTask is actually typical implementation of an abnormal call, we later learn to Netty, Dubbo, when will see this design concept.

Egg

RPC framework asynchronous calls is how to achieve?

A: The usual way to invoke RPC framework synchronous calls, asynchronous calls, in fact, they are essentially an asynchronous call, which is to use FutureTask way to achieve.

In general, by a thread (we call remote thread) to call remote interface, if it is a synchronous call, the caller directly to the thread to block waiting for the results of remote calling thread until the results returned back; if it is an asynchronous call, the first returns in the future can get a result to the remote FutureXxx things, of course, if this FutureXxx get called before the remote results returned () method will block with the same caller thread.

Interested students can go to prep about asynchronous calls dubbo (which is thrown into the Future of RpcContext).


I welcome the attention of the public number "Tong brother read source" view source code more series, and brother Tong source of ocean swim together.

qrcode

Guess you like

Origin www.cnblogs.com/tong-yuan/p/11795216.html