java学习之路

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

2020-11-16  本文已影响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());
    }
}

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

上一篇下一篇

猜你喜欢

热点阅读