一 Future(jdk5引入)

简介: Future接口是Java多线程Future模式的实现,可以来进行异步计算。

可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,也可以使用cancel方法停止任务的执行。

简单测试 -  主题 : Future模式可以理解成:我有一个任务,提交给了Future,Future替我完成这个任务,期间程序可以处理其他任务。

                              一段时间之后,主线程可以从Future那儿取出结果

public class ThreadPoolTest {

    private static  Logger logger= LoggerFactory.getLogger(ThreadPoolTest.class);

    public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单一线程
Future<String> future = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
logger.info("out2-----");
return "Hello world";
}
}); try {
try {
BigDecimal bigDecimal=new BigDecimal(0);
logger.info("out1:"+bigDecimal.toString());
logger.info(future.get(3000,TimeUnit.MILLISECONDS));//Hello world
} catch (TimeoutException e) {
logger.error("timeout-exception",e);
}
} catch (InterruptedException e) {
logger.error("interrupted-exception",e);
} catch (ExecutionException e) {
logger.error("execution-exception",e);
}finally {
threadPool.shutdown();
}
}

console输出:

11:17:17.558 [main] INFO tk.mybatis.springboot.util.thread.ThreadPoolTest - out1:0
Disconnected from the target VM, address: '127.0.0.1:54966', transport: 'socket'
11:17:19.556 [pool-1-thread-1] INFO tk.mybatis.springboot.util.thread.ThreadPoolTest - out2-----
11:17:19.557 [main] INFO tk.mybatis.springboot.util.thread.ThreadPoolTest - Hello world

超时会报错:

SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
10:44:07.413 [main] ERROR tk.mybatis.springboot.util.thread.ThreadPoolTest - timeout-exception
java.util.concurrent.TimeoutException: null
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
at tk.mybatis.springboot.util.thread.ThreadPoolTest.main(ThreadPoolTest.java:30) Process finished with exit code 0

总结:1  比起future.get(),推荐使用get (long timeout, TimeUnit unit) 方法,

设置了超时时间可以防止程序无限制的等待future的结果,可以进行异常处理逻辑。

2  虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,

只能通过阻塞或者轮询的方式得到任务的结果。

阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,

为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?

Future的接口很简单,只有五个方法。jdk8中源码如下:

package java.util.concurrent;

