Java Util Concurrent并发编程(四)四大函数式
在这个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值。我们可以用来放一些比较的逻辑或者判断逻辑的。
这里简单写代码做个测试(不要问我为什么不直接返回,我这里没有逻辑,就是表示下用法!):
断定型接口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());
}
}
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!