前言:

本文内容:单例模式、深入理解CAS、原子引用解决ABA问题

推荐免费JUC并发编程视频:【狂神说Java】JUC并发编程最新版通俗易懂_哔哩哔哩_bilibili

单例模式

饿汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jokerdig.single;

/**
* @author Joker大雄
* @data 2022/8/27 - 10:28
**/
// 饿汉式单例
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 Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}

懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.jokerdig.single;

/**
* @author Joker大雄
* @data 2022/8/27 - 10:34
**/
// 懒汉式单例
public class Lazy {
// 构造器私有
private Lazy(){
System.out.println(Thread.currentThread().getName()+"ok");
}
// 添加volatile
private volatile static Lazy lazy;

public static Lazy getInstance(){
// 双重检测锁模式的懒汉式单例 DCL懒汉式
if(lazy==null){
// 加锁
synchronized (Lazy.class){
if(lazy==null){
lazy = new Lazy(); //不是原子性操作,可能发生指令重排问题
/*
1. 分配内存空间
2. 执行构造方法,初始化对象
3. 把这个对象指向内存空间
假设由多条线程存在
A 132
B // 此时lazy还没有完成构造
必须加volatile
*/
}
}
}
return lazy;
}
// 单线程下没问题,多线程下,创建会出问题
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
lazy.getInstance();
}).start();
}
}
}

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jokerdig.single;

/**
* @author Joker大雄
* @data 2022/8/27 - 10:51
**/
// 静态内部类
public class Holder {
// 构造器私有
private Holder() {
}

public static Holder getInstance(){
return InnerClass.HOLDER;
}

public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}

反射破坏单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.jokerdig.single;

import java.lang.reflect.Constructor;

/**
* @author Joker大雄
* @data 2022/8/27 - 11:11
**/
/*
根据下方的问题可以发现,即使如何去防止,总有破坏单例的问题,该如何解决?
*/
public class Reflex {

// 使用红绿灯解决都是反射创建的破坏
private static boolean flag = false;

// 构造器私有
private Reflex(){
synchronized (Reflex.class){
// 通过红绿灯阻止破坏单例,但是如果flag也被拿到并修改呢?
if(flag == false){
flag=true;
}else{
throw new RuntimeException("不要试图破坏单例");
}
// if(reflex!=null){
// // 说明来破坏单例,若不为null 直接抛出下方异常
// // 但是还会有问题,如果两个都是通过反射创建呢?
// throw new RuntimeException("不要试图破坏单例");
// }
}
System.out.println(Thread.currentThread().getName()+"ok");

}
// 添加volatile
private volatile static Reflex reflex;
// 双重检测锁模式的懒汉式单例 DCL懒汉式
public static Reflex getInstance(){
if(reflex ==null){
// 加锁
synchronized (Lazy.class){
if(reflex==null){
reflex = new Reflex(); //不是原子性操作
}
}
}
return reflex;
}

public static void main(String[] args) throws Exception{
// 反射破坏单例模式
// Reflex instance = Reflex.getInstance();
Constructor<Reflex> constructor = Reflex.class.getDeclaredConstructor(null);
// 使用反射破坏单例
constructor.setAccessible(true); // 无视私有构造器
Reflex instance1 = constructor.newInstance();
// 都通过反射创建,单例又被破坏,抛出异常无效
Reflex instance = constructor.newInstance();
// 打印instance 发现两个instance地址不同,单例被破坏
System.out.println(instance);
System.out.println(instance1);
}
}

枚举

通过源码得知,不能通过反射破坏枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.jokerdig.single;

import java.lang.reflect.Constructor;

/**
* @author Joker大雄
* @data 2022/8/27 - 11:25
**/
// enum 枚举 本身也是一个Class
public enum EnumSingle {

INSTANCE;

public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
// 测试反射破坏枚举
EnumSingle instance1 = EnumSingle.INSTANCE;
// 通过反编译得到源码,分析出 这里并不是无参构造,而是一个有参构造,并且又两个参数String和int
//Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingle instance2 = constructor.newInstance();
// com.jokerdig.single.EnumSingle.<init>() 没有这个空参构造方法
System.out.println(instance1);
System.out.println(instance2);
// 再次运行 得出结果: Cannot reflectively create enum objects
// 无法以反射方式创建枚举对象
// 从而验证枚举无法被反射破坏
}
}