/**
* A {@code Future} represents the result of an asynchronous
* computation. Methods are provided to check if the computation is
* complete, to wait for its completion, and to retrieve the result of
* the computation. The result can only be retrieved using method
* {@code get} when the computation has completed, blocking if
* necessary until it is ready. Cancellation is performed by the
* {@code cancel} method. Additional methods are provided to
* determine if the task completed normally or was cancelled. Once a
* computation has completed, the computation cannot be cancelled.
* If you would like to use a {@code Future} for the sake
* of cancellability but not provide a usable result, you can
* declare types of the form {@code Future<?>} and
* return {@code null} as a result of the underlying task.
*
* <p>
* <b>Sample Usage</b> (Note that the following classes are all
* made-up.)
* <pre> {@code
* interface ArchiveSearcher { String search(String target); }
* class App {
* ExecutorService executor = ...
* ArchiveSearcher searcher = ...
* void showSearch(final String target)
* throws InterruptedException {
* Future<String> future
* = executor.submit(new Callable<String>() {
* public String call() {
* return searcher.search(target);
* }});
* displayOtherThings(); // do other things while searching
* try {
* displayText(future.get()); // use future
* } catch (ExecutionException ex) { cleanup(); return; }
* }
* }}</pre>
*
* The {@link FutureTask} class is an implementation of {@code Future} that
* implements {@code Runnable}, and so may be executed by an {@code Executor}.
* For example, the above construction with {@code submit} could be replaced by:
* <pre> {@code
* FutureTask<String> future =
* new FutureTask<String>(new Callable<String>() {
* public String call() {
* return searcher.search(target);
* }});
* executor.execute(future);}</pre>
*
* <p>Memory consistency effects: Actions taken by the asynchronous computation
* <a href="package-summary.html#MemoryVisibility"> <i>happen-before</i></a>
* actions following the corresponding {@code Future.get()} in another thread.
*
* @see FutureTask
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this Future's {@code get} method
*/
public interface Future<V> { /**
* Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, has already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when {@code cancel} is called,
* this task should never run. If the task has already started,
* then the {@code mayInterruptIfRunning} parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.
*
* <p>After this method returns, subsequent calls to {@link #isDone} will
* always return {@code true}. Subsequent calls to {@link #isCancelled}
* will always return {@code true} if this method returned {@code true}.
*
* @param mayInterruptIfRunning {@code true} if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete
* @return {@code false} if the task could not be cancelled,
* typically because it has already completed normally;
* {@code true} otherwise
*/
boolean cancel(boolean mayInterruptIfRunning); /**
* Returns {@code true} if this task was cancelled before it completed
* normally.
*
* @return {@code true} if this task was cancelled before it completed
*/
boolean isCancelled(); /**
* Returns {@code true} if this task completed.
*
* Completion may be due to normal termination, an exception, or
* cancellation -- in all of these cases, this method will return
* {@code true}.
*
* @return {@code true} if this task completed
*/
boolean isDone(); /**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
V get() throws InterruptedException, ExecutionException; /**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

看懂英文还是最直接的。

二. CompletableFuture介绍

2.1 Future模式的缺点

  • Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,我们无法得知Future什么时候完成。

  • 要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作。要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。

2.2 CompletableFuture介绍

 public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
}

Netty、Guava分别扩展了Java 的 Future 接口,方便异步编程。

在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,

可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,

并且提供了转换和组合CompletableFuture的方法。

1 )主动完成计算
CompletableFuture类实现了CompletionStageFuture接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果,尽管这种方式不推荐使用。
public T     get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent) //如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent
public T join()

 join返回计算的结果或者抛出一个unchecked异常(CompletionException),它和get对抛出的异常的处理有些细微的区别

@Test
public void test(){
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
int i=1/0;
return 100;
});
logger.info(String.format("join=%d", future.join()));
try {
logger.info(String.format("get=%s", future.get()));
} catch (InterruptedException e) {
logger.error("error=%s",e);
} catch (ExecutionException e) {
logger.error("error=%s",e);
}
}

尽管Future可以代表在另外的线程中执行的一段异步代码,但是你还是可以在本身线程中执行:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; public class BasicMain {
private static Logger logger = LoggerFactory.getLogger(BasicMain.class);

public static CompletableFuture<Integer> compute() {
final CompletableFuture<Integer> future = new CompletableFuture<>();
return future;
} public static void main(String[] args) throws Exception {
final CompletableFuture<Integer> f = compute(); class Client extends Thread {
CompletableFuture<Integer> f;
Client(String threadName, CompletableFuture<Integer> f) {
super(threadName);
this.f = f;
} @Override
public void run() {
try {
logger.info(this.getName() + ": " + f.get());
} catch (InterruptedException e) {
logger.error(e.getMessage());
} catch (ExecutionException e) {
logger.error(e.getMessage());
}
}
}
new Client("Client1", f).start();
new Client("Client2", f).start();
logger.info("waiting....");
f.complete(100);
f.obtrudeValue(200);
//logger Client1: 200 Client2: 200 或者 Client1: 100 Client1: 100 或者 Client1: 200 Client2: 100 ,都有可能 // f.completeExceptionally(new Exception());
    }
}

说明:

可以看到我们并没有把f.complete(100) 放在另外的线程中去执行,但是在大部分情况下我们可能会用一个线程池去执行这些异步任 务。CompletableFuture.complete()CompletableFuture.completeExceptionally只能被调用一次。

但是我们有两个后门方法可以重设这个值:obtrudeValueobtrudeException,但是使用的时候要小心,

因为complete已经触发了客户端,有可能导致客户端会得到不期望的结果

2)创建CompletableFuture对象。

CompletableFuture的静态工厂方法

方法名 描述
runAsync(Runnable runnable) 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。
runAsync(Runnable runnable, Executor executor) 使用指定的thread pool执行异步代码。
supplyAsync(Supplier<U> supplier) 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor) 使用指定的thread pool执行异步代码,异步操作有返回值

eg: runAsync 和 supplyAsync 方法比较

区别是:

runAsync返回的CompletableFuture是没有返回值的

    原因:

      Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空

共同点:

因为方法的参数类型都是函数式接口,所以可以使用lambda表达式实现异步任务,比如:

 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello";
}); try {
logger.info("out = "+ future.get());//out = Hello
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
      CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Hello");
}); try {
logger.info("out = "+ future.get());//out = null
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

简单试验 - 主题:在两个线程里并行执行任务A和任务B,只要有一个任务完成了,就执行任务C

package tk.mybatis.springboot.util.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.time.LocalTime;
import java.util.Random;
import java.util.concurrent.*; /**
* 在两个线程里并行执行任务A和任务B,只要有一个任务完成了,就执行任务C
*
* 两种方法useFuture和useCompletableFuture相比:
*
* 首先,useCompletableFuture 比 useFuture 的代码简单。
* 在useFuture 里,既要自己照顾线程池的创建和销毁,还要负责对任务A和任务B的监控。
* 而useCompletableFuture,只需要用CompletableFuture的runAfterEither就完成了任务A、任务B和任务C之间的依赖关系的定义。
*
*/
public class CompletableFutureTest { private static Logger logger = LoggerFactory.getLogger(CompletableFutureTest.class);
private static Random random = new Random(); /**
* useFuture test
* @throws InterruptedException
* @throws ExecutionException
*/
private static void useFuture() throws InterruptedException, ExecutionException {
logger.info("useFuture");
ExecutorService exector = Executors.newFixedThreadPool(3);
Future<Void> futureA = exector.submit(() -> work("A1"));
Future<Void> futureB = exector.submit(() -> work("B1"));
while (true) {
try {
futureA.get(1, TimeUnit.SECONDS);
break;
} catch (TimeoutException e) {
}
try {
futureB.get(1, TimeUnit.SECONDS);
break;
} catch (TimeoutException e) {
}
}
exector.submit(() -> work("C1")).get();
exector.shutdown();
} private static void useCompletableFuture() throws InterruptedException, ExecutionException {
logger.info("useCompletableFuture");
CompletableFuture<Void> futureA = CompletableFuture.runAsync(() -> work("A2"));
CompletableFuture<Void> futureB = CompletableFuture.runAsync(() -> work("B2"));
logger.info("get="+futureA.runAfterEither(futureB, () -> work("C2")).get()); // 或者
// CompletableFuture.runAsync(() -> work("A2"))
// .runAfterEither(CompletableFuture.runAsync(() -> work("B2"))
// , () -> work("C2"))
// .get();
} /**
* logger 输出
* @param name
* @return
*/
public static Void work(String name) {
logger.info(name + " starts at " + LocalTime.now());
try {
TimeUnit.SECONDS.sleep(random.nextInt(10));
} catch (InterruptedException e) {
}
logger.info(name + " ends at " + LocalTime.now());
return null;
} /**
* 从useFuture的输出可以看出,
* 任务C1的开始并不是紧随着任务A1的完成,差了0.001秒,
* 原因是在方法1里,是对任务A1和任务B1都用get(1,TimeUnit.SECONDS)来询问他们的状态,
* 当其中一个任务先完成时,主线程可能正阻塞在另一个未完成任务的get上
*
* 而从useCompletableFuture完全不存在这样的问题,
* 任务C2的开始于任务A1的结束之间没有任何的时间差
*
* @param args
* @throws InterruptedException
* @throws ExecutionException
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
// useFuture();
//logger 输出
// B1 starts at 10:28:09.562
// A1 starts at 10:28:09.562
// B1 ends at 10:28:17.566
// C1 starts at 10:28:17.567
// A1 ends at 10:28:18.563
// C1 ends at 10:28:20.570 // TimeUnit.SECONDS.sleep(10);
useCompletableFuture();
//logger 输出
// A2 starts at 10:43:46.867
// B2 starts at 10:43:46.867
// B2 ends at 10:43:51.871
// A2 ends at 10:43:51.871
// C2 starts at 10:43:51.871
// C2 ends at 10:44:00.874
TimeUnit.SECONDS.sleep(10);//避免打印出的start - end 不全 } }

 3)计算结果完成时的处理

 当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:

   public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
} public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(asyncPool, action);
} public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) {
return uniWhenCompleteStage(screenExecutor(executor), action);
}
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {
return uniExceptionallyStage(fn);
}

可以看到Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
       方法不以Async结尾,意味着Action使用相同的线程执行,

Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

注意这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。

package tk.mybatis.springboot.util.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future; public class MainTest {
private static Logger logger = LoggerFactory.getLogger(MainTest.class); private static Random random = new Random();
private static long t = System.currentTimeMillis(); static Map<String,String> getMoreData() {
logger.info("start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
logger.info("end ");
Map map=new HashMap();
map.put(random.nextInt(1000),random.nextInt(1000));
return map;
} public static void main(String[] args) throws Exception {
CompletableFuture<Map<String,String>> future = CompletableFuture.supplyAsync(MainTest::getMoreData);
Future<Map<String,String>> f = future.whenComplete((v, e) -> {
logger.info("v="+v);
logger.info("e="+e);
});
logger.info("get="+f.get());
}
}

     exceptionally方法返回一个新的CompletableFuture,

当原始的CompletableFuture抛出异常的时候,就会触发这个CompletableFuture的计算,调用function计算值,

否则如果原始的CompletableFuture正常计算完后,这个新的CompletableFuture也计算完成,它的值和原始的CompletableFuture的计算的值相同(??)。

也就是这个exceptionally方法用来处理异常的情况。

        CompletableFuture<Map<String,String>> futureException = future.exceptionally(new Function<Throwable, Map<String, String>>() {
@Override
public Map<String, String> apply(Throwable throwable) {
logger.error("error="+throwable.getMessage());
Map map=new HashMap();
map.put(random.nextInt(1000),random.nextInt(1000));
return map;
}
}); logger.info("get.exception="+futureException.get());

下面一组方法虽然也返回CompletableFuture对象,但是对象的值和原来的CompletableFuture计算的值不同。

当原先的CompletableFuture的值计算完成或者抛出异常的时候,会触发这个CompletableFuture对象的计算,结果由BiFunction参数计算而得。

因此这组方法兼有whenComplete和转换的两个功能。

public <U> CompletableFuture<U>     handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

同样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。

4)转换

    CompletableFuture可以作为monad(单子)和functor。由于回调风格的实现,我们不必因为等待一个计算完成而阻塞着调用线程,

而是告诉CompletableFuture当计算完成的时候请执行某个function。而且我们还可以将这些操作串联起来,或者将CompletableFuture组合起来。

三 ThreadPoolTaskExecutor

包路径:org.springframework.scheduling.concurrent

Future、 CompletableFuture、ThreadPoolTaskExecutor简单实践的更多相关文章

  1. Java8 增强的Future:CompletableFuture(笔记)

    CompletableFuture是Java8新增的一个超大型工具类,为什么说她大呢?因为一方面它实现了Future接口,更重要的是,它实现了CompletionStage接口.这个接口也是Java8 ...

  2. Thrift简单实践

    0.什么是RPC RPC(Remote Procedure Call - 远程过程调用),是通过网络从远程计算机上请求服务,而不需要了解底层网路技术的细节.简单点说,就是像调用本地服务(方法)一样调用 ...

  3. Java 异步处理简单实践

    Java 异步处理简单实践 http://www.cnblogs.com/fangfan/p/4047932.html 同步与异步 通常同步意味着一个任务的某个处理过程会对多个线程在用串行化处理,而异 ...

  4. Android 设计随便说说之简单实践(合理组合)

    上一篇(Android 设计随便说说之简单实践(模块划分))例举了应用商店设计来说明怎么做模块划分.模块划分主要依赖于第一是业务需求,具体是怎么样的业务.应用商店则包括两个业务,就是向用户展示appl ...

  5. c#中,委托Func的简单实践

    c# 委托Func的简单实践最近才真正的接触委托,所以针对Func类型的委托,做一个实践练习. 首先说一些我对委托的初级理解:"就是把方法当做参数,传进委托方法里". 我平时用到的 ...

  6. kafka原理和实践(二)spring-kafka简单实践

    系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践( ...

  7. SQL知识以及SQL语句简单实践

    综述 大家都知道SQL是结构化查询语言,是关系数据库的标准语言,是一个综合的,功能极强的同时又简洁易学的,它集级数据查询(Data Quest),数据操纵(Data Manipulation),数据定 ...

  8. ZooKeeper分布式锁简单实践

    ZooKeeper分布式锁简单实践 在分布式解决方案中,Zookeeper是一个分布式协调工具.当多个JVM客户端,同时在ZooKeeper上创建相同的一个临时节点,因为临时节点路径是保证唯一,只要谁 ...

  9. Spring 学习二-----AOP的原理与简单实践

    一.Spring  AOP的原理 AOP全名Aspect-Oriented Programming,中文直译为面向切面(方面)编程.何为切面,就比如说我们系统中的权限管理,日志,事务等我们都可以将其看 ...

随机推荐

  1. Druid 配置及内置监控,Web页面查看监控内容

    1.配置Druid的内置监控 首先在Maven项目的pom.xml中引入包 <dependency> <groupId>com.alibaba</groupId> ...

  2. spring MVC 如何接收前台传入的JSON对象数组

    spring MVC 如何接收前台传入的JSON对象数组 主要方法: (主要用到的包是 net.sf.json  即:json-lib-2.3-jdk15.jar 完整相关jar包: commons- ...

  3. (string 高精度) Lovekey hdu 2100

    Lovekey Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Su ...

  4. hbase记录-修改压缩格式

    在业务空闲的时候修改压缩格式 先测试 ---测试表create 'test', { NAME => 'c',VERSIONS => 1}desc 'test'disable 'test'a ...

  5. win2003服务器安全设置教程

    服务器安全设置 1.系统盘和站点放置盘必须设置为NTFS格式,方便设置权限. 2.系统盘和站点放置盘除administrators 和system的用户权限全部去除. 3.启用windows自带防火墙 ...

  6. 关于Meta标签的一些知识

    1.定义文档的编码格式(H5的标准写法) <meta charset="UTF-8"> 2.声明指定的浏览器以及版本 <meta http-equiv=" ...

  7. Jacob用法收集

    介绍 Jacob 是Java-COM Bridge的缩写,它在Java与微软的COM组件之间构建一座桥梁.使用Jacob自带的DLL动态链接库,并通过JNI的方式实现了在Java平台上对COM程序的调 ...

  8. u-boot移植(三)---修改前工作:代码流程分析2

    一.vectors.S 1.1 代码地址 vectors.S (arch\arm\lib) 1.2 流程跳转 跳转符号 B 为 start.S 中的 reset 执行代码,暂且先不看,先看看 vect ...

  9. Nginx 防盗链配置

    防盗链一般都是流媒体配置 location ~* \.(jpg|jpeg|png|bmg|swf|mp4|mp4|mmf|zip|rar|swf|flv)$ { // 对jpg|jpeg|png|bm ...

  10. luogu P2113 看球泡妹子

    2333 这么水的蓝题 f[i][j] 表示看了i场比赛,小♀红的什么东西为j时小♂明的什么值 强行压维蛤蛤 剩下的转移很简单(注意i的循环顺序从后往前,01背包) (具体见代码) #include& ...