Summary, summary!!!! java callback, future

Summarize, summarize, continue to summarize java, future

    Then the last java callback, the summary of future

The book says:Future 模式是多线程开发中非常常见的一种设计模式,它的核心思想是异步调用。当我们需要调用一一个函数方法时,如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调者立即返回,让它在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获得需要的数据。 Future 模式有点类似在网上买东西。如果我们在网上下单买了一一个手机,当我们支付完成后,手机并没有办法立即送到家里,但是在电脑上会立即产生一个订单。这个订单就是将来发货或者领取手机的重要凭证,这个凭证也就是Future模式中会给出的一个契约。在支付活动结束后,大家不会傻傻地等着手机到来,而是可以各忙各的。而这张订单就成为了商家配货、发货的驱动力。当然,这一切你并不用关心。你要做的,只是在快递上门时,开一下门,拿一下货而已。 对于Future模式来说,虽然它无法立即给出你需要的数据。但是,它会返回给你一一个契约,将来,你可以凭借着这个契约去重新获取你需要的信息。

As shown in the figure, a time-consuming program is called by the traditional synchronization method. The client issues a call request, which takes a long time to return. The client waits until the data is returned, and then processes other tasks.Enter image description

The picture below is the future mode

Enter image description

package com.test.version3future;
1.  @author: zhangzeli
2.  @date 20:35 2018/4/20
3. [博客版权](http://https://my.oschina.net/u/3703858/blog/1798696)
4. [项目地址 ](https://gitee.com/zhangzeli/java-concurrent)  
public class CommonCook {

    // 网购厨具线程
    static class OnlineShopping extends Thread {

        private Chuju chuju;

        @Override
        public void run() {
            System.out.println("第一步:下单");
            System.out.println("第一步:等待送货");
            try {
                Thread.sleep(5000);  // 模拟送货时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一步:快递送到");
            chuju = new Chuju();
        }
    }
    //  用厨具烹饪食材
    static void cook(Chuju chuju, Shicai shicai) {}
    // 厨具类
    static class Chuju {}
    // 食材类
    static class Shicai {}

    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        // 第一步 网购厨具
        OnlineShopping thread = new OnlineShopping();
        thread.start();
        thread.join();  // 保证厨具送到
        // 第二步 去超市购买食材
        Thread.sleep(2000);  // 模拟购买食材时间
        Shicai shicai = new Shicai();
        System.out.println("第二步:食材到位");
        // 第三步 用厨具烹饪食材
        System.out.println("第三步:开始展现厨艺");
        cook(thread.chuju, shicai);

        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

operation result:

  1. Step 1: place an order
  2. Step 1: Wait for delivery
  3. Step 1: Express delivery
  4. Step 2: The ingredients are in place
  5. Step 3: Start Showing Your Cooking Skills
  6. A total of 7014ms

As you can see, multithreading has lost its meaning. We can't do anything while the kitchen utensils are delivered. The corresponding code is to call the join method to block the main thread.

Someone asked, is it okay not to block the main thread? ? ?

no! ! !

从代码来看的话,run方法不执行完,属性chuju就没有被赋值,还是null。换句话说,没有厨具,怎么做饭。Java现在的多线程机制,核心方法run是没有返回值的;如果要保存run方法里面的计算结果,必须等待run方法计算完,无论计算过程多么耗时。 面对这种尴尬的处境,程序员就会想:在子线程run方法计算的期间,能不能在主线程里面继续异步执行??? Where there is a will,there is a way!!! 这种想法的核心就是Future模式,下面先应用一下Java自己实现的Future模式。

package com.test.version3future;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

1.  @author: zhangzeli
2.  @date 20:35 2018/4/20
3. [博客版权](http://https://my.oschina.net/u/3703858/blog/1798696)
4. [项目地址 ](https://gitee.com/zhangzeli/java-concurrent)  
public class FutureCook {

    //  用厨具烹饪食材
    static void cook(Chuju chuju, Shicai shicai) {}

    // 厨具类
    static class Chuju {}

    // 食材类
    static class Shicai {}

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 第一步 网购厨具
        Callable<Chuju> onlineShopping = new Callable<Chuju>() {

            @Override
            public Chuju call() throws Exception {
                System.out.println("第一步:下单");
                System.out.println("第一步:等待送货");
                Thread.sleep(5000);  // 模拟送货时间
                System.out.println("第一步:快递送到");
                return new Chuju();
            }

        };
        FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
        new Thread(task).start();
        // 第二步 去超市购买食材
        Thread.sleep(2000);  // 模拟购买食材时间
        Shicai shicai = new Shicai();
        System.out.println("第二步:食材到位");
        // 第三步 用厨具烹饪食材
        if (!task.isDone()) {  // 联系快递员,询问是否到货
            System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
        }
        Chuju chuju = task.get();
        System.out.println("第三步:厨具到位,开始展现厨艺");
        cook(chuju, shicai);

        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

operation result:

  1. Step 1: place an order
  2. Step 1: Wait for delivery
  3. Step 2: The ingredients are in place
  4. Step 3: Wait until the kitchen utensils are in a good mood (if you are in a bad mood, call the cancel method to cancel the order)
  5. Step 1: Express delivery
  6. The third step: kitchen utensils are in place, start to show cooking skills
  7. It can be seen in a total of 5002ms . During the period when the courier delivered the kitchen utensils, we were not idle and could go to buy ingredients; and we knew whether the kitchen utensils arrived or not, and we could even cancel the order when the kitchen utensils did not arrive. It's amazing, isn't there? Let's analyze the second code in detail

1) The time-consuming online shopping kitchenware logic is encapsulated into a Callable call method.

public interface Future<V> {  
    /**如果任务还没开始,执行cancel(...)方法将返回false;
       如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;
       当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;
       当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
    **/
    boolean cancel(boolean mayInterruptIfRunning);  
    //如果任务完成前被取消,则返回true。
    boolean isCancelled();  
    //如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
    boolean isDone();  
    //获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
    V get() throws InterruptedException, ExecutionException;  
     //获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;  
}  

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
// Callable接口可以看作是Runnable接口的补充,call方法带有返回值,并且可以抛出异常。

2) Take the Callable instance as a parameter, generate a FutureTask object, and then take this object as a Runnable and start a new thread as a parameter.

public class FutureTask<V> implements RunnableFuture<V>{}

public interface RunnableFuture<V> extends Runnable, Future<V>{}

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;
}
//这个继承体系中的核心接口是Future。Future的核心思想是:一个方法f,
//计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,
//可以通过Future这个数据结构去控制方法f的计算过程。

3) In the third step, the isDone method is called to check the status, and then the task.get method is called directly to get the kitchenware, but it has not been delivered yet, so it will still wait for 3 seconds. Comparing the execution result of the first piece of code, here we save 2 seconds. This is because during the delivery by the courier, we go to the supermarket to buy ingredients, and these two things are executed asynchronously in the same time period.

通过以上3步,我们就完成了对Java原生Future模式最基本的应用。下面具体分析下FutureTask的实现,先看JDK8的,再比较一下JDK6的实现。 既然FutureTask也是一个Runnable,那就看看它的run方法

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable; // 这里的callable是从构造方法里面传人的
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex); // 保存call方法抛出的异常
                }
                if (ran)
                    set(result); // 保存call方法的执行结果
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

First look at the logic in the try statement block, and find that the main logic of the run method is to run the call method of the Callable, and then save the result or exception (an attribute result used). What is more difficult to think of here is that the exception thrown by the call method is also saved.

What the hell is the attribute state that represents the state here?

* 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;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

Think of FutureTask as a Future, then its role is to control the execution process of Callable's call method, and there will naturally be state transitions during the execution process:

  • 1) When a FutureTask is newly created, the state is the NEW state; when COMPETING and INTERRUPTING are used,

    Indicates an instantaneous state and has a very short existence time (why should this state be established??? I don't understand);

    NORMAL means successful completion; EXCEPTIONAL means the execution process is abnormal; CANCELED means the execution process is canceled;

    INTERRUPTED was interrupted

  • 2) The execution process is successfully completed: NEW -> COMPLETING -> NORMAL

  • 3) An exception occurred during execution: NEW -> COMPLETING -> EXCEPTIONAL

  • 4) The execution process was canceled: NEW -> CANCELLED

  • 5) During execution, the thread is interrupted: NEW -> INTERRUPTING -> INTERRUPTED

