Fork me on GitHub

【Java并发】CallBack和Future模式

Callable

  在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果的目的。
  
  不过,Java中,也提供了使用CallableFuture来实现获取任务结果的操作。Callable用来执行任务,产生结果,而Future用来获得结果。
  
  Callable接口与Runnable接口是否相似,查看源码,可知Callable接口的定义如下:

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

  可以看到,与Runnable接口不同之处在于,call方法带有泛型返回值V

Future常用方法

  • V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
  • V get(Long timeout , TimeUnit unit):获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
  • boolean isDone():如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
  • boolean isCanceller() :如果任务完成前被取消,则返回true。
  • boolean cancel(boolean mayInterruptRunning)
    • 如果任务还没开始,执行cancel(...)方法将返回false
    • 如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false
    • 当任务已经完成,执行cancel(...)方法将返回falsemayInterruptRunning参数表示是否中断执行中的线程。

通过方法分析我们也知道实际上Future提供了3种功能:

  • (1)能够中断执行中的任务
  • (2)判断任务是否执行完成
  • (3)获取任务执行完成后额结果。

示例
  
  我们通过简单的例子来体会使用Callable和Future来获取任务结果的用法。
  

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CalBFutrueTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        final Future<String> submit = newCachedThreadPool.submit(new TaskCallback());
        System.out.println(Thread.currentThread().getName()+"-主线程开始执行");
        new Thread(new Runnable() {
            public void run() {
                try {
                    String result = submit.get();
                    System.out.println(result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        System.out.println("主线程执行任务...");
        if(newCachedThreadPool!=null) {
            //关闭线程池
            newCachedThreadPool.shutdown();
        }
    }
}
class TaskCallback implements Callable<String> {
    public String call() throws Exception {
        System.out.println("子线程正在执行任务,请等待5s");
        Thread.sleep(5000);
        System.out.println("子线程 任务结束");
        return "abab";
    }
}

执行结果

子线程正在执行任务,请等待5s
main-主线程开始执行
主线程执行任务...
子线程 任务结束
abab

Future模式

  Future模式的核心在于:去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑
  Futrure模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果是再取真实的结果。

  在多线程中经常举的一个例子就是:网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。  

Future模式时序图

  首先客户端向服务器请求RealSubject,但是这个资源的创建是非常耗时的,怎么办呢?这种情况下,首先返回Client一个FutureSubject,以满足客户端的需求,于此同时呢,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕。

公共数据接口,FutureData和RealData都要实现。

public abstract class Data {
    public abstract String getRequest();
}

真实数据RealData

public class RealData extends Data {
    private String result;

    public RealData(String data) {
        System.out.println("正在使用data:" + data + "网络请求数据,耗时操作需要等待.");
        try {
            Thread.sleep(3000);
        } catch (Exception e) {}
        System.out.println("操作完毕,获取结果...");
        result = "返回值aaaabb";
    }

    @Override
    public String getRequest() {
        return result;
    }
}

FutureData,当有线程想要获取RealData的时候,程序会被阻塞。等到RealData被注入才会使用getReal()方法。

public class FutureData extends Data {

    public volatile static boolean ISFLAG = false;
    private RealData realData;
    public synchronized void setRealData(RealData realData) {
        // 如果已经获取到结果,直接返回
        if (ISFLAG) {
            return;
        }
        // 如果没有获取到数据,传递真是对象
        this.realData = realData;
        ISFLAG = true;
        // 进行通知
        notify();
    }
    @Override
    public synchronized String getRequest() {
        while (!ISFLAG) {
            try {
                wait();
            } catch (Exception e) {}
        }
        // 获取到数据,直接返回
        return realData.getRequest();
    }
}

FutureClient 客户端

public class FutureClient {
    public Data request(String queryStr) {
        FutureData furureData = new FutureData();
        new Thread(new Runnable() {
            @Override
            public void run() {
                RealData realData = new RealData(queryStr);
                furureData.setRealData(realData);
            }
        }).start();
        return furureData;
    }

}

调用者:

public class Main {
    public static void main(String[] args) {
        FutureClient futureClient = new FutureClient();
        Data request = futureClient.request("请求参数.");
        System.out.println("请求发送成功!");
        System.out.println("执行其他任务...");
        String result = request.getRequest();
        System.out.println("获取到结果..." + result);
    }

}

执行结果

请求发送成功!
执行其他任务...
正在使用data:请求参数.网络请求数据,耗时操作需要等待.
操作完毕,获取结果...
获取到结果...返回值aaaabb
  • 调用者请求资源,client.request("name"); 完成对数据的准备,当要获取资源的时候,data.getResult(),如果资源没有准备好isReady = false;那么就会阻塞该线程。直到资源获取然后该线程被唤醒。
posted @ 2019-07-28 23:20  这个世界~  阅读(3431)  评论(0编辑  收藏  举报