ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/8/18 13:58:47 拓冰建站 浏览量
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 = newAtomicStampedReference<>(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();}// 修改操作时,版本号更新 + 1atomicStampedReference.compareAndSet(1, 2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);System.out.println("a2=>" + atomicStampedReference.getStamp());// 重新把值改回去, 版本号更新 + 1System.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连再起飞吧