單列模式的應用場景
- 單例模式(Singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。單例模式是創建新模式。J2EE標準中的ServletContext、ServletContextConfig 等、Spring 框架應用中的ApplictionContext、數據庫的連接池等都是單例形式。
餓漢式單例模式
- 先來看單例模式的類結構圖,如圖
-
餓漢式單例模式在類加載的時候立即初始化,並且創建單例對象。它是絕對線程安全,在線程還沒出現以前就實例化了,不可能存在線程安全問題。
優點: 沒有加任何鎖、執行效率比較高,用戶體驗比懶漢式單例模式好。
缺點: 類加載的時候就初始化,不管用不用都佔用空間,浪費了內存。
-
Spring 中Ioc 容器ApplicationContext本身就是典型的餓漢式單例模式,接下來看一段代碼。
public class HungrySingleton {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
- 還有另一種寫法靜態代碼塊機制
public class HungryStaticSingleton {
private static final HungryStaticSingleton HUNGRY_SINGLETON;
static {
HUNGRY_SINGLETON = new HungryStaticSingleton();
}
private HungryStaticSingleton(){
}
public static HungryStaticSingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
- 這兩種寫法都非常簡單,也非常好理解,餓漢式單例模式適用於單例對象比較少的情況,下面我們看性能更優的寫法。
懶漢式單例模式
- 懶漢式單例模式的特點是:被外部類調用的時候內部類纔會加載。下面看懶漢模式的簡單實現
//懶漢式單例模式在外部需要使用的時候才進行實例化
public class LazySimpleSingleton {
private static LazySimpleSingleton LAZY_SIMPLE_SINGLETON = null;
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if(LAZY_SIMPLE_SINGLETON == null){
LAZY_SIMPLE_SINGLETON = new LazySimpleSingleton();
}
return LAZY_SIMPLE_SINGLETON;
}
}
- 然後我們寫一個線程類
public class ExectorThread implements Runnable {
@Override
public void run() {
LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+ lazySimpleSingleton);
}
}
- 測試代碼
public class LazySimpleSingletonTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new ExectorThread());
Thread thread2 = new Thread(new ExectorThread());
thread1.start();
thread2.start();
}
}
- 運行結果:
- 上面的代碼一定概率出現兩種不同的結果,意味着上面的單例模式存在安全隱患。那麼我們怎樣優化代碼才能使得懶漢式單例模式線程安全呢?來看代碼我們在getInstance()方法加上synchronized關鍵字,使這個方法變成線程同步方法。
public class LazySimpleSingleton {
private static LazySimpleSingleton lazy = null;
private LazySimpleSingleton(){}
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
- 雖然線程安全問題解決了,但是用synchronized加鎖時,在線程數量比較多的情況下,如果cpu分配壓力上升,則會導致線程阻塞,性能大幅下降,我們引入一種新的方式,雙重加鎖
public class LazySimpleSingleton {
private volatile static LazySimpleSingleton lazy = null;
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if(lazy == null){
synchronized (LazySimpleSingleton.class){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
}
}
return lazy;
}
}
- 優化後的結果是代碼塊的阻塞,邏輯不復雜的情況下感知較小。但是用到synchronized 關鍵字總歸要上鎖,看下邊一種寫法,採用靜態內部類:
//這種形式兼顧了餓漢式單例模式的內存浪費問題 和sychronized 的性能問題
public class LazyInnerClassSingleton {
//使用LazyInnerClassSingleton的時候,默認會先初始化內部類
//入股沒使用則內部類是不加載的
private LazyInnerClassSingleton(){}
//static 關鍵字是爲了使單例的空間共享,不會被重寫,重載
public static final LazyInnerClassSingleton getInstance (){
return LazyHolder.LAZY;
}
//默認不加載
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
- 這種方式兼顧了餓漢式單例模式的內存浪費問題和synchronized的性能問題,內部類一定要在方法調用之前初始化,巧妙的避免了線程安全問題。
- 下期:單例模式詳解二 以及更優的單例模式。謝謝。