單例模式

Java內(nèi)存模型的抽象示意圖:

所有單例模式都有一個(gè)共性,那就是這個(gè)類沒有自己的狀態(tài)。也就是說無(wú)論這個(gè)類有多少個(gè)實(shí)例,都是一樣的;然后除此者外更重要的是,這個(gè)類如果有兩個(gè)或兩個(gè)以上的實(shí)例的話程序會(huì)產(chǎn)生錯(cuò)誤。

非線程安全的模式

public class Singleton {  private static Singleton instance;  private Singleton(){
  }  public static Singleton getInstance() {    if (instance == null) //1:A線程執(zhí)行
      instance = new Singleton(); //2:B線程執(zhí)行
    return instance;
  }
}

普通加鎖

public class SafeLazyInitialization {    private static Singleton instance;    public synchronized static Singleton getInstance() {        if (instance == null)
            instance = new Singleton();        return instance;
    }
}

出于性能考慮,采用雙重檢查加鎖的模式

雙重檢查加鎖模式

public class Singleton{  private static Singleton singleton;  private Singleton(){

  }  public static Singleton getInstance(){    if(null == singleton){  //第一次檢查
      synchronized(Singleton.class){  //加鎖
        if(null == singleton){  //第二次檢查
          singleton = new Singleton();//問題的根源出在這里
        }
      }
    }    return singleton;
  }
}

雙重檢查加鎖模式相對(duì)于普通的單例和加鎖模式而言,從性能和線程安全上來(lái)說都有很大的提升和保障。然而雙重檢查加鎖模式也存在一些隱蔽不易被發(fā)現(xiàn)的問題。首先我們要明白在JVM創(chuàng)建新的對(duì)象時(shí),主要要經(jīng)過三個(gè)步驟。

  • 分配內(nèi)存

  • 初始化構(gòu)造器

  • 將對(duì)象指向分配的內(nèi)存地址

這樣的順序在雙重加鎖模式下是么有問題的,對(duì)象在初始化完成之后再把內(nèi)存地址指向?qū)ο蟆?/p>

問題的根源

但是現(xiàn)代的JVM為了追求執(zhí)行效率會(huì)針對(duì)字節(jié)碼(編譯器級(jí)別)以及指令和內(nèi)存系統(tǒng)重排序(處理器重排序)進(jìn)行調(diào)優(yōu),這樣的話就有可能(注意是有可能)導(dǎo)致2和3的順序是相反的,一旦出現(xiàn)這樣的情況問題就來(lái)了。

java源代碼到最終實(shí)際執(zhí)行的指令序列:

前面的雙重檢查鎖定示例代碼的(instance = new Singleton();)創(chuàng)建一個(gè)對(duì)象。這一行代碼可以分解為如下的三行偽代碼:

memory = allocate();   //1:分配對(duì)象的內(nèi)存空間ctorInstance(memory);  //2:初始化對(duì)象instance = memory;     //3:設(shè)置instance指向剛分配的內(nèi)存地址

上面三行偽代碼中的2和3之間,可能會(huì)被重排序(在一些JIT編譯器上,這種重排序是真實(shí)發(fā)生的,詳情見參考文獻(xiàn)1的“Out-of-order writes”部分)。2和3之間重排序之后的執(zhí)行時(shí)序如下:

memory = allocate();   //1:分配對(duì)象的內(nèi)存空間instance = memory;     //3:設(shè)置instance指向剛分配的內(nèi)存地址
                       //注意,此時(shí)對(duì)象還沒有被初始化!ctorInstance(memory);  //2:初始化對(duì)象

多線程并發(fā)執(zhí)行的時(shí)候的情況:

解決方案

基于Volatile的解決方案

先來(lái)說說Volatile這個(gè)關(guān)鍵字的含義:

  • 可以很好地解決可見性問題

  • 但不能確保原子性問題(通過 synchronized 進(jìn)行解決)

  • 禁止指令的重排序(單例主要用到此JVM規(guī)范)

Volatile 雙重檢查加鎖模式

public class Singleton{  private volatile static Singleton singleton;  private Singleton(){
  }  public static Singleton getInstance(){    if(null == singleton){      synchronized(Singleton.class){        if(null == singleton){
          singleton = new Singleton();
        }
      }
    }    return singleton;
  }
}

基于類初始化的解決方案

利用靜態(tài)內(nèi)部類的方式來(lái)創(chuàng)建,因?yàn)殪o態(tài)屬性由JVM確保第一次初始化時(shí)創(chuàng)建,因此也不用擔(dān)心并發(fā)的問題出現(xiàn)。當(dāng)初始化進(jìn)行到一半的時(shí)候,別的線程是無(wú)法使用的,因?yàn)镴VM會(huì)幫我們強(qiáng)行同步這個(gè)過程。另外由于靜態(tài)變量只初始化一次,所以singleton仍然是單例的。

這個(gè)方案的實(shí)質(zhì)是:允許“問題的根源”的三行偽代碼中的2和3重排序,但不允許非構(gòu)造線程(這里指線程B)“看到”這個(gè)重排序。

靜態(tài)內(nèi)部類的方式

public class Singleton{  private Singleton(){}  public static Singleton getInstance(){    return InnerClassSingleton.singleton;
  }  private class InnerClassSingleton{    protected static Singleton singleton = new Singleton();
  }
}

