安全发布对象 为什么volatile不能保证原子性而Atomic可以?

发布对象

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

不正确的发布可变对象导致的两种错误:
1.发布线程意外的所有线程都可以看到被发布对象的过期的值
2.线程看到的被发布对象的引用是最新的,然而被发布对象的状态却是过期的

Java 线程安全性中的对象发布和逸出

单例模式

懒汉模式:

/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 */
@NotThreadSafe
public class SingletonExample1 {

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

    }

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

    // 静态的工厂方法
    public static SingletonExample1 getInstance() {
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}
这个是线程不安全的,如果同时有两个线程到达
    if(instance == null)
一个new完之后还会再new一个对象
 
为了使得具有线程安全性,可以使用synchronized关键字,但是这种方式会使性能受影响。不推荐。
/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 */
@ThreadSafe
@NotRecommend
public class SingletonExample3 {

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

    }

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

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

改进:

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

但是这种方式依然是线程不安全的。原因是可能发生指令重排的情况,比如AB两个想同时到达这个方法,当A已经执行到   instance = new SingletonExample4(); // A - 3  的时候,在cpu中发生重排执行了

12这两步,此时B执行到 if (instance == null),发现instance不为空,直接return,而其实instance并没有进行初始化,B拿到这个对象就会发生错误。

 
进一步改进:使用volatile关键字,禁止进行指令重排(前面我们讲过,volatile的使用场景由两个,一个是状态表示量,一个是double check双重检测,也就是使用在这了)
注意:
volatile阻止的不 singleton = new Singleton()这句话内部[1-2-3]的指令重排,
而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作( if (instance == null))。
/**
 * 懒汉模式 -》 双重同步锁单例模式
 * 单例实例在第一次使用时进行创建
 */
@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) { // 双重检测机制        // B
            synchronized (SingletonExample5.class) { // 同步锁
                if (instance == null) {
                    instance = new SingletonExample5(); // A - 3
                }
            }
        }
        return instance;
    }
}
 
饿汉模式:
import com.mmall.concurrency.annoations.ThreadSafe;

/**
 * 饿汉模式
 * 单例实例在类装载时进行创建
 */
@ThreadSafe
public class SingletonExample2 {

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

    }

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

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

它是线程安全的。

缺点 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());
    }
}

枚举模式

/**
 * 枚举模式:最安全
 */
@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;
        }
    }
}

 相比于懒汉模式更加容易保证安全性,相比饿汉模式只在需要使用的时候才执行初始化。

这篇博客对java单例模式讲解的非常好:

https://www.jianshu.com/p/eb30a388c5fc?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

深入浅出单实例SINGLETON设计模式

猜你喜欢

转载自www.cnblogs.com/xiangkejin/p/9267372.html