线程安全——安全发布对象(二)

对象发布和溢出

  • 对象发布:使对象能够被当前范围之外的代码所使用。
  • 对象溢出:一种错误的发布,使对象还未构造完成就被其他线程看见。

不正确的发布可变对象导致的两种错误:

  1. 发布线程意外的所有线程都可以看到被发布对象的过期的值
  2. 线程看到的被发布对象的引用是最新的,然而被发布对象的状态却是过期的
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
package com.mmall.concurrency.example.publish;

import com.mmall.concurrency.annoations.NotRecommend;
import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {

private int thisCanBeEscape = 0;

public Escape () {
new InnerClass();
}

private class InnerClass {

public InnerClass() {
log.info("{}", Escape.this.thisCanBeEscape);
}
}

public static void main(String[] args) {
new Escape();
}
}

这个Escape类的构造函数没有执行完,而他的内部类却对这个类的封装实例进行引用,则已经把这个值打印出来了,而未来这个thisCanBeEscape值仍会改变,比如说在 new 内部类之后对 thisCanBeEscape赋值,那么 log 的值是过期的。这是个 this 引用在构造期间溢出的错误。

安全发布对象的四种方式

  1. 在静态初始化函数中初始化一个对象引用
  2. 将对象的应用保存到 volatile 类型的域或者 AtomicReferance 对象中
  3. 将对象的引用保存到某个正确构造对象的 final 类型域中
  4. 将对象的引用保存到一个由锁保护的域中。

以上所提到的几种方法都可以应用到单例模式中。

懒汉式单例

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
/**
* 懒汉模式 -》 双重同步锁单例模式
* 单例实例在第一次使用时进行创建
*/
@ThreadSafe
public class SingletonExample4 {

// 私有构造函数
private SingletonExample4() {

}

// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存

// JVM和cpu优化,发生了指令重排(多线程 )

// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象

// 单例对象 volatile + 双重检测机制 -> 禁止指令重排
private volatile static SingletonExample4 instance = null;

public static SingletonExample4 getInstance() {
if (instance == null) { // 双重检测机制 // B
synchronized (SingletonExample4.class) { // 同步锁
if (instance == null) {
instance = new SingletonExample4(); // A - 3
}
}
}
return instance;
}
}

饿汉模式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 饿汉模式
* 单例实例在类装载时进行创建
*/
@ThreadSafe
public class SingletonExample2 {

// 私有构造函数
private SingletonExample2() {

}

// 单例对象
private static SingletonExample2 instance = new SingletonExample2();

// 静态的工厂方法
public static SingletonExample2 getInstance() {
return instance;
}
}

静态内部类单例

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
/**
* 使用静态内部类实现的单例模式-线程安全
* 实例在第一次使用的时候创建
*
* @author 01
*/
public class SingletonExample8 {
/**
* 私有构造函数
*/
private SingletonExample8() {
}

/**
* 静态工厂方法-获取实例
*
* @return instance
*/
public static SingletonExample8 getInstance() {
return LazyHolder.INSTANCE;
}

/**
* 用静态内部类创建单例对象
*/
private static class LazyHolder {
private static final SingletonExample8 INSTANCE = new SingletonExample8();
}
}

枚举模式

上面实现的单例模式不是完全安全的,我们都可以通过反射机制去获取私有构造器更改其访问级别从而实例化多个不同的对象。
这时我们就需要使用到内部枚举类了,因为JVM可以阻止反射获取枚举类的私有构造方法。

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
/**
* 枚举模式:最安全
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {

// 私有构造函数
private SingletonExample7() {

}

public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}

private enum Singleton {
INSTANCE;

private SingletonExample7 singleton;

// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample7();
}

public SingletonExample7 getInstance() {
return singleton;
}
}
}
文章目录
  1. 1. 对象发布和溢出
  2. 2. 安全发布对象的四种方式
  3. 3. 懒汉式单例
  4. 4. 饿汉模式单例
  5. 5. 静态内部类单例
  6. 6. 枚举模式
|