12、JDK1.8 部分新特性
一、四大函数式接口(必需掌握)
新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
一般情况是有一个方法的接口,并非绝对
函数式接口在框架底层比较多,可以简化代
JDK1.8 新特性
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
1、常规接口 Function
常规函数式接口
接收 T 类型参数,输出 R 类型结果
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
//T : 参数类型
//R : 返回值类型
- 示例
public class FunctionDemo01 {
public static void main(String[] args) {
/*Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String str) {
return str;
}
};*/
Function function = str -> {return str;};// lambda 表达式
System.out.println(function.apply("adaffa2342423f"));
}
}
2、断定型接口 Predicate
接收一个 T 类型参数,输出 布尔类型值
public class PredicateDemo {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>(){
@Override
public boolean test(String str) {
return str.length() > 5 ? true : false;
}
};
// lambda 表达式
Predicate<String> predicate1 = (str) -> {return str.length() > 5 ? true : false;};
System.out.println(predicate1.test("afaf"));
}
}
3、供给型接口 Supplier
不需要输入,有返回值
public class SupplierDemo01 {
public static void main(String[] args) {
/* Supplier<String> supplier = new Supplier<String>(){
@Override
public String get() {
return DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now());
}
};*/
Supplier<String> supplier = () -> {return DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now());};
System.out.println(supplier.get());
}
}
4、消费型接口 Consumer
接收一个 T 类型参数,没有返回
public class ConsumerDemo01 {
public static void main(String[] args) {
/* Consumer<String> consumer = new Consumer<String>(){
@Override
public void accept(String str) {
System.out.println(str);
}
};*/
//lambda 表达式
Consumer<String> consumer = (str) -> {System.out.println(str);};
consumer.accept("afafa212414f");
}
}
5、区别
全部位于 java.util.function 包下
类型 | 接口类 | 方法 | 输入参数类型 | 返回值类型 |
---|---|---|---|---|
常规 | Function | R apply(T t) | T | R |
断定值 | Predicate | boolean test(T t) | T | boolean |
供给型 | Supplier | T get() | - | T |
消费型 | Consumer | void accept(T t) | T | - |
二、Stream 流式计算
package functionInterface;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
/**
* @author ajun
* Date 2021/7/7
* @version 1.0
* 流式计算
* --------------------
* 输出要求:
* 1、ID 为偶数
* 2、年龄大于23
* 3、名字大写
* 4、名字按字母倒序排列
* 5、只输出2个
*/
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 20);
User u4 = new User(4, "d", 25);
User u5 = new User(5, "e", 23);
User u6 = new User(6, "f", 24);
List<User> list = Arrays.asList(u1, u2, u3, u4, u5,u6);
//lambda表达式、链式编程、函数式接口、Stream流式计算
list.stream()
.filter((u) -> {return u.getId() % 2 == 0;})//filter 参数是断定型函数式接口
.filter((u) -> {return u.getAge() > 23;})
//.map((u) -> {return new User(u.getId(),u.getName().toUpperCase(),u.getAge());})
.map((u) -> {u.setName(u.getName().toUpperCase());return u;})
.sorted((uu1,uu2) -> {return uu2.getName().compareTo(uu1.getName());})
.limit(2)
//.forEach((u) -> {System.out.println(u);});//forEach 参数是消费型函数式接口
.forEach(System.out::println);//forEach 参数是消费型函数式接口。双冒号是简写
}
}
//lombok
@Data //pojo 数据模型
@NoArgsConstructor //无参构造器
@AllArgsConstructor //全参构造器
class User{
private int id;
private String name;
private int age;
}
//输出结果
User(id=6, name=F, age=24)
User(id=4, name=D, age=25)
知识点:
双冒号:: 表示方法引用 (JDK1.8新特性)
静态方法引用(static method)语法:classname::methodname 例如:Person::getAge
对象的实例方法引用语法:instancename::methodname 例如:System.out::println
对象的超类方法引用语法: super::methodname
类构造器引用语法: classname::new 例如:ArrayList::new
数组构造器引用语法: typename[]::new 例如: String[]::newforEach((u) -> {System.out.println(u);});//forEach 参数是消费型函数式接口 forEach(System.out::println);//forEach 参数是消费型函数式接口。双冒号是简写
13、分支合并 ForkJoin
1、什么是ForkJoin?
ForkJoin在JDK1.7,并行执行任务,大数据量!
大数据:Map Reduce( 把大任务拆分成小任务)
2、ForkJoin特点
工作窃取
内部维护的是一个双端队列
当一个子任务先运行完之后,会帮助其它任务运行;从其它任务的另一端进入;
3、类/接口 关系图
4、ForkJoinPool
分支合并池
5、ForkJoinTask
分支合并任务
recursive
英 [rɪˈkɜːsɪv] 美 [rɪˈkɜːrsɪv]
adj. 递归的;循环的
6、示例
用三种方法求和计算:
1、传统方法
2、ForkJoin
3、并行流
- ForkJoin 任务类
package forkJoin;
import java.util.concurrent.RecursiveTask;
/**
* @author ajun
* Date 2021/7/7
* @version 1.0
* 分支合并任务
* -------------
* 求和计算
* 通过forkjoinpool来执行
* 计算任务forkJoinPool, execute(forkjoinTask task),submit(forkjoinTask task)
* 计算类要继承自 forkjointask
*/
public class ForkJoinTaskDemo extends RecursiveTask<Long> {
private long start;//起始值
private long end;//结束值
private long temp = 1_0000L;//临界值
//构造器
public ForkJoinTaskDemo(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if ((end - start) < temp) {//不拆分
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {//分支合并计算
long middle = (start + end) / 2;//中间值
ForkJoinTaskDemo task1 = new ForkJoinTaskDemo(start, middle);
task1.fork();//拆分任务,把任务压入线程队列
ForkJoinTaskDemo task2 = new ForkJoinTaskDemo(middle + 1, end);
task2.fork();//拆分任务,把任务压入线程队列
return task1.join() + task2.join();//合并子任务的结果
}
}
}
- 测试类
package forkJoin;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* @author ajun
* Date 2021/7/7
* @version 1.0
*/
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1(0L,50_0000_0000L);
test2(0L,50_0000_0000L);
test3(0L,50_0000_0000L);
}
//传统方法求和
public static void test1(Long start,Long end) {
Instant instant1 = Instant.now();//开始时间
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
Instant instant2 = Instant.now();//结束时间
Duration duration = Duration.between(instant1, instant2);//时间差
System.out.println("耗时:" + duration.toMillis() + " 毫秒");
System.out.println("sum = " + sum);
}
//分支合并求和
public static void test2(Long start,Long end) throws ExecutionException, InterruptedException {
Instant instant1 = Instant.now();//开始时间
ForkJoinPool forkJoinPool = new ForkJoinPool();//定义ForkJoin池子
ForkJoinTaskDemo forkJoinTast = new ForkJoinTaskDemo(start, end);//定义ForkJoin任务
ForkJoinTask<Long> result = forkJoinPool.submit(forkJoinTast);//执行任务
Long sum = result.get();//得到结果
Instant instant2 = Instant.now();//结束时间
Duration duration = Duration.between(instant1, instant2);//时间差
System.out.println("耗时:" + duration.toMillis() + " 毫秒");
System.out.println("sum = " + sum);
}
//并行流 stream 求和
public static void test3(Long start,Long end) {
Instant instant1 = Instant.now();//开始时间
/*
range(): 从 start 到 end ,不包含 end
rangeClosed(): 从 start 到 end ,包含 end
parallel():并行执行
reduce():根据一定的规则将Stream中的元素进行计算后返回一个唯一的值
*/
long sum = LongStream.rangeClosed(start, end).parallel().reduce(0, Long::sum);
Instant instant2 = Instant.now();//结束时间
Duration duration = Duration.between(instant1, instant2);//时间差
System.out.println("耗时:" + duration.toMillis() + " 毫秒");
System.out.println("sum = " + sum);
}
}
//运行结果
耗时:1556 毫秒
sum = -5946744071209551616
耗时:1499 毫秒
sum = -5946744071209551616
耗时:808 毫秒
sum = -5946744071209551616
14、异步调用
Future设计的初衷: 对将来的某个事件的结果进行建模
1、无返回值
runAsync
package completableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @author ajun
* Date 2021/7/7
* @version 1.0
* 异步调用
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
}
//无返回值异步调用
public static void test1() throws ExecutionException, InterruptedException {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 异步调用。无返回值");
});
System.out.println("11111");
future.get();
}
}
//返回结果
11111
ForkJoinPool.commonPool-worker-1异步调用。无返回值
2、有返回值
supplyAsync
/*
* 有返回值的异步调用
* 有成功回调,有异常(失败)回调
*/
public static void test2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
//System.out.println("供给型接口");
//int i = 10/0;
return 1024;
});
System.out.println("111");
//得到结果值
//1024 : 成功
//200 : 失败
Integer result = future.whenComplete((t, u) -> {
//成功方法
//System.out.println(t);//正常的返回结果
//异常方法
//System.out.println(u);异常的返回结果
}).exceptionally((e) -> {//异常时
//e.printStackTrace();
//System.out.println(e.getMessage());
return 200;
}).get();
System.out.println("结果:"+result);
}
- 简易写法
/*
* 有返回值的异步调用
* 有成功回调,有异常(失败)回调
*/
public static void test2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
//System.out.println("供给型接口");
//int i = 10/0;
return 1024;//成功时
}).exceptionally((e) -> {
return 200;//失败时
});
System.out.println("111");
Integer result = future.get();
System.out.println("结果:"+result);
}
15、JMM
1、引言
面试:对 Volatile 的理解
volatile
英 [ˈvɒlətaɪl] 美 [ˈvɑːlətl]
adj.易变的;无定性的;无常性的;可能急剧波动的;不稳定的;易恶化的;易挥发的;易发散的
Volatile 是java虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
2、什么是JMM?
JMM: java内存模型 (Java Memory Model)
是不存在的东西,只是一种概念!一种约定!
3、JMM的一些同步约定
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁必须是同一把锁
4、线程、工作内存、主内存
5、四对(8种)操作
组 | 操作 | 说明 |
---|---|---|
1 | lock加锁 | 作用于主内存的变量,把一个变量标识为线程独占状态 |
1 | unlock解锁 | 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 |
2 | read 读取 | 作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用 |
2 | load载入 | 作用于工作内存的变量,它把read操作从主存中变量放入工作内存中 |
3 | use 使用 | 作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令 |
3 | assign赋值 | 作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中 |
4 | store 存储 | 作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用 |
4 | write 写入 | 作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中 |
assign
英 [əˈsaɪn] 美 [əˈsaɪn]
v. 分配(某物);分派,布置(工作、任务等);指定;指派;委派;派遣
6、八种规则
编号 | 规则 |
---|---|
1 | 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write |
2 | 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存 |
3 | 不允许一个线程将没有assign的数据从工作内存同步回主内存 |
4 | 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作 |
5 | 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁 |
6 | 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值 |
7 | 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量 |
8 | 对一个变量进行unlock操作之前,必须把此变量同步回主内存 |
16、Volatile
1、保证可见性
package volatileTest;
import java.util.concurrent.TimeUnit;
/**
* @author ajun
* Date 2021/7/8
* @version 1.0
* volatile 可见性测试
*/
public class Demo01 {
//不加volatile 程序就会死循环
//加上volatile 可以保证可见性
private static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while(num == 0){
//num = 0 时,线程一直循环执行
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
1)非原子性测试
package volatileTest;
/**
* @author ajun
* Date 2021/7/8
* @version 1.0
* volatile 原子性测试
*/
public class Demo02 {
private volatile static int num = 0;
private static void add(){//加法
num++;
}
public static void main(String[] args) {
//开启20个线程,每个线程执行1000次add
//最后num的理论值应该是 20000
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
//循环判断线程数量,大于2时就说明加法计算没有执行完,就一直等待
while(Thread.activeCount() > 2){//程序中至少有两个线程存在:主线程main、垃圾回收线程gc
Thread.yield();
}
System.out.println(num);
}
}
//输出结果
19991
2)原因分析
对class文件反编译后发现,add方法中的 num++ 操作,被分成好几步来完成,在多线程情况下,这几步不是一个整体,不具有原子性,多个线程中这几步操作可能会穿插执行
反编译
javap -c -private Demo02
3)实现原子性
- 加 lock 和 synchronized (不推荐)
- 使用原子类
可以不加volatile
这些类的底层都直接和操作系统挂钩 ! 在内存中修改值! UnSafe类是一个很特殊的存在
private volatile static AtomicInteger num = new AtomicInteger(0);//原子类
private static void add(){//加法
num.getAndIncrement();//加1
}
3、禁止指令重排
什么是指令重排?
你写的程序,计算机并不是按照指定的的步骤执行,有可能会重新排序后执行
源代码 —> 编译器优化源代码 –> 指令并行也可能会重排 —> 内存系统也会重排 —> 执行
volatile可以避免指令重排:
内存屏障: CPU指令, 作用:
- 保证特定的操作的执行顺序!
- 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
4、总结
Volatile 是可以保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排的现象产生 !