JavaSE高级:JUC并发(下)

目录

12.四大函数式接口(必需掌握)

13.Stream流式计算

14.ForkJoin

15.异步回调 

16.JMM

17.Volatile

18.玩转单例模式

19.深入理解CAS

20.原子引用

21.各种锁的理解 

1.公平锁、非公平锁

2.可重入锁

3.自旋锁

4.死锁


12.四大函数式接口(必需掌握)

新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
//超级多的@FunctionalInterface
//简化编程模型,在新版本的框架底层大量应用
//foreach()的参数也是一个函数式接口,消费者类的函数式接口

函数型接口可以使用lambda表达式;

代码测试:

Function函数型接口

/**
 * Function函数型接口
 */
public class Demo01 {
    public static void main(String[] args) {
        Function function1 = new Function<String,String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };
        System.out.println(function1.apply("starasdas"));
        //使用lambda简化
        Function<String,String> function = (str) ->{return str;};
        System.out.println(function.apply("starasdas"));

    }
}

Predicate断定型接口

/**
 * 断定型接口:有一个输入参数,返回值只能是 布尔值!
 */
public class Demo2 {
    public static void main(String[] args) {
        //判断字符串是否为空
        Predicate<String> predicate = (str)->{return str.isEmpty();};
        System.out.println(predicate.test("11"));
        System.out.println(predicate.test(""));
    }
}

Consummer 消费型接口

/**
 * 消费型接口 没有返回值!只有输入!
 */
public class Demo3 {
    public static void main(String[] args) {
        Consumer<String> consumer = (str)->{System.out.println(str);};
        consumer.accept("abc");
    }
}

Supplier供给型接口

/**
 * 供给型接口,只返回,不输入
 */
public class Demo4 {
    public static void main(String[] args) {
        Supplier<String> supplier = ()->{return "1024";};
        System.out.println(supplier.get());
    }
}

13.Stream流式计算

什么是Stream流式计算?

存储+计算

存储:集合、MySQL

计算:流式计算~

=== 链式编程 ===

用户类

public class User {
    private int id;
    private String name;
    private int age;

//get、set 有参无参
}
/*
* 题目要求
* 1.id必须是偶数
* 2.年龄必须大于25
* 3.用户名转为大写
* 4.用户名字母倒着排序
* 5.只能输出一个用户
* */
public class Test {
    public static void main(String[] args) {
        User user1 = new User(1,"a",21);
        User user2 = new User(2,"b",22);
        User user3 = new User(3,"c",23);
        User user4 = new User(4,"d",24);
        User user5 = new User(5,"e",25);
        User user6 = new User(6,"f",26);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);

        //计算交给流stream
        //链式编程!!!!
        list.stream()
                .filter((u)->{return u.getId()%2==0;})//filter按表达式过滤
                .filter((u)->{return u.getAge()>23;})
                .map((u)->{return u.getName().toUpperCase();})//map转换
                .sorted((uu1,uu2)->{//sorted排序
                    return uu2.compareTo(uu1);
                })
                .limit(1)//limit只输出指定个数
                .forEach(System.out::println);
    }
}

14.ForkJoin

什么是ForkJoin?

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:MapReduce 核心思想->把大任务拆分为小任务!

ForkJoin 特点: 工作窃取! 

实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!

如何使用ForkJoin?

  • 1.通过ForkJoinPool来执行

  • 2.计算任务 execute(ForkJoinTask<?> task)
  • 3.计算类要去继承ForkJoinTask;

 

public class ForkJoinDemo extends RecursiveTask<Long> {

    private long star;
    private long end;

    //临界值
    private long temp=10000L;

    public ForkJoinDemo(long star, long end) {
        this.star = star;
        this.end = end;
    }

    /**
     * 计算方法
     * @return Long
     */
    @Override
    protected Long compute() {
        if((end-star)<temp){
            Long sum = 0L;
            for (Long i = star; i < end; i++) {
                sum+=i;
            }
//            System.out.println(sum);
            return sum;
        }else {
            //使用forkJoin 分而治之 计算
            //计算中间值
            long middle = (star+ end)/2;
            ForkJoinDemo forkJoinDemoTask1 = new ForkJoinDemo(star, middle);
            forkJoinDemoTask1.fork();  //拆分任务,把线程任务压入线程队列
            ForkJoinDemo forkJoinDemoTask2 = new ForkJoinDemo(middle, end);
            forkJoinDemoTask2.fork();  //拆分任务,把线程任务压入线程队列
            long taskSum = forkJoinDemoTask1.join() + forkJoinDemoTask2.join();
            return taskSum;
        }
    }
}