Let's look at the implementation of the get method:

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        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();
            }

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

The logic of the get method is very simple. If the execution process of the call method is completed, the result is given; if it is not completed, the current thread is suspended and waited. The logic of the infinite loop in the awaitDone method can be understood after a few deductions; the main innovation of the suspended thread in it is to define the WaitNode class to organize multiple waiting threads into a queue, which is the biggest difference from the implementation of JDK6.

Finally, let's take a look at the more advanced applications derived from the Future pattern.

Another scenario: we write a simple database connection pool ourselves, which can reuse database connections and work normally under high concurrency.

package test;

import java.util.concurrent.ConcurrentHashMap;

public class ConnectionPool {

    private ConcurrentHashMap<String, Connection> pool = new ConcurrentHashMap<String, Connection>();
    
    public Connection getConnection(String key) {
        Connection conn = null;
        if (pool.containsKey(key)) {
            conn = pool.get(key);
        } else {
            conn = createConnection();
            pool.putIfAbsent(key, conn);
        }
        return conn;
    }
    
    public Connection createConnection() {
        return new Connection();
    }
    
    class Connection {}
}

We used ConcurrentHashMap, so that the getConnection method does not have to be synchronized (of course, Lock can also be used), when multiple threads call the getConnection method at the same time, the performance is greatly improved.

