這篇博客介紹線程安全的應用——單例模式。
單例模式
單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例。
雙重校驗鎖
實例:
/** * @author: ChenHao * 關於懶漢式的線程安全問題,使用同步機制 * 對於一般的方法內,使用同步方法塊,可以考慮使用this * 對於靜態方法而言,使用當前類充當鎖。 */ public class TestSingleton { public static void main(String[] args) { System.out.println(MySingle.getInstance()); System.out.println(MySingle.getInstance()); } } class MySingle{ //聲明一個私有的靜態變量,第一次調用才初始化,避免內存浪費。 private volatile static MySingle instance=null; //讓構造器為private私有化,避免外部直接創建對象 private MySingle(){} public static MySingle getInstance(){ if(null ==instance){//提高效率:如果已經存在對象,則不進行鎖等待,直接返回對象,只有當對象為空才會進入鎖等待,這里可以在第一個進入鎖創建對象后,sleep10秒來放大效果 //這里有五個線程等待 synchronized(MySingle.class){ //第一次:當一個線程進來后,其他線程都在鎖外面 //第一個線程創建對象后,釋放鎖,其他線程得到鎖后,如果instance不為null,則不需要創建 if(null ==instance){ instance =new MySingle(); try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } return instance; } }
代碼分析:多個線程同時創建MySingle類的實例,比如現在有6個線程,第一次同時調用getInstance()靜態方法,
線程A獲取了鎖,其他5個線程都在synchronized(MySingle.class)外面等待,第一個線程創建對象后,釋放鎖,其他線程得到鎖后,如果instance不為null,則不需要創建;
第一個if(null ==instance)作用是提高效率:如果已經存在對象,則不進行鎖等待,直接返回對象,只有當對象為空才會進入鎖等待,這里可以在第一個進入鎖創建對象后,sleep10秒來放大效果,此時已經創建了instance ,但是還沒有釋放鎖,所以新來的線程不需要再等待鎖,直接使用已經創建好的instance;
第二個if(null ==instance)判斷instance是否已經存在,如果第一個線程已經創建instance,並釋放鎖,接下來的線程進入后則不需要再創建;
運行結果:輸出相同的對象實例

餓漢
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
CAS(AtomicReference)實現單例模式
public class Singleton { private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } }
用CAS的好處在於不需要使用傳統的鎖機制來保證線程安全,CAS是一種基於忙等待的算法,依賴底層硬件的實現,相對於鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的並行度。
CAS的一個重要缺點在於如果忙等待一直執行不成功(一直在死循環中),會對CPU造成較大的執行開銷。而且,這種寫法如果有多個線程同時執行singleton = new Singleton();也會比較浪費堆內存。