class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    /**
     * 普通计算
     */
    public static void test1(){
        long star = System.currentTimeMillis();//获取时间 (开始)
        long sum = 0L;
        for (long i = 1L; i < 10_0000_0000; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();//获取时间 (结束)
        System.out.println("sum="+"时间:"+(end-star));
        System.out.println(sum);
    }

    /**
     * 使用ForkJoin
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
        long aLong = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
        System.out.println(aLong);
    }


    /**
     * 使用Stream 并行流
     */
    public static void test3(){
        long star = System.currentTimeMillis();
        //Stream并行流()    parallel并行计算
        long sum = LongStream.range(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
        System.out.println(sum);
    }
}

 

 .parallel().reduce(0, Long::sum)使用一个并行流去计算整个计算,提高效率。

 

reduce方法的优点:

15.异步回调 

Future 设计的初衷:对将来的某个事件结果进行建模!

其实就是前端 --> 发送ajax异步请求给后端

 (1)没有返回值的runAsync异步回调

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 发起 一个 请求
        System.out.println(System.currentTimeMillis());
        System.out.println("---------------------");
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            //发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ".....");
        });
        System.out.println(System.currentTimeMillis());
        System.out.println("1111");
        System.out.println("------------------------------");
        //输出执行结果
        System.out.println(future.get());  //获取执行结果
    }


}

(2)有返回值的异步回调supplyAsync

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //有返回值的异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
                int i = 1 / 0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1024;
        });
        //whenComplete完成
        System.out.println(completableFuture.whenComplete((t, u) -> {
            //success 回调
            System.out.println("t=>" + t); //正常的返回结果
            System.out.println("u=>" + u); //抛出异常的 错误信息
        }).exceptionally((e) -> {
            //error回调
            System.out.println(e.getMessage());//获取异常信息
            return 404;
        }).get());

    }
}

whenComplete: 有两个参数,一个是t 一个是u

T:是代表的 正常返回的结果

U:是代表的 抛出异常的错误信息

如果发生了异常,get可以获取到exceptionally返回的值;

16.JMM

请你谈谈你对Volatile 的理解

Volatile 是 Java 虚拟机提供 轻量级的同步机制

  • 1.保证可见性
  • 2.不保证原子性
  • 3.禁止指令重排

什么是JMM?

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

关于JMM的一些同步的约定:

  • 1.线程解锁前,必须把共享变量立刻刷回主存;

  • 2.线程加锁前,必须读取主存中的最新值到工作内存中;
  • 3.加锁和解锁是同一把锁;

线程中分为 工作内存、主内存

8种操作:

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

JMM对这8种操作给了相应的规定

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

问题:程序不知道主内存的值已经被修改过了

17.Volatile

1.保证可见性

public class JMMDemo01 {

    // 如果不加volatile 程序会死循环
    // 加了volatile是可以保证可见性的
    private volatile static int number = 0;

