JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)

十三、Stream流式计算 (必修掌握)

什么是Stream流式计算?
大数据:存储 + 计算
存储:集合、MySQL 本质就是存储东西的;
计算:都应该交给流来操作!
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第1张图片
常用方法:
在这里插入图片描述
可以看出参数类型是我们刚刚学过的断点型函数式接口,只有参数返回值类型为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(中文叫:分支合并)
ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。
在大数据量去提高效率!他是一个线程并发成多个去操作的
大数据:Map Reduce (把大任务拆分为小任务)

ForkJoin拆分:
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第2张图片

ForkJoin 特点:工作窃取

这个里面维护的都是双端队列(两端都可以操作,AB都可以从上下两端进去操作)

线程B执行完了之后,不让他等待着,他就会去把A线程的任务偷过来执行,可以提高效率这就叫工作窃取。
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第3张图片
查看官方文档
在这里插入图片描述
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第4张图片
如何使用 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
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第5张图片
无返回值: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
         */
    }
}

无返回值输出:
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第6张图片
有返回值成功输出:
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第7张图片
有返回值失败输出:
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第8张图片
错误回调方法,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);

写这种东西要从源码开始看

十六 、JMM

请你谈谈你对 Volatile 的理解

Volatile 是 Java 虚拟机提供轻量级的同步机制(synchroized)
1、保证可见性
2、不保证原子性
3、禁止指令重排

当别人问你怎么保证可见性的就说JMM

什么是JMM

JMM : Java内存模型,不存在的东西,概念!约定!

关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的新值到工作内存中!
3、加锁和解锁是同一把锁

线程 :工作内存 、主内存

8种操作(指令):

这边write和store写反了,先store再write
在这里插入图片描述
在这里插入图片描述

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类 型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量 才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便 随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机 遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变 量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中, 以便后续的write使用
  • write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内 存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须 write
  • 不允许线程丢弃他近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量 实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解 锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前, 必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题: 程序不知道主内存的值已经被修改过了,当A线程修改了工作内存的变量写入到主内存时,线程B还不知道该值被改变过,不可见。
在这里插入图片描述

十七、Volatile

1、保证可见性

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);

    }
}

2、不保证原子性

原子性 : 不可分割
线程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然后写回,多个线程拿到也是一样的
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第9张图片
使用原子类,解决 原子性问题
非常高效,比同步锁高效很多倍
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第10张图片
上面那两个锁更耗费资源

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实现了可见性)
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第11张图片
写上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;
    }

}

DCL 懒汉式

代码顺序:
1、先构造器私有
2、因为是懒汉式单例所以不是一开始就加载对象,而是先把对象放在这里,只创建不实例化。
3、然后给一个对外能拿到对象的方法(不同点:判断如果上面对象为空,才去new创建对象,否则直接返回lazyMan)

那么这个懒汉式有什么问题呢?
单线程的话确实单例没问题,
但是我们是多线程并发

测试:
写循环10次线程去拿到这个对象,但是偶尔成功拿到偶尔失败,失败次数比较多,每次结果不一样

(失败同时创建了多个线程拿到这个单例对象)
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第12张图片

这个时候我们要进阶要加一把锁,这个时候我们需要
双层判断,如果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,那么他就是个天然的单例模式类
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第13张图片

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);

    }

}

因为上面说的是反射不能创建枚举类,然后我们来试一下看到底能不能破坏创建
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第14张图片
测试报错了,说是枚举类里面没有一个这样的无参构造方法,但是我们都知道无参构造方法是JVM自动创建的,我们看class(字节码)文件里面也是有的,正常的话他应该给我们报个Cannot reflectively create enum objects 不能用反射去创建枚举对象

先看一下这个字节码文件里面是有的,这里有个无参构造 证明idea骗了我们
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第15张图片

然后我们就要去源码里面分析,用idea进入文件夹,然后打开命令行
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第16张图片
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第17张图片
查看源码:java -p 字节码文件全名
然后可以看到这里也有一个空参的构造,说明这个也骗了你,因为程序最后的执行结果是不会骗人的
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第18张图片
这个时候我们就要去找一个更专业的工具,可以用jad去反编译class类
把jad拿到当前文件夹下
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第19张图片
然后把这个源码反编译成java文件
命令:jad -sjava 字节码文件全名
JUC并发编程三(stream流式计算、ForkJoin、异步回调、JMM、Volatile、单例模式)_第20张图片
在这里插入图片描述
枚举类型的终反编译源码:
可以看到里面只有一个构造函数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)

这个错误才是正确的表示反射确实不能破坏创建枚举

你可能感兴趣的:(Java基础学习,java,java-ee,spring,maven,tomcat)