美文网首页java学习之路
Java Util Concurrent并发编程(四)四大函数式

Java Util Concurrent并发编程(四)四大函数式

作者: 唯有努力不欺人丶 | 来源:发表于2020-11-16 19:50 被阅读0次

    在这个java15都出来了的年代,我偶尔还能听到一种说法叫java8新特性,有点太扯了。
    现在的程序员必要技能:lambda表达式,链式编程,函数式接口,stream流。
    有好多人会说公司不让用,但是让不让用是一方面,会不会是另一方面。起码要做到看得懂别人的代码吧?好的,下面开始讲正题。

    四大函数式接口

    函数式接口:只有一个方法的接口。其中典型的就是 Runnable.Callable之类的。这类接口上面会有一个注解:@FunctionalInterface

    这里我们还是要去官方手册上看的。java中有个包叫java.util.function。我们可以点进去看一下这里面所有的都是接口,而我们所说的四大函数式接口也就是这里最基本的四个接口,如下图:

    四大函数式接口
    函数型接口Function
    我们要一个个去代码中查看是什么,怎么用。先去手册上看一波介绍:
    Function的用法
    然后点进Function源码中看一下,如下图:
    只有一个待实现方法
    注意我说的是只有一个待实现方法,也就是抽象方法。如果你们看了发现明明有很多方法有点懵逼的话可以注意,别的方法都是有具体的实现的(java8中接口可以有方法的实现了)!而这个抽象方法的作用就是输出一个T类型参数,返回一个R类型参数。主要就是用作用工具的简便实用。
    下面贴一下简单的使用demo:
        public static void main(String[] args) throws Exception {
            Function<String, Integer> function = (str)->{return Integer.valueOf(str);};
        }
    

    断定型接口Predicate
    这个其实我们也可以点进源码看一下,输入一个泛型,返回一个boolean值。我们可以用来放一些比较的逻辑或者判断逻辑的。

    image.png
    这里简单写代码做个测试(不要问我为什么不直接返回,我这里没有逻辑,就是表示下用法!):
    断定型接口Demo
    消费型接口Consumer
    这个接口只有一个输入类型,没有返回值。这个就是消费了
    源码
    下面是简单的使用demo:
    测试demo
    供给型接口Supplier
    源码中可以看到,这个和消费型正好相反,是没有参数,有返回值
    源码
    下面简单的测试一波:
    获取随机串
    其实函数式接口几乎都是从这四个类型中演变出来的。消费供给,函数断定。几乎满足了所有的方法了吧,这四个接口比较容易记住的。下面说下一个知识点:流式计算。

    Stream流式计算

    先去看官方手册肯定是没错的,我们去看看什么是Stream流。


    Stream流

    Stream中介绍的非常多,就不一一的在这里列出来了,简单来说我是理解为一个构造器了。这个和sql语句就很像,可以筛选,可以排序,可以获取对象的某一个属性。你看sql语句:从成千上万条记录的表中,经过一系列的操作,给出了我们想要的数据。其中经过筛选,经过排序,还可以经过分页,还可以只获取指定的列的字段。本质上是差不多的。下面直接说使用方法:


    条件过滤
    排序
    分页

    Forkjoin(分支合并)

    **这个是jdk1.7出现的。用于并行执行任务,大任务拆成一个个小任务,最终把小任务的结果集汇总。 **


    Forkjoin

    Forkjoin特点:工作窃取(因为是双端队列)。


    工作窃取原理B执行完偷A最后一个任务执行
    下面步入整体Forkjoin,来一波手册上的介绍:
    如图可见,ForkjoinPool

    其实这个虽然不完全一样,但是我们可以猜出这个用处,毕竟ForkJoinPool中Pool本身是池的意思。这个就是任务池。这个是在juc包下的一个类。我们继续看这个类的方法:


    这个execute方法就是执行
    继续往下看执行参数,ForkJoinTask。点进去瞅瞅:
    ForkJoinTask介绍
    方法详解。
    其实这个fork,get都是经常使用方法,这个图没截下来,还有一个join是当完成后返回结果。
    暂时来说我们知道了这个类的简单说明和使用方法。然后去代码中使用一下吧:
    下面是一个forkJoin的简单使用demo和现阶段求和的几种方法:
    ForkJoin的实现:
    /**
     * 继承有返回值类型的
     * @author lisijia
     *
     */
    public class Task extends RecursiveTask<Long>{
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        long start;
        long end;
        //临界值。10000以上拆分,一万以下不拆分
        long temp = 100000L;
    
        public Task(long start, long end) {
            this.start = start;
            this.end = end;
        }
        
        @Override
        protected Long compute() {  
            //超过临界值的递归拆分
            if((end-start)<temp) {
                long sum = 0;
                for(long i = start;i<=end;i++) sum += i;
                return sum;         
            }else {
                long mid = (end-start)/2+start;
                ForkJoinTask<Long> forkJoinTask1 = new Task(start, mid);
                forkJoinTask1.fork();
                ForkJoinTask<Long> forkJoinTask2 = new Task(mid+1, end);
                forkJoinTask2.fork();
                return  forkJoinTask1.join()+forkJoinTask2.join();
            }       
        }
    }
    

    其实说简单也挺麻烦的,但是却也不难理解。就是把一个任务拆分成多个然后节省时间。而且拆分的临界点是自己定义的,还可以调优什么的。其中的用法就是如下,这里fork方法,join方法上面都说过了。
    下面贴出来的是三种求和的实现(性能最好的是流计算!)直接贴代码:

    public class D {
        /**
         * 求和计算的任务
         * @param args
         * @throws Exception
         */
        public static void main(String[] args){
            test1();//笨方法累加
            test2();//forkJoin
            test3();//流计算
        }
        public static void test1() {
            long start = System.currentTimeMillis();
            long sum = 0l;
            for(int i = 0;i<1000000000;i++) {
                sum += i;
            }
            long end = System.currentTimeMillis();
            System.out.println("test1:sum="+sum+",时间="+(end-start));
        }
        public static void test2() {
            long start = System.currentTimeMillis();        
            try {
                long sum = new ForkJoinPool().submit(new Task(0l,1000000000l)).get();
                long end = System.currentTimeMillis();
                System.out.println("test2:sum="+sum+",时间="+(end-start));
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
        public static void test3() {
            long start = System.currentTimeMillis();
            long sum = LongStream.rangeClosed(0l, 1000000000l).parallel().sum();
            long end = System.currentTimeMillis();
            System.out.println("test2:sum="+sum+",时间="+(end-start));
        }   
    }
    

    上面三种大家可以自己跑跑试试。1性能最差,3性能最好。2居中。我就不一一截图了,然后这里forkJoin的使用就到这里了,下一个知识点。

    异步回调

    其实我们之前也说过这块,就是在讲缓存FutureTask的时候。是可以先运行起来,然后在一段时间后去取结果的。反正说到这首先还是去手册看吧:


    Future介绍

    其实这个类挺好玩的,第一次讲这个,但是子类我们差不多都认全了。红色框起来的都说过了,上面ForkJoin讲了三次,FutureTask之前讲了,这里主要是看看黄色框起来的子类:
    首先我们可以简单的去看下官方手册:


    官方介绍
    其实这个看起来就比较晦涩。我们在代码中简单使用一下就能清楚了:
    • 这个类是有个泛型的。这个泛型其实是返回值类型。

    这个是没有返回值的使用方法:

        public static void main(String[] args) throws Exception {
            CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
                System.out.println("执行了!");
            });
            completableFuture.get();
        }
    

    有返回值的方法其实正确的返回值和错误的返回值都要自己定义。这块的知识是连续的,要先从前面的函数式接口中看到,供给型接口:没有参数,有返回值。
    使用这样的方法使得执行有返回值。并且可以get到。而且这个供给型接口是进化版的,有两个参数,分别是正常执行的返回值和非正常执行的返回值,说了这么多直接上代码:


    有返回值的参数

    上面的代码是为了让我们看到错误的代码走向。t正常返回值的null。u错误返回值是打印的e的信息。返回值是exceptionally中的500.
    其实去掉那个10/0的代码,就可以看到正确的打印。我也附上截图:


    正确的打印结果
    再把实现代码贴上来。感兴趣的可以自己去试试:
    public class D1 {
        public static void main(String[] args) throws Exception {
            CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
                System.out.println("执行了!");
                return "ok";
            });
            //像是ajax一样,正确返回正确的结果,错误返回错误的结果。异常打印异常信息
            System.out.println(completableFuture.whenComplete((t,u)->{
                System.out.println("t:"+t);
                System.out.println("u:"+u);
            }).exceptionally((e)->{return "500";}).get());
        }
    }
    

    本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!

    相关文章

      网友评论

        本文标题:Java Util Concurrent并发编程(四)四大函数式

        本文链接:https://www.haomeiwen.com/subject/wrnpbktx.html