    public static void main(String[] args) {
        //main线程
        //子线程1
        new Thread(()->{
            while (number==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //子线程2
        new Thread(()->{
            while (number==0){
            }

        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        number=1;
        System.out.println(number);
    }
}

2.不保证原子性

原子性:不可分割;

线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

/**
 * 不保证原子性
 * number <=2w
 * 
 */
public class VDemo02 {

    private static volatile int number = 0;

    public static void add(){
        number++; 
        //++ 不是一个原子性操作,是两个~3个操作
        //
    }

    public static void main(String[] args) {
        //理论上number  === 20000

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){//activeCount  还存活的线程数量
            //main  gc  默认执行的
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
        //main,num=17268  结果始终到不了20000,所以volatile不保证原子性
    }
}

如果不加lock和synchronized的话,他不保证原子性,那怎么样保证原子性?

 解决方法:使用JUC下的原子包下的class;

代码如下:

public class VDemo02 {

    private static volatile AtomicInteger number = new AtomicInteger();

    public static void add(){
        //number++;//++ 不是一个原子性操作
        //number.getAndIncrement();  //底层是CAS保证的原子性
        number.incrementAndGet();

    }

    public static void main(String[] args) {
        //理论上number  === 20000

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){//activeCount  还存活的线程数量
            //main  gc  默认执行的
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+",num="+number);
        //main,num=17268  结果始终到不了20000,所以volatile不保证原子性
    }
}

这些类的底层都直接和操作系统挂钩!是在内存中修改值。

Unsafe类是一个很特殊的存在;

3.禁止指令重排

什么是指令重排?

我们写的程序,计算机并不是按照我们自己写的那样去执行的

源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行

处理器在进行指令重排的时候,会考虑数据之间的依赖性!

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4

//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的

可能造成的影响结果:前提:a b x y这四个值 默认都是0

线程A线程B
x=ay=b
b=1a=2

正常的结果: x = 0; y =0;但是可能遇到指令重排

线程A线程B
b=1a=2
x=ay=b

可能在线程A中会出现,先执行b=1,然后再执行x=a;

在B线程中可能会出现,先执行a=2,然后执行y=b;

那么就有可能结果如下:x=2; y=1.

volatile可以避免指令重排:

volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

内存屏障:CPU指令。作用:

  • 1.保证特定的操作的执行顺序;
  • 2.可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

4.总结

  • volatile可以保证可见性;
  • 不能保证原子性
  • 由于内存屏障,可以保证避免指令重排的现象产生

面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式

18.玩转单例模式

饿汉式

一上来就把所有的东西加载进来,非常浪费空间

/**
 * 饿汉式单例
 */
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懒汉式

//懒汉式单例模式
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    private static LazyMan lazyMan;

    //懒汉式
    public static LazyMan getInstance(){
        //需要加锁
        if(lazyMan==null){
                    lazyMan=new LazyMan();
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

发现这个在多线程的时候会出现问题,会出现原子性不一致问题

DCL懒汉式

//懒汉式单例模式
public class LazyMan2 {
    private LazyMan2(){
        System.out.println(Thread.currentThread().getName());
    }
    private static LazyMan2 lazyMan;

    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan2 getInstance(){
        //需要加锁
        if(lazyMan==null){
            synchronized (LazyMan2.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan2();//不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *  就有可能出现指令重排问题
                     *  比如执行的顺序是1 3 2 等
                     *  我们就可以添加volatile保证指令重排问题
                     */
                }
            }
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

这里解决了原子性不一致的问题,但是可能出现指令重排的问题,给LazyMan2的类对象加一个volatile保证指令重排的问题

静态内部类

//静态内部类
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    public static class InnerClass{
        private static final Holder holder = new Holder();
    }
}

单例不安全, 因为反射

//懒汉式单例模式
public class LazyMan2 {

    private LazyMan2(){
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    private volatile static LazyMan2 lazyMan;//加一个volatile,防止指令重排

    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan2 getInstance(){
        //需要加锁
        if(lazyMan==null){
            synchronized (LazyMan2.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan2();//不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *  就有可能出现指令重排问题
                     *  比如执行的顺序是1 3 2 等
                     *  我们就可以添加volatile保证指令重排问题
                     */
                }
            }
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //Java中有反射
        LazyMan instance = LazyMan.getInstance();//正常获取
        Constructor<LazyMan2> declaredConstructor = LazyMan2.class.getDeclaredConstructor(null);//反射获取
        declaredConstructor.setAccessible(true); //无视了私有的构造器
        LazyMan2 lazyMan1 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(lazyMan1);
    }
}

查看结果发现两个的结果已经不一样,反射破坏了单例

main ok
main ok
com.jiang.single.LazyMan2@4554617c
com.jiang.single.LazyMan2@74a14482

再加一层和再加一层标识位,一样还是能被反射破坏

//懒汉式单例模式
public class LazyMan3 {

    private static boolean key = false;

    private LazyMan3(){
        synchronized (LazyMan3.class){
            if (key==false){
                key=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    private volatile static LazyMan3 lazyMan;//加一个volatile,防止指令重排

    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan3 getInstance(){
        //需要加锁
        if(lazyMan==null){
            synchronized (LazyMan3.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan3();//不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *  就有可能出现指令重排问题
                     *  比如执行的顺序是1 3 2 等
                     *  我们就可以添加volatile保证指令重排问题
                     */
                }
            }
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //Java中有反射
//        LazyMan instance = LazyMan.getInstance();
        Field key = LazyMan3.class.getDeclaredField("key");
        key.setAccessible(true);
        Constructor<LazyMan3> declaredConstructor = LazyMan3.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视了私有的构造器
        LazyMan3 lazyMan1 = declaredConstructor.newInstance();
        key.set(lazyMan1,false);
        LazyMan3 instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(lazyMan1);
        System.out.println(instance == lazyMan1);
    }
}

枚举

//enum 是什么? 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(null);
        declaredConstructor.setAccessible(true);
        //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()

        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

出现错误提示没有此方法

枚举类型使用JAD最终反编译后源码:

如果我们看idea 的文件:会发现idea骗了我们,居然告诉我们是使用的无参构造

使用cmd反编译文件,也是使用的无参构造。

我们使用jad进行反编译,发现他使用的是一个有参构造器 参数为 String 和 int

枚举的反编译代码:

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/ogj/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
        });
    }
}

将无参构造的null更换为String.class,int.class

//enum 是什么? 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);
        //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()

        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

使用枚举,我们就可以防止反射破坏了。

19.深入理解CAS

大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        //AtomicInteger 原子类
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect期望值, int update更新值)
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

点击AtomicInteger进入查看源码

点击getAndIncrement查看源码

总结:

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

缺点:

  • 循环会耗时;
  • 一次性只能保证一个共享变量的原子性;
  • 它会存在ABA问题

CAS:ABA问题?(狸猫换太子)

A去看值为1,想去换成2

然后B去看值是1,把值换成了3,又给换了回来,这个过程A不知道

A又去看了一下,值还是1,但却不知道已经被狸猫换太子了

public class casDemo2 {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
//        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

20.原子引用

什么叫原子引用? 带版本号的 原子操作!

解决ABA问题,对应的思想:就是使用了乐观锁~

Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升!

错误案例:

正确案例:

代码:如果想去测试将下面这个类放入自己的包中,然后更改其中数值即可

public class casDemo3 {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        //AtomicStampedReference  如果泛型是一个包装类注意对象的引用问题
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//获取最新版本号
            System.out.println("a1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)+"=>a2");
            System.out.println("a2=>" + atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)+"=>a3");
            System.out.println("a3=>" + atomicStampedReference.getStamp());
        }, "a").start();
        //乐观锁的原理相同
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//获取版本号
            System.out.println("b1=>" + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 3, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)+"=>b2");
            System.out.println("b2=>" + atomicStampedReference.getStamp());
        }, "b").start();

    }
}

正常业务操作中,我们一般使用的是一个个对象,一般情况不会遇到这种情况。

21.各种锁的理解 

1.公平锁、非公平锁

公平锁:非常公平;不能插队的,必须先来后到;

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁:非常不公平,允许插队的,可以改变顺序。

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

这里如果需要进一步了解公平锁和非公平锁可以去lock锁那边查看

2.可重入锁

Synchronized锁

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();//这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

lock锁

//lock
public class Demo02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}
class Phone2{

    Lock lock=new ReentrantLock();

    public void sms(){
        lock.lock(); //细节:这个是两把锁,两个钥匙
        //lock锁必须配对,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}
  • lock锁必须配对,相当于lock和 unlock 必须数量相同;
  • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

3.自旋锁

spinlock

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

自定义自旋锁

public class SpinlockDemo {

    //int 0
    //thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"===> mylock");

        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");
        }
    }


    //解锁
    public void myunlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }

}
class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {

        //使用CAS实现自旋锁
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);


        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t2").start();
    }
}

运行结果:

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。

4.死锁

死锁是什么?

死锁测试

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"t2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+" lock:"+lockA+"===>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock:"+lockB+"===>get"+lockA);
            }
        }
    }
}

解决问题:堆栈信息

  • 1.使用jps定位进程号,jdk的bin目录下: 有一个jps工具

命令:jsp -l

  • 2.使用 jstack进程号 找到死锁信息

命令:jstack 进程号

一般情况信息在最后:

找到死锁直接解决即可

面试,工作中!排查问题的方式!

  • 1.日志(大部分人的选择)
  • 2.堆栈信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值