什么是Stream流式计算?
大数据:存储 + 计算
存储:集合、MySQL 本质就是存储东西的;
计算:都应该交给流来操作!
常用方法:
可以看出参数类型是我们刚刚学过的断点型函数式接口,只有参数返回值类型为boolean
等等,大多数的都是函数式接口,所以一定要先学会函数式接口再来学stream流
解析:u就是个形式参数,是从stream里面得出的泛型
推荐使用stream流,因为jdk1.8在里面做了很多优化操作,会提高效率
package com.kuang.stream;
import java.util.Arrays;
import java.util.List;
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输出一个用户!
*/
public class Test {
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",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
// 集合就是存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// 计算交给Stream流
// 这一道题体现了这四点:lambda表达式、链式编程(一条链下来)、函数式接口、Stream流式计算
list.stream()//把list转成stream
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})//从这一步开始stream的泛型变成了(stream)
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//倒序
.limit(1)//只输出一个
.forEach(System.out::println);
}
}
什么是ForkJoin(中文叫:分支合并)
ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。
在大数据量去提高效率!他是一个线程并发成多个去操作的
大数据:Map Reduce (把大任务拆分为小任务)
这个里面维护的都是双端队列(两端都可以操作,AB都可以从上下两端进去操作)
线程B执行完了之后,不让他等待着,他就会去把A线程的任务偷过来执行,可以提高效率这就叫工作窃取。
查看官方文档
如何使用 forkjoin
1、forkjoinPool 通过它来执行
2、计算任务 forkjoinPool.execute(ForkJoinTask task)
3. 计算类要继承 ForkJoinTask
package com.kuang.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* 求和计算的任务!
* 3000 6000(ForkJoin) 9000(Stream并行流)
* // 如何使用 forkjoin
* // 1、forkjoinPool 通过它来执行
* // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
* // 3. 计算类要继承 ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start; // 1
private Long end; // 1990900000
// 临界值 就是操作这个要分成两个线程
private Long temp = 10000L;
public ForkJoinDemo(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 { // forkjoin 递归
long middle = (start + end) / 2; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); // 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork(); // 拆分任务,把任务压入线程队列
return task1.join() + task2.join();
}
}
}
测试:
package com.kuang.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
/**
* 同一个任务,别人效率高你几十倍!
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1(); // 12224
// test2(); // 10038
// test3(); // 153
}
// 普通程序员
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
// 会使用ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
public static void test3(){
long start = System.currentTimeMillis();
// Stream并行流 () (]
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+"时间:"+(end-start));
}
}
Future 设计的初衷: 对将来的某个事件的结果进行建模
JUC包下的Future的实现类CompletableFuture(看配勒博Future)用他去进行异步调用
类似于Ajax
无返回值:CompletableFuture.runAsync();
有返回值:CompletableFuture.supplyAsync();
成功completableFuture.whenComplete()
失败exceptionally()
最后再get方法
package com.kuang.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* 异步调用: CompletableFuture
* // 异步执行
* // 成功回调
* // 失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 没有返回值的 runAsync 异步回调
// CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
// });
//
// System.out.println("1111");
//
// completableFuture.get(); // 获取阻塞执行结果
// 有返回值的 supplyAsync 异步回调
// ajax,成功和失败的回调
// 返回的是错误信息;
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); // 正常的返回结果
System.out.println("u=>" + u); // 错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233; // 可以获取到错误的返回结果
}).get());
/**
* succee Code 200
* error Code 404 500
*/
}
}
无返回值输出:
有返回值成功输出:
有返回值失败输出:
错误回调方法,Function指定参数泛型是Throable类,返回类型自己写
public CompletableFuture<T> exceptionally(
Function<Throwable, ? extends T> fn) {
return uniExceptionallyStage(fn);
}
之前stream流的map方法,Function的参数和返回值都未指定
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
写这种东西要从源码开始看
请你谈谈你对 Volatile 的理解
Volatile 是 Java 虚拟机提供轻量级的同步机制(synchroized)
1、保证可见性
2、不保证原子性
3、禁止指令重排
当别人问你怎么保证可见性的就说JMM
什么是JMM
JMM : Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的新值到工作内存中!
3、加锁和解锁是同一把锁
线程 :工作内存 、主内存
这边write和store写反了,先store再write
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类 型的变量来说,load、store、read和write操作在某些平台上允许例外)
JMM对这八种指令的使用,制定了如下规则:
问题: 程序不知道主内存的值已经被修改过了,当A线程修改了工作内存的变量写入到主内存时,线程B还不知道该值被改变过,不可见。
package com.kuang.tvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
// 不加 volatile 程序就会死循环!
// 加 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) { // main
new Thread(()->{ // 线程 1 对主内存的变化不知道的
while (num==0){ //会一直在这里循环
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
原子性 : 不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
package com.kuang.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
// volatile 不保证原子性
public class VDemo02 {
// volatile 不保证原子性
private volatile static int num = 0;
public static void add(){
num++; // 不是一个原子性操作
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ // main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
如果不加 lock 和 synchronized ,怎么样保证原子性?
底层,就是num++拿到这个值 +1然后写回,多个线程拿到也是一样的
使用原子类,解决 原子性问题
非常高效,比同步锁高效很多倍
上面那两个锁更耗费资源
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
并不是一个简单的+1操作,而是用的底层的CAS CPU的并发原理
package com.kuang.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
// volatile 不保证原子性
public class VDemo02 {
// volatile 不保证原子性
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; // 不是一个原子性操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ // main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
我们来看一下AtomicInteger的自增方法getAndIncrement的底层:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
他返回了一个unsafe的对象去执行这个方法
private static final Unsafe unsafe = Unsafe.getUnsafe();
然后我们进入Unsafe的类中可以发现他属于sun包,然后其中的方法多是native方法,之后深入理解CAS会讲——》在juc并发编程四讲
java.util.curcurrent.atomic包(JUC原子包)中的这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!
什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码到执行的顺序:
源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
我们所期望的执行顺序:1234 但是可能执行的时候回变成 2134 1324
可不可能是 4123! 不会
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
可能造成影响的结果: a b x y 这四个值默认都是 0;
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果: x = 0;y = 0;但是可能由于指令重排
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的诡异结果: x = 2;y = 1;
指令重排他是一个概念,这种你可能写1000w次代码不会出现,但是理论上逻辑上他是有可能会出现的
非计算机专业只要理解一部分就行了
volatile可以避免指令重排:
系统中有一个叫内存屏障他就是CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)
写上Voliate就能加上这么个内存屏障
Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
Volatile的内存屏障在哪里使用得最多,一定要回答在单例模式中使用的最多
饿汉式 DCL懒汉式(Volatile),深究!
饿汉式单例顾名思义很饿,很饿就是他一上来(初始化)就可以把这个对象给加载
代码顺序:
1、单例模式最重要的思想:构造器私有
一旦构造器私有,别人就无法去new这个对象了,保证内存中只有这一个对象
2、而饿汉式单例就是一上来(初始化)直接给他这个对象给new出来,这就能保证他是唯一的了
3、然后抛出一个对外的方法去拿到这个对象
那么饿汉式会不会有什么问题?
会不会浪费内存?
然后我们设置一些数组,这四组数据非常耗内存资源
饿汉式他一上来就把这些内存里面的东西全部加载上来,然后这些数组对象的空间又是没有被使用的
然后就可以造成浪费空间。
解决方案:于是乎出来了一个叫懒汉式单例
package com.kuang.single;
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
代码顺序:
1、先构造器私有
2、因为是懒汉式单例所以不是一开始就加载对象,而是先把对象放在这里,只创建不实例化。
3、然后给一个对外能拿到对象的方法(不同点:判断如果上面对象为空,才去new创建对象,否则直接返回lazyMan)
那么这个懒汉式有什么问题呢?
单线程的话确实单例没问题,
但是我们是多线程并发
测试:
写循环10次线程去拿到这个对象,但是偶尔成功拿到偶尔失败,失败次数比较多,每次结果不一样
这个时候我们要进阶要加一把锁,这个时候我们需要
双层判断,如果lazyMan==null先上一把锁,锁LazyMan.class静态对象(唯一)
双重检测锁模式的懒汉式单例就叫做:DCL懒汉式单例
但是这个代码还不安全,之前讲过一个词叫voliate,这个new LazyMan不是一个原子性操作(看起来一步其实底层会进行三步操作)
不是原子性操作会经历的步骤1、分配内存空间2、执行构造方法,初始化对象3、把这个对象指向这个空间
这个时候会底层有可能会发生指令重排的现象,比如我们希望刚刚的步骤是123 然后有可能编程132,如果在多个线程的情况下,A132,但是又来了个线程B,因为这个对象已经指向这个内存空间了,所以他会走另外一个判断方法直接return一个lazyman对象,但是此时这个lazyman还没有完成构造
所以这个时候应该在创建LazyMan对象的地方加个volatile
package com.kuang.single;
import com.sun.corba.se.impl.orbutil.CorbaResourceUtil;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
// 懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
for(int i = 0;i<10; i ++){
new Thread(() ->{
LazyMan.getInstance();
}).start();
}
}
/**
* 1. 分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 A
* B // 此时lazyMan还没有完成构造
*/
}
单例模式的静态内部类
先构造器私有
再创建一个静态内部类,内部类里面直接实例化对象
然后再来一个方法给外部调用这个静态内部类的对象
package com.kuang.single;
// 静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstace(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
注意:
单例不安全,反射
反射:霸道,只要有反射任何私有的关键词都是纸老虎
反射破坏:过程先用自写的getInstance方法去获得对象,然后反射获得对象然后打印出可以看到不是同一个对象
保护:这时候给LazyMan的构造方法里面加把锁,然后锁里面增加判断表示当有人通过自写的getInstance方法去获得对象后,对象不为null了再想通过反射创建就抛出异常,这时候就是三种检测,避免了某一种反射的失败
反射破坏策略2:但是这个时候如果两个对象都是反射创建出来的,那么还是可以破坏这个单例
保护策略2:用红绿灯方法给他声明一个boolean值变量只让他创建一次 ,这时候想用反射new两个就不行了
再反射破坏:可以用反射获得他的属性,然后破坏这个属性,在创建完之后再更改这个属性的值(false)就又可以通过反射创建多个对象 这时候又可以创建多个对象了单例破坏
package com.kuang.single;
import com.sun.corba.se.impl.orbutil.CorbaResourceUtil;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
// 懒汉式单例
// 道高一尺,魔高一丈!
public class LazyMan {
private static boolean qinjiang = false;
private LazyMan(){
synchronized (LazyMan.class){
if (qinjiang == false){
qinjiang = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
/*if(lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏异常");
}*/
}
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
// 反射!
public static void main(String[] args) throws Exception {
// LazyMan instance = LazyMan.getInstance();
Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
qinjiang.setAccessible(true);
//拿到对象得到他的构造器然后给个null因为是个空参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//这个方法无视了私有的构造器
declaredConstructor.setAccessible(true);
//通过反射创建对象
LazyMan instance = declaredConstructor.newInstance();
qinjiang.set(instance,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
输出:可以看到后面的值证明他们已经不是一个值了,按照单例来说他们应该是一样的才对
得出结论:反射可以破坏这种单例
com.kuang.single.LazyMan@677327b6
com.kuang.single.LazyMan@14ae5a5
// 道高一尺,魔高一丈!
这个时候咋解决???
看源码:newInstance
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
Cannot reflectively create enum objects 不能用反射去创建枚举对象
枚举是jdk1.5出来的,自带单例模式
enum 是一个什么? 本身也是一个Class类
测试上面是否是一个单例模式:可以看到输出的是同一个值,所以只要用一个INSTANCE,那么他就是个天然的单例模式类
package com.kuang.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
// enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE; // 这样就默认是个单例的,这时候对象就不会被人拿走了
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
// NoSuchMethodException: com.kuang.single.EnumSingle.()
System.out.println(instance1);
System.out.println(instance2);
}
}
因为上面说的是反射不能创建枚举类,然后我们来试一下看到底能不能破坏创建
测试报错了,说是枚举类里面没有一个这样的无参构造方法,但是我们都知道无参构造方法是JVM自动创建的,我们看class(字节码)文件里面也是有的,正常的话他应该给我们报个Cannot reflectively create enum objects 不能用反射去创建枚举对象
先看一下这个字节码文件里面是有的,这里有个无参构造 证明idea骗了我们
然后我们就要去源码里面分析,用idea进入文件夹,然后打开命令行
查看源码:java -p 字节码文件全名
然后可以看到这里也有一个空参的构造,说明这个也骗了你,因为程序最后的执行结果是不会骗人的
这个时候我们就要去找一个更专业的工具,可以用jad去反编译class类
把jad拿到当前文件夹下
然后把这个源码反编译成java文件
命令:jad -sjava 字节码文件全名
枚举类型的终反编译源码:
可以看到里面只有一个构造函数private EnumSingle(String s, int i)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.kuang.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/kuang/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
然后我们再用有参的构造器去获得这个构造器,这个时候报错
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.kuang.single.Test.main(EnumSingle.java:23)
这个错误才是正确的表示反射确实不能破坏创建枚举