然而,雖然靜態(tài)內(nèi)部類模式可以很好地避免并發(fā)創(chuàng)建出多個(gè)實(shí)例的問題,但這種方式仍然有其存在的隱患。

存在的隱患

  • 一旦一個(gè)實(shí)例被持久化后重新生成的實(shí)例仍然有可能是不唯一的。

  • 由于java提供了反射機(jī)制,通過反射機(jī)制仍然有可能生成多個(gè)實(shí)例。

序列化和反序列化帶來(lái)的問題:反序列化后兩個(gè)實(shí)例不一致了。

private static void singleSerializable() {    try (FileOutputStream fileOutputStream=new FileOutputStream(new File("myObjectFilee.txt"));
         ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);) {//            SingletonObject singletonObject = SingletonObject.getInstance();//            InnerClassSingleton singletonObject = InnerClassSingleton.getInstance();
        EnumSingleton singletonObject = EnumSingleton.INSTANCE;
        objectOutputStream.writeObject(singletonObject);
        objectOutputStream.close();
        fileOutputStream.close();
        System.out.println(singletonObject.hashCode());
    } catch (IOException e) {
        e.printStackTrace();
    }    try (FileInputStream fileInputStream=new FileInputStream(new File("myObjectFilee.txt"));
         ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);) {//            SingletonObject singleTest=(SingletonObject) objectInputStream.readObject();//            InnerClassSingleton singleTest=(InnerClassSingleton) objectInputStream.readObject();
        EnumSingleton singleTest=(EnumSingleton) objectInputStream.readObject();
        objectInputStream.close();
        fileInputStream.close();
        System.out.println(singleTest.hashCode());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

問題點(diǎn)及解決辦法
ObjectInputStream中的readOrdinaryObject。

if (obj != null &&
    handles.lookupException(passHandle) == null &&
    desc.hasReadResolveMethod())
{    Object rep = desc.invokeReadResolve(obj);    if (unshared && rep.getClass().isArray()) {
        rep = cloneArray(rep);
    }    if (rep != obj) {
        handles.setObject(passHandle, obj = rep);
    }
}

調(diào)用自定義的readResolve方法

protected Object readResolve(){
    System.out.println("調(diào)用了readResolve方法!");    return  InnerClassSingleton.getInstance();
}

通過反射機(jī)制獲取到兩個(gè)不同的實(shí)例

private static void attack() {    try {
        Class<?> classType = InnerClassSingleton.class;
        Constructor<?> constructor = classType.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        InnerClassSingleton singleton = (InnerClassSingleton) constructor.newInstance();
        InnerClassSingleton singleton2 = InnerClassSingleton.getInstance();
        System.out.println(singleton == singleton2);  //false
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

解決方案 : 私有構(gòu)造方法中進(jìn)行添加標(biāo)志判斷。

private InnerClassSingleton() {
    synchronized (InnerClassSingleton.class) {        if (false == flag) {
            flag = !flag;
        } else {            throw new RuntimeException("單例模式正在被攻擊");
        }
    }
}

單例最優(yōu)方案,枚舉的方式

枚舉實(shí)現(xiàn)單例的優(yōu)勢(shì)

  • 自由序列化;

  • 保證只有一個(gè)實(shí)例(即使使用反射機(jī)制也無(wú)法多次實(shí)例化一個(gè)枚舉量);

  • 線程安全;

public enum Singleton {
    INSTANCE;    private Singleton(){}
}

Hibernate的解決方案

通過ThreadLocal的方式

import org.hibernate.HibernateException;import org.hibernate.Session;import org.hibernate.cfg.Configuration;public class HibernateSessionFactory {    private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";    private static final ThreadLocal threadLocal = new ThreadLocal();    private static Configuration configuration = new Configuration();    private static org.hibernate.SessionFactory sessionFactory;    private static String configFile = CONFIG_FILE_LOCATION;    static {       try {
           configuration.configure(configFile);
           sessionFactory = configuration.buildSessionFactory();
       } catch (Exception e) {
           System.err.println("%%%% Error Creating SessionFactory %%%%");
           e.printStackTrace();
       }
    }    private HibernateSessionFactory() {
    }    public static Session getSession() throws HibernateException {
       Session session = (Session) threadLocal.get();       if (session == null || !session.isOpen()) {           if (sessionFactory == null) {
              rebuildSessionFactory();
           }
           session = (sessionFactory != null) ? essionFactory.openSession() : null;
           threadLocal.set(session);
       }       return session;
    }// Other methods...}

參考文檔:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

掃描公眾號(hào),關(guān)注更多信息


---------------------------------------------------------------------------------我是分割線--------------------------------------------------------------------------


to be a better me, talk is cheap show me the code


版權(quán)所有,轉(zhuǎn)載請(qǐng)注明原文鏈接。

文中有不妥或者錯(cuò)誤的地方還望指出,以免誤人子弟。如果覺得本文對(duì)你有所幫助不妨【推薦】一下!如果你有更好的建議,可以給我留言討論,共同進(jìn)步!

再次感謝您耐心的讀完本篇文章。

http://www.cnblogs.com/rwxwsblog/p/6662951.html