zoukankan      html  css  js  c++  java
  • 对象发布

    0.摘要

      主要讨论了在多线程并发环境下,安全发布的几种方式(多种单例模式演示 )。

    1.基本概念

      发布对象:使对象能够被当前范围之外的代码所看见。比如通过类的非私有方法返回对象的引用。

      对象逸出:一种错误的发布。当一个对象还没有构造完成时,就被其他线程所看见

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

        1)发布线程以外的任何线程都可以看到被发布对象的过期的值 

        2)线程看到的被发布线程的引用是最新的,然而被发布对象的状态却是过期的

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

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

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

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

        4)将对象的引用保存到某个正确构造对象的final类型域中(本文目前没涉及到)

    2.懒汉模式

      (1)最简单的一段:在第一次使用的时候创建

     1 public class SingletonExample1 {
     2     private SingletonExample1() {
     3     }
     4     private static SingletonExample1 instance = null;
     5 
     6     public static SingletonExample1 getInstance() {
     7         if (instance == null) {
     8             instance = new SingletonExample1();
     9         }
    10         return instance;
    11     }
    12 }

       分析:单线程的情况下,线程是安全的。在多线程的情况下,第7-9行代码会出现线程不安全的问题。两个线程同时调用这段代码的时候会拿到两个不同的实例,尽管并不会产生什么不好的情况,但是如果这个私有构造方法在实现的时候要做很多操作,如资源的释放、运算等。那么这样产生的单例是线程不安全的。


      (2)推荐和线程安全一段:上述可以单独使用synchronized修饰getInstance()来保证线程安全,但是有可能会造成性能损耗。更好的方法

     1 /**
     2  * 懒汉模式-双重同步锁单例模式
     3  * 单例在使用时候创建
     4  */
     5 public class SingletonExample1 {
     6     private SingletonExample1() {
     7     }
     8 
     9     //单例对象
    10     private static SingletonExample1 instance = null;
    11 
    12     //静态工厂方法
    13     public static SingletonExample1 getInstance() {
    14         if (instance == null) {//双重检测机制
    15             synchronized (SingletonExample1.class) {//同步锁
    16                 if (instance == null) {
    17                     instance = new SingletonExample1();
    18                 }
    19             }
    20         }
    21         return instance;
    22     }
    23 }

      分析:但上述的代码并不是线程安全的,插播一个小知识点。

    这里有一个知识点:CPU指令相关 
    在上述代码中,执行new操作的时候,CPU一共进行了三次指令 
    (1)memory = allocate() 分配对象的内存空间 
    (2)ctorInstance() 初始化对象 
    (3)instance = memory 设置instance指向刚分配的内存

      在程序运行过程中,CPU为提高运算速度和JVM优化会做出违背代码原有顺序的优化。我们称之为乱序执行优化或者说是指令重排。 
      那么上面知识点中的三步指令极有可能被优化为(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;

    3.饿汉模式

      (1)最简单的一段:在类装载的时候创建,能保证线程安全

     1 //饿汉模式,在类装载的时候创建
     2 public class SingletonExample1 {
     3     private SingletonExample1() {
     4     }
     5     
     6     private static SingletonExample1 instance = new SingletonExample1();
     7 
     8     public static SingletonExample1 getInstance() {
     9         return instance;
    10     }
    11 }

      分析:第一如果其私有构造函数中存在大量处理,那么类装载的时间会很长,第二如果只进行了类的加载却没有调用,会造成性能的浪费。(下面的分析有点长)


      (2)通过静态块来初始化,注意静态代码块一定要在静态工厂方法的上面

     1 public class SingletonExample {
     2     // 私有构造函数
     3     private SingletonExample() {
     4     }
     5     // 单例对象
     6     private static SingletonExample instance = null;
     7     static {
     8         instance = new SingletonExample();
     9     }
    10     // 静态的工厂方法
    11     public static SingletonExample getInstance() {
    12         return instance;
    13     }
    14 }

    4.枚举模式

     1 /**
     2  *枚举模式
     3  */
     4 public class SingletonExample2 {
     5     private SingletonExample2() {
     6     }
     7 
     8     public static SingletonExample2 getInstance() {
     9         return Singleton.INSTANCE.getSingleton();
    10     }
    11 
    12     private enum Singleton {
    13         INSTANCE;
    14         private SingletonExample2 singleton;
    15 
    16         //JVM来保证这个方法只被调用一次
    17         Singleton() {
    18             singleton = new SingletonExample2();
    19         }
    20 
    21         public SingletonExample2 getSingleton() {
    22             return singleton;
    23         }
    24     }
    25 
    26 }

      分析:由于枚举类的特殊性,JVM可以保证枚举类的构造函数只被调用一次,相比饿汉模式,在实际调用的时候才做初始化。相比懒汉模式更加安全。推荐使用。

    5.小结

      本文章是作者学习并发编程中的笔记总结,方面我温故,希望能帮到大家。主要总结了几种创建单例的方法,推荐使用枚举模式。

    2019-05-14 16:09:33

    我不喜欢这个世界,我喜欢你
  • 相关阅读:
    POJ 1330 Nearest Common Ancestors (LCA,dfs+ST在线算法)
    BZOJ 2002: [Hnoi2010]Bounce 弹飞绵羊 (动态树LCT)
    HDU 4010 Query on The Trees (动态树)
    SPOJ 375. Query on a tree (动态树)
    BZOJ 2049: [Sdoi2008]Cave 洞穴勘测 (动态树入门)
    HDU 3726 Graph and Queries (离线处理+splay tree)
    POJ 3580 SuperMemo (splay tree)
    Android中visibility属性VISIBLE、INVISIBLE、GONE的区别
    mysql如何在一张表中插入一万条数据?(用存储过程解决)
    Gradle Build速度加快方法汇总
  • 原文地址:https://www.cnblogs.com/truekai/p/10861102.html
Copyright © 2011-2022 走看看