Executor
这个接口可以定义线程的运行方式。只有一个execute方法。(在将来的某个时间执行给定的命令。)
public interface Executor {
void execute(Runnable command);
}
场景:以前都是new Thread或者实现Runnable接口然后调用start方法才得以运行,这种方式是固定的,是写死了的。现在有了Executor,不用亲自去指定每一个thread,它的运行方式可以亲自去定义了,至于怎么定义就看怎么实现这个接口了(这个接口我觉得应该是体现了定义和分开这么一个含义),如下给出用例Example:
public class T00_MyExecutor implements Executor{
public static void main(String[] args) {
new T00_MyExecutor().execute(()->{
System.out.println("hello executor");
});
}
@Override
public void execute(Runnable command) {
//new Thread(command).run();
command.run();
}
}
ExecutorService
它继承了Executor,它除了实现Executor可以实现的一个任务之外,它还完善了整个任务执行的生命周期。
拿线程池举例,线程池里有一堆线程,执行完一个任务后,怎么结束呢?
ExecutorService里定义了一系列的方法。实现了线程池生命周期的一些东西。
ExecutorService接口里还有一个submit方法,这个方法是异步提交任务的,提交完后,原线程该怎么运行就怎么运行。远行完后有一个结果会存在future,因为它的返回值是一个future。
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
}
Callable
这个是jdk1.5之后开始增加的一个接口,它和Runnable 一样,可以让一个线程来运行它。
@FunctionalInterface
public interface Callable<V> {
//计算结果,如果无法这样做,则抛出异常。
V call() throws Exception;
}
场景:创建线程,并执行,为什么有了Runnable接口还需要Callable接口,是因为Runnable接口里的run方法没有返回值,而Callable接口里的call方法是有一个返回值的。如下给出用例Example:
public class T02_Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello Callable";
}
};
ExecutorService service = Executors.newCachedThreadPool();
Future<String> future = service.submit(c);
System.out.println(future.get());
service.shutdown();
}
}
Future
场景:Callable是一个任务,是有很多步骤的,执行完的结果用一个future来存储,存储执行的将来才会产生的结果。如下给出用例Example:
public class T03_Future {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(5);
Future<Integer> f = service.submit(()->{//异步
TimeUnit.MILLISECONDS.sleep(500);
return 1;
});
System.out.println(f.get());//get方法是阻塞的
System.out.println(f.isDone());
}
}
FutureTask
futureTask本身也是个任务,FutureTask = Callable + future;FutureTask用起来比future更灵活。
public class FutureTask<V> implements RunnableFuture<V> {}
public interface RunnableFuture<V> extends Runnable, Future<V> {
//将这个Future设置为其计算的结果
void run();
}
场景:因为FutureTask既能作为task来用,也能最为future来用,而原先的callable只能作为一个task来用而不能作为future来用。FutureTask就好比能作为一个任务来用,任务的结果也能存在自己上。如下给出用例Example:
public class T04_FutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(()->{
TimeUnit.MILLISECONDS.sleep(500);
return 1000;
});
new Thread(task).start();
System.out.println(task.get());//阻塞
}
}
CompletableFuture
这是非常高级的一个类,CompletableFuture可以管理多个Future的结果,当然这只是其中一部分功能,还可以产生各种各样的异步任务,对各种各样的结果进行组合处理。
为什么有了Future,还需要ComplteableFurure ?
原因在于Future需要自己通过get()获取结果,如果结果没有计算出来,就会一直阻塞在那里,当然中间也可以执行自己的逻辑,这也是异步的方式。而ComplteableFurure是一个异步回调机制。自己不主动拿结果,让他得到结果后来通知我。解决了Future的痛点。
场景:假设你能够提供一个服务,这个服务查询各大电商网站同一类产品的价格并汇总展示。如下给出用例Example:
public class T05_CompletableFuture {
public static void main(String[] args) {
long start, end;
start = System.currentTimeMillis();
CompletableFuture.supplyAsync(()->priceOfTM())
.thenApply(String::valueOf)
.thenApply(str-> "price " + str)
.thenAccept(System.out::println);
end = System.currentTimeMillis();
System.out.println("use completable future! " + (end - start));
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static double priceOfTM() {
delay();
return 1.00;
}
private static void delay() {
int time = new Random().nextInt(500);
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("After %s sleep!\n", time);
}
}
Java的异步编程和其他语言的有什么差异?
Java的异步编程是响应式编程 + NIO,响应式可以简单的理解为收到某个事件或通知后采取的一系列动作,如响应操作系统的网络数据通知,然后以回调的方式处理数据。异步模式的交互流程,即nio方式。nio在java里的实现主要是:channel、buffer、selector,这些组件组合起来实现了多路复用机制,即nio是非阻塞的。
在Java使用nio后无法立即拿到真实的数据,而且先得到一个”future“,可以理解为邮戳或快递单,为了获悉真正的数据我们需要不停的通过快递单号查询快递进度,所以 J.U.C 中的 Future 是Java对异步编程的第一个解决方案,通常和线程池结合使用。
但future缺点很明显:
- 无法方便得知任务何时完成
- 无法方便获得任务结果
- 在主线程获得任务结果会导致主线程阻塞
如下给出用例Example:
public class example_Future {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService service = Executors.newCachedThreadPool(5);
Future<Integer> f = service.submit(()->{//异步
Thread.sleep(500);//模拟接口调用,耗时500ms
return "hello world";
});
//在输出下面异步结果时,主线程可以不阻塞的做其它事情
System.out.println(f.get());//主线程获取异步结束
}
}
Java8里的CompletableFuture和Java9的Flow Api勉强可以解决上面问题。
但CompletableFuture处理简单的任务可以使用,但并不是一个完整的反应式编程解决方案,在服务调用复杂的情况下,存在服务编排、上下文传递、柔性限流(背压)方面的不足。所以如果面对这些情况使用CompletableFuture可能需要自己额外造轮子了。
Java9的Flow虽然是基于 Reactive Streams 规范实现的,但没有RxJava、Project Reactor这些异步框架丰富和强大完整的解决方案。这些异步框架提供了丰富的api,所以我们可以把主要精力放在数据的流转上,而不是原来的逻辑控制上。这也是异步编程带来的思想上的转变。