• 并发编程-FutureTask&CompletableFuture


    并发编程-FutureTask&CompletableFuture

    今天会聊到【Future/callable】并且分析他们的原理,同时也会聊到【CompletableFuture】的使用和原理,在这一章中,我们聊并发就到此结束,下面我可能会去看一下中间件的源码和实现,然后之后咱们接着聊关于中间件的东西,因为这些在分布式中同样重要。

    Future/callable使用

    它是在原来线程的基础上,提供了一个带有返回值的线程。

    public class FutureCallableExample {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            CalculationCallable calculationCallable=new CalculationCallable(1,2);
             FutureTask<Integer> futureTask=new FutureTask(calculationCallable);
            System.out.println("开始--->"+new Date());
            //FutureTask本质上还是一个线程
            new Thread(futureTask).start();
            //这里是一个阻塞方法 ,我们要拿到返回结果,肯定要等到call方法执行完成才能进行返回,所以会阻塞
            Integer result= futureTask.get();
            System.out.println(result);
            System.out.println("结束--->"+new Date());
        }
    
        static class CalculationCallable implements Callable<Integer>{
            int x;
            int y;
            public CalculationCallable(int x, int y) {
                this.x = x;
                this.y = y;
            }
            @Override
            public Integer call() throws Exception {
                System.out.println("begin call--->"+new Date());
                TimeUnit.SECONDS.sleep(2);
                return x+y;
            }
        }
    }

    如何实现&源码解析

    把重写Callable中的call方法传递到FutureTask中,然后把FutureTask放在线程中启动->FutureTas本身就是一个实现了Runnable的类,所以才能被启动,而传递这个对象进去,肯定是要调用他的call方法,那这个时候就要有一个共享变量记录他的状态,从而可以判断时候执行完毕or not

    get 方法去获取结果的时候->通过上面的状态判读时候执行完成or not 如果没有执行完成则加入一个队列中进行等待,因为可能有多个线程来请求get,

    整体流程:

    • 当执行run的时候执行call方法,把执行结果放在一个全局变量中,在set结果的时候唤醒被阻塞的线程,上下文切换的时候线程阻塞到了get方法中,这个时候进入get方法的下一次自旋中,然后获得结果从而返回结果
    • 当执行get方法的时候判断当前状态时候是执行完毕状态,是的话则直接返回数据,不是,则把当前节点加入到队列中进行阻塞

    run()

    public void run() {
        //这里就是进行状态的判断
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //执行传递进来的Callable中的call方法
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
    
            runner = null;
           
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

    进行线程的唤醒

    //这里进行唤醒操作
    private void finishCompletion() {
        // assert state > COMPLETING;
        //遍历整个链表
        for (WaitNode q; (q = waiters) != null;) {
            //改成null,->不断的把节点释放掉
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        //进行释放
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
    
        done();
    
        callable = null;        // to reduce footprint
    }

    get()

    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;
        //自旋,因为可能有多个线程来调用get方法,cas的时候可能失败,所以自旋重试
        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)
                //把当前节点设置到waiters(一个队列中)
                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
                //阻塞,在set方法完成结果的时候就会去唤醒
                LockSupport.park(this);
        }
    }

    CompletableFuture

    我们发现FutureTask在获取结果的时候会阻塞 ,我们是否可以让一个线程执行完成结果后主动通知我们呢(异步回调),是的,它就是做这个事情的,异步回调你的方法,不进行阻塞的获得结果,完成你获得结果后想做的事情。这个类图可以看出CompletableFuture融合了两者的实现,既可以通过future等待执行结果,又可以使用completionStage去增强异步回调的功能。

    构建一个CompletableFuture

    • supplyAsync(Supplier<U> supplier):没有返回值
    • runAsync(Runnable runnable,Executor executor) :异步执行一个任务,并且可以自定义线程池,默认用【ForkJoinPool.commonPool
            //不带返回值(但是在调用get方法的时候也会阻塞)
            CompletableFuture.runAsync(() -> System.out.println("异步执行一个任务:" + Thread.currentThread().getName())).get();
            //带返回值(但是在调用get方法的时候也会阻塞)
          // System.out.println(CompletableFuture.supplyAsync(() -> "Glen is the handsomest person").get());
            //使用链式调用
            CompletableFuture.supplyAsync(() -> "Glen is the handsomest person").thenAccept(System.out::println);=

    CompletionStage的api

    纯消费类型】:只消费上个的执行结果,不返回新的数据 【一定包含了accept关键字

     这里只演示几个用法大致相同,除了包含either的方法,他是只要前面任何一个执行完成,就完成。

    public class AcceptExample {
        public static void main(String[] args) {
            CompletableFuture.supplyAsync(()->"Then accept message").thenAcceptAsync(System.out::println);
            CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> "Then accept both");
            CompletableFuture<String> stringCompletableFuture1 = CompletableFuture.supplyAsync(() -> "message");
            //这里指的是两个任务都执行结束,可以在函数中获取两个函数的结果进行操作
            stringCompletableFuture.thenAcceptBoth(stringCompletableFuture1,(x1,x2)-> System.out.println(x1+"-->"+x2));
    
        }
    }

    有返回值的方法】: 即消费上个任务,也返回新的数据【包含apply

    public class ApplyExample {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //还是获取上一个的执行结果,在下一个链中操作,然后返回
            System.out.println(CompletableFuture.supplyAsync(() -> "apply").thenApply((result) -> result + "->Glen").get());
            //这里你可以增加一个CompletableFuture,和上面相似
            System.out.println(CompletableFuture.supplyAsync(() -> "combine")
                    .thenCombine(CompletableFuture.supplyAsync(() -> "Glen"), (x1, x2) -> x1 + ":" + x2).get());
        }
    }

    不消费也不返回】: 上个任务的结果我不需要,我只是想等你结束后,然后我执行,我的结果业务也不需要,我也不用返回

    public class RunExample {
        public static void main(String[] args) {
            //注意:这里不消费也不返回,并且接受不到之前的结果
            CompletableFuture.supplyAsync(()->"Both").runAfterBoth(CompletableFuture.supplyAsync(()->"message"),()->{
                System.out.println("there isn't previous result");
            });
        }
    }

    组合类型】:组合多个类型任务,比如多个任务执行完成了,后面的任务才能执行 ,这个和上面的那些差不多

    针对异常处理

    public class ExceptionExample {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            CompletableFuture completableFuture= CompletableFuture.supplyAsync(()->{
                throw  new RuntimeException("occur exception");
            });/*.runAfterBoth(CompletableFuture.supplyAsync(()->"message"),()->{
                System.out.println("I have done all");
            });*/
            //这个时候获取就会抛出异常
            /*System.out.println(completableFuture.get());*/
            completableFuture.whenComplete((rs,exception)->{
                if (exception!=null){
                    System.out.println("前置任务正常异常");
                }else {
                    System.out.println("前置任务正常执行!!"+rs);
                }
            });
            System.out.println(completableFuture.handleAsync((result, exception) -> exception != null ? "任务异常" : result).get());
        }
    }

     应用在实际中(模拟一个查询商品,但是现实中应该不会这样用,这里只是把上面的这些东西整合使用一下)

    @Data
    @ToString
    public class Commodity {
        public Commodity(Integer id, String name, BigDecimal price) {
            this.id = id;
            this.name = name;
            this.price = price;
        }
        Integer id;
        String name;
        BigDecimal price;
        Integer repo;
        Integer buyerNum;
        List<String> remarks;
    }

    几个模拟获取数据的service

    @Service
    public class CommodityService {
        List<Commodity> getRemarkById(){
            return Arrays.asList(
                    new Commodity(1,"电视",new BigDecimal(500)),
                    new Commodity(2,"手机",new BigDecimal(1000)),
                    new Commodity(3,"电脑",new BigDecimal(2000)),
                    new Commodity(4,"台灯",new BigDecimal(50)),
                    new Commodity(5,"水杯",new BigDecimal(56))
            );
        }
    
    }
    @Service
    public class RemarkService {
        List<String> getRemarkById(Integer goodId){
            return Arrays.asList("好","非常好","还可以");
        }
    }
    @Service
    public class RepoService {
        Integer getRepoById(Integer goodId){
            return new Random().nextInt(1000);
        }
    
    }

    controller层,对上面的技术进行使用

    /**
     * @author : lizi
     * @date : 2021-07-02 23:34
     **/
    @RestController
    public class GoodsController {
        @Autowired
        CommodityService commodityService;
        @Autowired
        RemarkService remarkService;
        @Autowired
        RepoService repoService;
    
        @GetMapping("/goods")
        public List<Commodity> goods() throws ExecutionException, InterruptedException {
            //获取物品列表
            CompletableFuture<List<Commodity>> listCompletableFuture = CompletableFuture.supplyAsync(() -> commodityService.getRemarkById());
    
            //这里面都是在异步执行
            //thenApply 指的是获取上面的listCompletableFuture后再进行操作
            CompletableFuture<List<Commodity>> listCompletableFuture1 = listCompletableFuture.thenApply(goods -> {
                goods.stream().map(goods1 -> CompletableFuture.supplyAsync(() -> {
                    goods1.setRepo(repoService.getRepoById(goods1.getId()));
                    return goods1;
                }).thenCompose(goods2 -> CompletableFuture.supplyAsync(() -> {
                    goods2.setRemarks(remarkService.getRemarkById(goods2.getId()));
                    return goods2;
                }))).toArray();
                return goods;
            });
    
          /*  for (;;){
                System.out.println("这里可以写我们的逻辑,但是上面的东西是交给一个线程去跑的,所以就可以直接执行向下执行,这样从某种意义上讲反应就快了");
            }*/
    
            //在这里get的时候进行阻塞
            return (List<Commodity>) listCompletableFuture1.handleAsync((good, exception) -> exception != null ? "系统繁忙" : good).get();
        }
    }

      原理分析

    他是基于cas的无锁并发栈(Completion是一个栈结构,存储的我们传递进去的任务这里的任务指的是我们传递进CompletableFuture中方法的一系列操作)

    我们看completion的的关系类图,他是使用【forkjoin】来作为底层的,具体如下:

    比如你使用

    thenApply】:这种传递传递进来的任务会使用【UniCompletion】他下面的相关的类进行包装,

    CoCompletion】:针对两个任务的combination的实现,比如【applyToEither】的任务 so on 

    因为不同类型链式的结构执行的方式不一样,包装完成后,当【forkjoin】从上向下走的时候,他才能对不同的类型使用不同的执行策略,那是如何组装这些的呢?

      

    举个例子(这就类似一个树的样子,我们每次点出一个新的方法,他就想当于树上的树枝,然后再次点出来的则数据树枝上的叶子,或者树枝)

    /**
     * @author : lizi
     * @date : 2021-07-03 13:09
     **/
    public class PrincipleExample {
        public static void main(String[] args) {
            CompletableFuture<String> base_future = CompletableFuture.completedFuture("Base Future");
            //这个时候构建的一定是一个UniCompletion对他进行包装(因为他是一个只有单个任务的 )
            base_future.thenApply(r->"then apply:"+r);
    
            //这里的第一个thenAccept,就是入栈(Completion)到base_future中的第二个,然后他返回一个新的CompletableFuture
            //这里的第二个thenAccept则是入栈到新的CompletableFuture中
            base_future.thenAccept(r-> System.out.println(r)).thenAccept(none-> System.out.println("no result"));
    
            //这里的第一个thenApply则成为第三个入栈到到base_future中的,然后同理返回新的,thenAccept则进去新的completableFuture中,
            // 也就是现在在base_future中有三个子节点由UniCompletion进行包装,而其中的两个都有一个新的子节点,这样就类似于一个树的样子
            base_future.thenApply(r->"message").thenAccept(r-> System.out.println("where  should i go "));
    
        }
    }

     当任务执行的时候,则是逐步出栈(等于一个个树枝进行捋,如果树枝下面有还有树枝,则继续)

     

  • 相关阅读:
    入职演讲
    关于EOM(Enterprise Operating Model)企业经营模型
    程序员都很自信
    全能型的程序员
    用EOM的眼光评判‘我要做全国最最好的标准权限组件和通用权限管理软件’1
    用EOM的眼光评判‘我要做全国最最好的标准权限组件和通用权限管理软件’2
    用EOM的眼光评判‘我要做全国最最好的标准权限组件和通用权限管理软件’3
    用EOM的眼光评判‘我要做全国最最好的标准权限组件和通用权限管理软件’4
    “IT农民工”这一说法
    怎样快速确定程序员编程的水平
  • 原文地址:https://www.cnblogs.com/UpGx/p/14966185.html
Copyright © 2020-2023  润新知