單例模式的定義和特點:
單例模式(Singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。單例模式是創建型模式。單例模式在現實生活中應用也非常廣泛,例如,國家主席,公司CEO 等 。J2EE 中 的 ServletContext 、ServletContextConfig 等 、 Spring 框 架 應 用 中 的ApplicationContext、數據庫的連接池等也都是單例形式。使用單例模式創建對象可以節省內存資源避免重複浪費內存創建對象實例,保證數據內容的一致性。
餓漢式單例模式(佔着茅坑不拉屎不管用不用都創建):
類圖:
代碼:
public class HungrySingleton {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
//構造方法私有化
private HungrySingleton () {
}
//提供一個全局訪問點
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
除此之外還有1種利用java的類加載機制的順序寫在靜態代碼塊的方式:
public class HungrySingleton2 {
private static final HungrySingleton2 HUNGRY_SINGLETON_2 ;
static {
HUNGRY_SINGLETON_2 = new HungrySingleton2();
}
//構造方法私有化
private HungrySingleton2() {
}
//提供一個全局訪問點
public static HungrySingleton2 getInstance(){
return HUNGRY_SINGLETON_2;
}
}
總結:餓漢式單例的優點是沒有加任何的鎖,線程安全,執行效率比較高,不過由於在類加載的時候就初始化了,不論用或者不用,都創建了實例,會比較浪費內存。
懶漢式單例(在被調用的時候創建):
懶漢式單例解決了餓漢式單例佔着茅坑不拉屎的問題,在被外部類調用時纔會加載。
類圖:
代碼:
public class LazySingleton1 {
//注意這裏不能用final修飾 否則無法動態根據外部調用獲得實例
private static LazySingleton1 LAZY_SINGLETON_1 = null ;
//私有構造方法
private LazySingleton1() {
}
//全局訪問點
public static LazySingleton1 getInstance() {
if(LAZY_SINGLETON_1 == null) {
LAZY_SINGLETON_1 = new LazySingleton1();
}
return LAZY_SINGLETON_1;
}
}
單線程下測試返回了我們期望的結果:
接下來我們用2個線程來跑一下這個單例:
先寫一個線程的啓動類在run方法中創建實例如下:
public class ExcutorThread implements Runnable {
@Override
public void run() {
LazySingleton1 lazySingleton1 = LazySingleton1.getInstance();
System.out.println(Thread.currentThread().getName() + " ==實例地址:===" + lazySingleton1);
}
}
測試方法:可以看到2種情況同一實例和不同實例出現了線程不安全問題
造成這個結果的原因是線程搶佔cpu資源是隨機的,如圖:
當2個線程按照順序先後執行時則會返回同一實例,如果不同時順序執行則可能不同,也可能返回被覆蓋的同一實例。
爲了防止這種情況我們需要給我們的getInstance方法加上鎖synchronized,當其他線程進入該方法時讓其阻塞。如下通過斷點調試發現只有獲得了鎖的線程被監視器監視了。得到的結果也是同一對象。
加鎖雖然讓線程安全的問題解決了。但是,用synchronized 加鎖時,在線程數量比較多的情況下,如果 CPU 分配壓力上升,則會導致大批線程阻塞,從而導致程序性能大幅下降。那麼,有沒有一種更好的方式,既能兼顧線程安全又能提升程序性能呢?
答案是肯定的,雙重檢查鎖的單例模式因運而生。
雙重檢查鎖的懶漢式單例模式:
public class LazySingleton1 {
//注意這裏不能用final修飾 否則無法動態根據外部調用獲得實例
//加入volatile內存屏障防止在賦值的時候重排序指令
private volatile static LazySingleton1 LAZY_SINGLETON_1 = null;
//私有構造方法
private LazySingleton1() {
}
//全局訪問點
public static LazySingleton1 getInstance() {
if (LAZY_SINGLETON_1 == null) {
synchronized (LazySingleton1.class) {
if (LAZY_SINGLETON_1 == null) {
LAZY_SINGLETON_1 = new LazySingleton1();
//1.分配內存給這個對象
//2.初始化對象
//3.設置 LAZY_SINGLETON_1 指向剛分配的內存地址
}
}
}
return LAZY_SINGLETON_1;
}
}
當第一個線程調用 getInstance()方法時,第二個線程也可以調用。當第一個線程執行到synchronized 時會上鎖,第二個線程就會變成 MONITOR 狀態,出現阻塞。此時,阻塞並不是基於整個類的阻塞,而是在 getInstance()方法內部的阻塞,只要邏輯不太複雜,對於調用者而言感知不到。但是,用到 synchronized 關鍵字總歸要上鎖,對程序性能還是存在一定影響的。難道就真的沒有更好的方案嗎?當然有。我們可以從類初始化的角度來考慮,靜態內部類單例模式因運而生。
靜態內部類單例模式:
//利用內部類的加載機制
public class LazyInnerClassSingleton {
//私有構造器
private LazyInnerClassSingleton() {}
//提供一個全局的訪問方法
//使用 LazyInnerClassSingleton的時候,默認加載外層的.class文件內部類用$LazyHolder指向
//如果沒使用,則內部類是不加載的
//每一個關鍵字都不是多餘的,static 是爲了使單例的空間共享,保證這個方法不會被重寫、重載
public static final LazyInnerClassSingleton getInstance(){
//在返回結果以前,一定會先加載內部類
return LazyHolder.LAZY;
}
//默認不加載
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
容器式單例:
public class ContainerSingleton {
private static ContainerSingleton CONTAINER_SINGLETON;
private ContainerSingleton() {
}
private static Map<String, ContainerSingleton> ioc = new ConcurrentHashMap<String, ContainerSingleton>();
public static ContainerSingleton getInstance() {
synchronized (ioc) {
if (ioc.containsKey("instance")) {
return ioc.get("instance");
} else {
CONTAINER_SINGLETON = new ContainerSingleton();
try {
ioc.put("instance", CONTAINER_SINGLETON);
} catch (Exception e) {
e.printStackTrace();
}
return CONTAINER_SINGLETON;
}
}
}
}
枚舉單例:
public enum EnumSingleton {
/**
* 枚舉實例
*/
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getIns() {
return INSTANCE;
}
}