运行结果

1
2
3
4
5
6
7
Exception in thread "main" java.lang.NoSuchMethodException: com.jokerdig.single.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.jokerdig.single.Test.main(EnumSingle.java:26)

Process finished with exit code 1
// 没有这个无参构造的方法

修改为有参构造后运行结果

1
2
3
4
5
6
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.jokerdig.single.Test.main(EnumSingle.java:29)

Process finished with exit code 1
// Cannot reflectively create enum objects 无法以反射方式创建枚举对象

枚举反编译源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {

private final String name;

public final String name() {
return name;
}

private final int ordinal;

public final int ordinal() {
return ordinal;
}

protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}

public String toString() {
return name;
}

public final boolean equals(Object other) {
return this==other;
}

public final int hashCode() {
return super.hashCode();
}

protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}

public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}

@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}

protected final void finalize() { }

private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}

private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}

深入理解CAS

什么是CAS

CAS(compare and swap),即比较交换。CAS是一种基于锁的操作,而且是乐观锁。

Java中锁分为乐观锁和悲观锁

悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。

乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。

CAS 操作

  • 内存位置(V)

  • 预期原值(A)

  • 新值(B)

简单练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jokerdig.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author Joker大雄
* @data 2022/8/27 - 11:45
**/
public class CASDemo {
// CAS compareAndSet:比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
// public final boolean compareAndSet(int expect, int update)
// 如果期望的值达到,就更新为新的值
atomicInteger.compareAndSet(2022,2023);
System.out.println(atomicInteger.get()); // 结果2023
// 这里没有更新为2021
atomicInteger.compareAndSet(2022,2021);
System.out.println(atomicInteger.get()); // 结果2023
}
}

Unsafe类

16

15

CAS缺点:

  1. 循环会耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

原子引用解决ABA问题

ABA问题

ABA问题是指在CAS操作时,其他线程将变量值A改为了B,但是又被改回了A,等到本线程使用期望值A与当前变量进行比较时,发现变量A没有变,于是CAS就将A值进行了交换操作,但是实际上该值已经被其他线程改变过,这与乐观锁的设计思想不符合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.jokerdig.cas;

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

/**
* @author Joker大雄
* @data 2022/8/27 - 12:59
**/
// ABA问题
public class ABA {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(23);
// 如果期望的值达到,就更新为新的值
// -------------捣乱的线程------------------
atomicInteger.compareAndSet(23,22);
System.out.println(atomicInteger.get());

atomicInteger.compareAndSet(22,23);
System.out.println(atomicInteger.get());

// -------------期望的线程------------------
atomicInteger.compareAndSet(23,66);
// 虽然结果没有影响,但是希望修改的情况也告诉我们
System.out.println(atomicInteger.get()); // 结果2023
}
}

原子引用解决ABA

带版本号的原子操作,原理:使用乐观锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.jokerdig.cas;

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

/**
* @author Joker大雄
* @data 2022/8/27 - 12:59
**/
// 解决ABA问题
public class ABA {
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2022);
// 原子引用
// Integer包装类 -127~127之间,超过就不会复用该对象
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(23,1);
// 新建两个线程
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("A1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改的值
atomicStampedReference.compareAndSet(23, 22, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);

System.out.println("A2=>"+atomicStampedReference.getStamp());

System.out.println(atomicStampedReference.compareAndSet(22, 23, 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(23, 66, stamp, stamp + 1));
System.out.println("B2=>"+atomicStampedReference.getStamp());
},"B").start();


}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
A1=>1
B1=>1
// B2执行成功
true
B2=>2
// A2 A3未执行成功因为=>2 没有+1
A2=>2
false
A3=>2

Process finished with exit code 0