1. 程式人生 > >淺析Java中的23種設計模式之----單例模式

淺析Java中的23種設計模式之----單例模式

單例模式作為23種設計模式之一,有著它特定的需求場景,比如一些內部資料結構不需要發生變化的例項(一些工具類)。

單例模式的核心就是隻允許有一個該類的靜態例項,並且這個靜態例項必須由這個類自己對外提供,也就是說只能由這個類自己例項化自己。由於只允許這個類例項化自己,就意味著我們在其他類裡不能使用new關鍵字為這個類例項化,所以這個類的建構函式應該用private修飾。

單例模式分為三種模式,分別為餓漢式,懶漢式,靜態內部類。

1.餓漢式

餓漢式單例中,類在一進入記憶體就完成了對自身靜態例項的初始化,永久駐存在記憶體中。所以即使有多個執行緒在同一時刻呼叫該類的獲取例項方法,也不會存在該物件再次被初始化的情況。餓漢式是執行緒安全的。

餓漢例項

package com.huang.Instance;

//餓漢模式
public class HungryInstance {
	
	private HungryInstance(){};
	
	private static HungryInstance hungryInstance = new HungryInstance();
	
	public static HungryInstance getInstance(){
		return hungryInstance;
	}

}

獲取該例項只需要通過HungryInstance.getInstance()方法即可獲得該類的唯一靜態例項。

2.懶漢式

在懶漢式單例中,類不會在被載入進記憶體的時候直接將自身例項初始化,而是在對外提供的獲取例項的方法內進行初始化。具體實現如下

package com.huang.Instance;

//懶漢模式
public class LazyInstance {

	private LazyInstance() {
	};

	private static LazyInstance lazyInstance = null;

	public static LazyInstance getInstance() {
		if (lazyInstance == null) {
			lazyInstance = new LazyInstance();
		}
		return lazyInstance;
	}

}

看上去沒什麼問題,相較於餓漢模式也只是例項化時間不同而已,的確,在單執行緒的情況下,餓漢式和懶漢式沒有區別,但是在多執行緒下懶漢式會存線上程安全問題,通過getInstance方法提供的例項將不再是唯一例項,違背了單例模式的設計原則。下面是一個多執行緒測試類:

package com.huang.test;

import com.huang.Instance.LazyInstance;

public class Test {

	public static void main(String[] args) {

        //執行緒1
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println(LazyInstance.getInstance());
			}
		}).start();

        //執行緒2
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println(LazyInstance.getInstance());
			}
		}).start();

        //執行緒3
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println(LazyInstance.getInstance());
			}
		}).start();
	}
}

該測試類的main方法中有三個執行緒,分別會打印出它們所獲取到的例項在堆(heap)中的記憶體地址,讓我們來看一下結果:

很明顯記憶體地址發生了變化,這是因為執行緒的執行速率非常快,快過了例項化的速率。比如執行緒1進入getInstance的if條件語句,條件成立開始例項化,此時執行緒2也進入if條件語句,由於執行緒1還未完成例項化,所以建立的物件依舊為null,所以執行緒2的if條件也成立,也會進行例項化,在getInstance方法中加入執行緒睡眠會將問題放大的更清晰。

package com.huang.Instance;

//懶漢模式
public class LazyInstance {

	private LazyInstance() {
	};

	private static LazyInstance lazyInstance = null;

	public static LazyInstance getInstance() {
		if (lazyInstance == null) {
try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			lazyInstance = new LazyInstance();
		}
		return lazyInstance;
	}

}

輸入的結果:

這個時候我們就需要通過執行緒鎖+雙重判斷的方式來確保所提供的例項是全域性唯一的,具體方式如下:

package com.huang.Instance;

//懶漢模式
public class LazyInstance {

	private LazyInstance() {
	};

	private static LazyInstance lazyInstance = null;

	public static LazyInstance getInstance() {
		if (lazyInstance == null) {
			//確保鎖的物件應該是類物件,而不是例項,同一時刻只能有一個執行緒訪問這個類。
			synchronized (LazyInstance.class) {
				if (lazyInstance == null) {
					lazyInstance = new LazyInstance();
				}
			}
		}
		return lazyInstance;
	}

}

現在就不會出現例項多次被初始化的情況了。

懶漢式存線上程安全問題,即在多執行緒的情況下可能會出現例項多次被建立的情況,因此不推薦使用懶漢式。如果使用了懶漢式,需要加同步鎖來解決執行緒安全問題。

3.靜態內部類

靜態內部類也是執行緒安全的,具體實現如下:

package com.huang.Instance;

//靜態內部類實現
public class InnerClassInstance {
	
	private InnerClassInstance() {
	};

	
	private static class InnerClass{
		private static InnerClassInstance innerClassInstance = new InnerClassInstance();
	}
	
	public InnerClassInstance getInstance(){
		return InnerClass.innerClassInstance;
	}

}

在getInstance方法中會先將靜態內部類InnerClass載入入記憶體,在載入的同時完成例項化。

單例模式可以減少程式碼中new關鍵字的出現,減少程式對記憶體的佔用。但是對於擴充套件顯得非常的困難,對於一些需要增加屬性或方法的類,只能通過修改類的程式碼來實現。