多线程与高并发(七)

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,所以我们可以把主要精力放在数据的流转上,而不是原来的逻辑控制上。这也是异步编程带来的思想上的转变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

༄༊心灵骇客༣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值