java高并发5.2 安全发布(单例模式)

安全发布对象示例(多种单例模式演示)

如何安全发布对象?共有四种方法

  1. 在静态初始化函数中初始化一个对象引用

  2. 将对象的引用保存到volatile类型域或者AtomicReference对象中

  3. 将对象的引用保存到某个正确构造对象的final类型域中 (目前为止没讲过)

  4. 将对象的引用保存到一个由锁保护的域中

下面我们用各种单例模式来演示其中的几种方法

  1. 如果我们想一个类只被初始化一次, 首先要保证这个类不能被new一个新的对象出来, 所以他的默认的构造函数必须是私有

  2. 单例对象 (每次返回对象都是同一个)

要保证这两点**:

java高并发5.2 安全发布(单例模式)_第1张图片

____________________________________________________________________________

1、懒汉式(最简式)

 单线程他是安全的, 多线程是不安全的 (两个线程可以都会拿到instance = null 都去实例化). 

 

java高并发5.2 安全发布(单例模式)_第2张图片

分析:

1、在多线程环境下,当两个线程同时访问这个方法,同时制定到instance==null的判断。都判断为null,接下来同时执行new操作。这样类的构造函数被执行了两次。一旦构造函数中涉及到某些资源的处理,那么就会发生错误。所以说最简式是线程不安全的

懒汉式(synchronized) (不推荐)

线程安全的. 

在类的静态方法上使用synchronized修饰 (但是不推荐这种写法,开销大同一时间只有一个线程访问该方法)

 

分析:

1、使用synchronized修饰静态方法后,保证了方法的线程安全性,同一时间只有一个线程访问该方法

2、有缺陷:会造成性能损耗

java高并发5.2 安全发布(单例模式)_第3张图片

懒汉模式:再度改进

双重同步锁模式【先入坑再出坑】

线程不安全的.

java高并发5.2 安全发布(单例模式)_第4张图片

分析:

1、我们将上面的第二个例子(懒汉式(synchronized))进行了改进,由synchronized修饰方法改为先判断后,再锁定整个类,再加上双重的检测机制,保证了最大程度上的避免耗损性能。

2、这个方法是线程不安全的,可能大家会想在多线程情况下,只要有一个线程对类进行了上锁,那么无论如何其他线程也不会执行到new的操作上。接下来我们分析一下线程不安全的原因:

 

这里有一个知识点:CPU指令相关

在上述代码中,执行new操作的时候,CPU一共进行了三次指令

(1)memory = allocate() 分配对象的内存空间

(2)ctorInstance() 初始化对象

(3)instance = memory 设置instance指向刚分配的内存

 

在程序运行过程中,CPU为提高运算速度会做出违背代码原有顺序的优化。

我们称之为乱序执行优化或者说是指令重排(单线程的话,指令重排没影响,对多线程就不一样了)

线程不安全的原因是JVM和CPU优化, 发生了指令重排

  • 那么上面知识点中的三步指令极有可能被优化为(1)(3)(2)的顺序。当我们有两个线程A与B,A线程遵从132的顺序,经过了两此instance的空值判断后,执行了new操作,并且cpu在某一瞬间刚结束指令(3),并且还没有执行指令(2)。而在此时线程B恰巧在进行第一次的instance空值判断,由于线程A执行完(3)指令,为instance分配了内存,线程B判断instance不为空,直接执行return,返回了instance,这样就出现了错误。

 

解决办法:

在对象声明时使用volatile关键字修饰,阻止CPU的指令重排。

private volatile static SingletonExample instance = null;

 

____________________________________________________________________________

饿汉模式(简式):

单例实例在类装载时进行创建, 是线程安全的

 

java高并发5.2 安全发布(单例模式)_第5张图片

 

分析:

1、饿汉模式由于单例实例是在类装载的时候进行创建,因此只会被执行一次,所以它是线程安全的。

2、该方法存在缺陷:如果构造函数中有着大量的事情操作要做,那么类的装载时间会很长,影响性能。如果只是做的类的构造,却没有引用,那么会造成资源浪费

3、饿汉模式适用场景为:(1)私有构造 函数在实现的时候没有太多的处理(2)这个类在实例化后肯定会被使用

 

 

饿汉式(静态块初始化)

java高并发5.2 安全发布(单例模式)_第6张图片

分析:

1、除了使用静态域直接初始化单例对象,还可以用静态块初始化单例对象。

2、值得注意的一点是,静态域与静态块的顺序一定不要反,在写静态域和静态方法的时候,一定要注意顺序,不同的静态代码块是按照顺序执行的,它跟我们正常定义的静态方法和普通方法是不一样的。

____________________________________________________________________________

枚举式 (推荐)

用枚举做实例化是最安全的**

 

java高并发5.2 安全发布(单例模式)_第7张图片

由于枚举类的特殊性,枚举类的构造函数Singleton方法只会被实例化一次,且是这个类被调用之前。这个是JVM保证的。

对比懒汉与饿汉模式,它的优势很明显。

 

你可能感兴趣的:(高并发,并发编程)