安全发布对象的四种方法

发布对象

先给出两个概念 , 然后通过singleton 示例代码 ,来看看安全发布对象的四种方法.

发布对象
使一个对象能够被当前范围之外的代码所使用
对象逸出
是一种错误的发布, 当一个对象还没有构造完成时 , 就使得它被其他线程看见

四种方法

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

如何保证一个实例只被初始化一次 , 且线程安全呢?

代码演示 1

package com.mmall.concurrency.example.singleton;

/**
 * Created by Charles Date:2018/3/19
 */

import com.mmall.concurrency.annoations.NotThreadSafe;

/**
 * 懒汉模式
 * 单例的实例在第一次使用的时候创建
 * 但是这种写法在多线程情况下回出问题, 它可能会实例化出多个对象 
 */
public class SingletonExample1 {

    // 私有的构造函数
    private SingletonExample1(){

    }

    // 单例对象
    private static SingletonExample1 instance = null;

    // 静态工厂方法
    public static SingletonExample1 getInstance(){
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}

代码演示2

/**
 * 饿汉模式
 * 单例的实例在类装载的时候创建
 * 这个饿汉模式是线程安全的
 * 使用饿汉模式要注意两个问题
 *      1.其私有构造函数在实现的时候没有太多的处理,否则可能会造成性能的问题
 *      2.这个类在实际的过程中肯定会被使用, 不会造成资源的浪费.
 */
public class SingletonExample2 {

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

    }

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

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

代码演示 3

/**
 * 懒汉模式
 * 单例的实例在第一次使用的时候创建
 * 
 */
public class SingletonExample3 {

    // 私有的构造函数
    private SingletonExample3(){

    }

    // 单例对象
    private static SingletonExample3 instance = null;

    // 静态工厂方法
    public static synchronized SingletonExample3 getInstance(){
        if (instance == null) {
            instance = new SingletonExample3();
        }
        return instance;
    }
}

上面这个示例是 线程安全的懒汉模式 , 但是它的性能不好, 因为它通过 synchronize来使得同一时间内, 只允许一个线程来访问的方式来保证安全 , 降低了程序性能 .

代码示例 4

/**
 * 懒汉模式 -》 双重同步锁单例模式
 * 单例实例在第一次使用时进行创建
 * 线程不安全
 */
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() 初始化对象

    // 单例对象
    private 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;
    }
}

上面的这个代码不是线程安全的 , 当代码执行 下面这句的时候
instance = new SingletonExample4()
主要进行三部指令 ,如上面的 代码注释部分, 在完成注释中描述的三步之后 instance 就指向了实际分配的内存地址了 . 在单线程模式下, 执行是没有什么问题的 ,但是在多线程模式下, 这三步指令会发生指令重排 , 比如由于指令重排, 线程A 执行到了 第三步 instance = memory 设置instance指向刚分配的内存 , 线程 B在执行时判断到 instance 已经有值了, 就会直接返回 , 但是实际在线程A 哪里初始化对象这一步还没有完成, 线程 B 在拿到这个还没有初始化的 instance 之后 , 一旦去调用 , 就会出现问题了 .所以说这种写法是线程不安全的.
既然 双重检测机制有指令重排的问题, 那如何解决这个问题 , 看下面的代码示例

代码示例 5

/**
 * 懒汉模式 --> 双重同步锁单例模式
 * 单例的实例在第一次使用的时候创建
 */
@ThreadSafe
public class SingletonExample5 {

    // 私有的构造函数
    private SingletonExample5(){

    }

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


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

    // 静态工厂方法
    public static SingletonExample5 getInstance(){
        if (instance == null) { // 双重检测机制
            synchronized(SingletonExample5.class){ //同步锁
                if (instance == null) {
                    instance = new SingletonExample5();
                }
            }
        }
        return instance;
    }
}

既然双重检测会发生指令重排, 那么我们就禁止它发生指令重排 提到禁止指令重排 ,应该就想到了一个关键字 volatile , 所以可以通过 volatile + 双重检测机制 来禁止指令重排做到 懒汉模式的线程安全

代码示例 6

/**
 * 饿汉模式
 * 单例的实例在类装载的时候创建
 *
 * 使用饿汉模式要注意两个问题
 *      1.其私有构造函数在实现的时候没有太多的处理,否则可能会造成性能的问题
 *      2.这个类在实际的过程中肯定会被使用, 不会造成资源的浪费.
 */
@ThreadSafe
public class SingletonExample6 {

    // 私有的构造函数
    private SingletonExample6(){

    }

    // 单例对象
    private static SingletonExample6 instance = null;

    static {
        instance = new SingletonExample6();
    }

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

    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}

使用 静态块 初始化 instance

代码示例 7

public class SingletonExample7 {

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

    }

    // 静态工厂方法
    public static SingletonExample7 getInstance() {
        return Singleton.INSTANCE.getIntance();
    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample7 singleton;

        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getIntance() {
            return singleton;
        }
    }
}

枚举模式 实现 线程安全饿汉单例模式 , 是最推荐的一种方式 ,相较于懒汉模式, 其在安全性方面更容易保证, 相较于其他饿汉模式, 它是在实际调用的时候才开始初始化, 而在后续的使用中, 可以直接获取里面的值 , 不会造成资源的浪费 .

你可能感兴趣的:(Java高并发)