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连再起飞吧