It seems to be perfect, but it may lead to the creation of redundant connections. Deduce it again:

At a certain moment, 3 threads enter the getConnection method at the same time, call pool.containsKey(key) and return false, and then each of the 3 threads creates a connection. Although the put method of ConcurrentHashMap will only join one of them, it still generates 2 redundant connections. If it is a real database connection, it will cause a huge waste of resources.

So, our difficulty now is: how to execute createConnection only once when multiple threads access the getConnection method.

Combined with the previous implementation analysis of Future mode: when three threads need to create a connection, if only one thread executes the createConnection method to create a connection, the other two threads only need to use this connection. Further extension, put the createConnection method into a Callable's call method, and then generate a FutureTask. We only need to let one thread execute the run method of FutureTask, and other threads only execute the get method.

Above code:

package test;

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ConnectionPool {

    private ConcurrentHashMap<String, FutureTask<Connection>> pool = 
new ConcurrentHashMap<String, FutureTask<Connection>>();

    public Connection getConnection(String key) throws InterruptedException, ExecutionException {
        FutureTask<Connection> connectionTask = pool.get(key);
        if (connectionTask != null) {
            return connectionTask.get();
        } else {
            Callable<Connection> callable = new Callable<Connection>() {
                @Override
                public Connection call() throws Exception {
                    return createConnection();
                }
            };
            FutureTask<Connection> newTask = new FutureTask<Connection>(callable);
            connectionTask = pool.putIfAbsent(key, newTask);
            if (connectionTask == null) {
                connectionTask = newTask;
                connectionTask.run();
            }
            return connectionTask.get();
        }
    }

    public Connection createConnection() {
        return new Connection();
    }

    class Connection {
    }
}

推演一遍:当3个线程同时进入else语句块时,各自都创建了一个FutureTask, 但是ConcurrentHashMap只会加入其中一个。第一个线程执行pool.putIfAbsent方法后返回null, 然后connectionTask被赋值,接着就执行run方法去创建连接,最后get。后面的线程执行pool.putIfAbsent方法不会返回null, 就只会执行get方法。在并发的环境下,通过FutureTask作为中间转换,成功实现了让某个方法只被一个线程执行。

Love work. There is no power that makes man great and wise as labor, the power of collective, fraternal, free labor. - Gorky

The next article will talk about the enhanced future mode CompletableFuture

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325444972&siteId=291194637