Java 單例模式的線程安全實現

先看一下通常的實現方式安全

public class lazySingleton {
	private static lazySingleton m_instance=null;
	private lazySingleton() {
		// TODO Auto-generated constructor stub
		System.out.println("構造函數");
	}
	public static lazySingleton getInstance(){
		if(m_instance==null){//b
			synchronized (lazySingleton.class) {
				if(m_instance==null){
					m_instance=new lazySingleton(); //a
				}
			}
		}
		return m_instance;//c
	}
	public void print(){
		System.out.println("print");
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		lazySingleton.getInstance().print();
	}
}

這種實現方式,在單線程模式下徹底沒有問題。但在多線程模式下,則可能引起報錯。 緣由以下:多線程

  1. m_instance=new lazySingleton();語句編譯以後實際上是包含兩條指令,new lazySingleton()指令和給m_instance賦值。因爲編譯時或CPU執行時指令重排序,可能存在先給m_instance賦值,後初始化對象的狀況。這時,就出現一種狀況,m_instance值不爲null,可是m_instance對象尚未實例化完成。
  2. 當線程A執行到a處時,m_instance值不爲null,可是m_instance對象尚未實例化完成。線程B因爲m_instance值不爲null,從b出到c處返回m_instance對象。
  3. 當線程B調用m_instance的方法時,將引起對象還沒有初始化錯誤。

解決:針對引起問題的主要緣由1和2,分別提出解決的辦法。函數

  • 方法一:private static volatile lazySingleton m_instance=null;在m_instance前添加volatile 關鍵字,提醒編譯器和運行時環境,在volatile變量上的操做不能與其它操做重排序,從而避免後續問題產生。線程

  • 方法二:在getInstance方法前添加synchronized 同步鎖,保證同時只會有一個線程getInstance方法,也就保證了在A線程返回m_instance以前,其餘線程都是阻塞狀態。也就不會出現m_instance值不爲null,可是m_instance對象尚未實例化完成的狀況。code

除了以上兩種方法,還有一種簡單安全還可靠的方法,那就是依賴JVM的靜態類的靜態屬性實現單例模式。以下對象

public class Singleton { 
  static class SingletonHolder { 
    static Singleton instance = new Singleton(); 
  } 
    
  public static Singleton getInstance(){ 
    return SingletonHolder.instance; 
  } 
}

此種寫法利用了類加載器的加載原理,每一個類只會被加載一次,這樣單例對象在其內部靜態類被加載的時候生成,並且此過程是線程安全的。排序

相關文章
相關標籤/搜索