JUC并发编程-单例模式、深入理解CAS、原子引用

18. 玩转单例模式

饿汉式、DCL懒汉式

单例模式,单线程模式下是安全的的,但是多线程模式下,不安全

1)饿汉式

在这里插入图片描述

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

2)DCL懒汉式

在这里插入图片描述

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

    private static boolean key = false;

    private LazyMan(){
   
        synchronized (LazyMan.class){
   
            if (key==false){
   
                key=true;
            }
            else{
   
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        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();
                    /**
                     * 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 = LazyMan.class.getDeclaredField("key");
        key.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视了私有的构造器
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        key.set(lazyMan1,false);
        LazyMan instance = declaredConstructor.newInstance();

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

3)静态内部类

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

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

单例不安全, 因为反射

4)枚举

遇到问题:反射可以破坏单例模式

在这里插入图片描述

①尝试1:加锁,禁止反射重复创建对象

在这里插入图片描述

如果使用反射重复创建单例对象,单例模式还是会被破坏

在这里插入图片描述

②尝试2:设置标志位

在这里插入图片描述

反射也可以获取标志位,修改标志位的值,再次破坏单例模式
在这里插入图片描述

最终解决

只能通过源码分析解决

在这里插入图片描述

枚举类型从jdk1.5出来,自带单例类型

在这里插入图片描述

1.使用枚举解决尝试

在这里插入图片描述

2.源码中有提示有无参构造的方法

在这里插入图片描述

在这里插入图片描述

再次通过源码分析和反编译

在这里插入图片描述

以上说明源码和反编译的结果骗了我们,不然结果肯定对呀

3.使用jad将编译文件反编译成java

在这里插入图片描述

枚举类型的最终反编译源码:

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

最终测试:

//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

1)什么是CAS?

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

public class casDemo {
   
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
   
        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 类 源码分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2)总结

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

缺点:

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

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

在这里插入图片描述

线程1:期望值是1,要变成2;

线程2:两个操作:

  • 1、期望值是1,变成3
  • 2、期望是3,变成1

所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;

public class casDemo {
   
    //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包装类的问题

带版本号原子操作

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

在这里插入图片描述

带版本号的原子操作

package com.marchsoft.lockdemo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;


public class CASDemo {
   
    /**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
     * 正常在业务操作,这里面比较的都是一个个对象,一般很少用Integer来指定泛型
     */
    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1, 1);

    // CAS compareAndSet : 比较并交换!
    public static void main(String[] args) {
   
        new Thread(() -> {
   
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>" + stamp);
            
            try {
   
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            // 修改操作时,版本号更新 + 1
            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            
            System.out.println("a2=>" + atomicStampedReference.getStamp());
            // 重新把值改回去, 版本号更新 + 1
            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println("a3=>" + atomicStampedReference.getStamp());
        }, "a").start();
        
        // 乐观锁的原理相同!
        new Thread(() -> {
   
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>" + stamp);
            try {
   
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 3,
                    stamp, stamp + 1));
            System.out.println("b2=>" + atomicStampedReference.getStamp());
        }, "b").start();
    }
}

JUC并发编程-单例模式、深入理解CAS、原子引用 到此完结,笔者归纳、创作不易,大佬们给个3连再起飞吧

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-01 19:22:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-01 19:22:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-02-01 19:22:01       87 阅读
  4. Python语言-面向对象

    2024-02-01 19:22:01       96 阅读

热门阅读

  1. react经验10:与jquery配合使用

    2024-02-01 19:22:01       53 阅读
  2. OpenAI Gym 中级教程----深入解析 Gym 代码和结构

    2024-02-01 19:22:01       46 阅读
  3. 一文详解docker compose

    2024-02-01 19:22:01       52 阅读
  4. centos 安装docker CE

    2024-02-01 19:22:01       65 阅读
  5. HIVE的数据类型-整型

    2024-02-01 19:22:01       67 阅读
  6. MySQL与PgSQL的优缺点对比

    2024-02-01 19:22:01       62 阅读
  7. SQL分页写法

    2024-02-01 19:22:01       58 阅读
  8. 征集各位的意见

    2024-02-01 19:22:01       48 阅读
  9. 【风水】-- 家居风水四大注意事项

    2024-02-01 19:22:01       47 阅读