多例:只是單例的一種延伸 ? 不必過于在意各種模式的名字,重要的是學會融會貫通,把生產的car放到集合中 類似JDBC 的連接池 把連接對象放到池中
多例模式特點:
???? 1. 多例類可以有多個實例
???? 2. 多例類必須自己創建自己的實例,并管理自己的實例,和向外界提供自己的實例
package com.pers.hoobey;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Car1 {
private static Car1 car1 = new Car1();
private static Car1 car2 = new Car1();
private static List<Car1> list = new ArrayList<Car1>();//用于存放多個實例的car
private static final int maxCount =2;//最多的實例數
static{
list.add(car1);
list.add(car2);
}
private Car1(){}//私有構造方法 避免外部創建實例
/*
* @description 指定拿取某一個實例
*/
public static Car1 getInstance(int index){
return list.get(index);
}
//隨機拿取實例
public static Car1 getInstance(){
Random random = new Random();
int current = random.nextInt(maxCount);
return list.get(current);
}
public void run(){
System.out.println("奔跑中的車.....");
}
}
順便總結一下 普通工廠模式 工廠方法模式和抽象工廠模式的區別:
簡單工廠是用來生產”東西“的,那任何”東西“的子類,比如汽車,自行車,輪船,洗發水都是可以被生產的,但此處簡單工廠的壓力太大了啊,任何”東西“的子類都可以被生產,負擔太重,所以一般對簡單工廠類也有種稱呼,叫”上帝類“。
而工廠方法模式就很好的減輕了工廠類的負擔,把某一類/某一種東西交由一個工廠生產,同時增加某一類”東西“并不需要修改工廠類,只需要添加生產這類”東西“的工廠即可,使得工廠類符合開放-封閉原則。
對于”東西“的分類,有時候不能光是橫向的分類,從另一個角度也是可以分類的,不知道這句話的意思能不能懂,打個比方:汽車可以根據品牌分為奔馳、奧迪,也可以根據類別分為普通三廂車和SUV車,如果用工廠方法來描述的話,奔馳車工廠有一個方法即生產奔馳車,奧迪車工廠有一個方法生產奧迪車,但在有多重分類的情形下,這樣寫已經不夠用,不符合實際了,這個時候需要用到抽象工廠模式,即奧迪車工廠有兩個方法,一個方法是生產普通三廂奧迪車,另一個方法是生產SUV奧迪車。奔馳車工廠有兩個方法,一個方法是生產普通三廂奔馳車,另一個方法是生產SUV奔馳車。
上面即工廠方法模式和抽象工廠模式的應用場景,因為這兩者很像,所以概念上不容易區分,可以這么說,工廠方法模式是一種極端情況的抽象工廠模式,而抽象工廠模式可以看成是工廠方法模式的一種推廣。
再說下抽象工廠模式,此處的抽象工廠接口應該是有兩個方法,一個是生成普通三廂車,一個是生產SUV車,可以說,生產的”東西“已經被限定住了,因此你不能生產某品牌汽車外的其他”東西“,因而可以理解成使用抽象工廠模式不能新增新的”東西“(在簡單工廠和工廠方法中理論上都是可以新增任意”東西“的)
?
下面分析一下懶漢模式下的單例模式
作為一個單例,我們首先要確保的就是實例的“唯一性”,有很多因素會導致“唯一性”失效,它們包括:多線程、序列化、反射、克隆等,更特殊一點的情況還有:分布式系統、多個類加載器等等。其中,多線程問題最為突出。為了提高應用的工作效率,現如今我們的工程中基本上都會用到多線程;目前使用單線程能輕松完成的任務,日復一日,隨著業務邏輯的復雜化、用戶數量的遞增,也有可能要被升級為多線程處理。所以任何在多線程下不能保證單個實例的單例模式,我都認為應該立即被棄用。
在只考慮一個類加載器的情況下,“餓漢方式”實現的單例(在系統運行起來裝載類的時候就進行初始化實例的操作,由JVM虛擬機來保證一個類的初始化方法在多線程環境中被正確加鎖和同步,所以)是線程安全的,而“懶漢”方式則需要注意了,先來看一種最簡單的“懶漢方式”的單例:
這種寫法只能在單線程下使用。如果是多線程,可能發生一個線程通過并進入了?if (singleton == null)
?判斷語句塊,但還未來得及創建新的實例時,另一個線程也通過了這個判斷語句,兩個線程最終都進行了創建,導致多個實例的產生。所以在多線程環境下必須摒棄此方式。
除了多并發的情況,實現單例模式時另一個重要的考量因素是效率。前述的“懶漢方式”的多線程問題可以通過加上?synchronized
?修飾符解決,但考慮到性能,一定不要簡單粗暴地將其添加在如下位置:
上述方式通過為?getInstence()
?方法增加?synchronized
?關鍵字,迫使每個線程在進入這個方法前,要先等候別的線程離開該方法,即不會有兩個線程可以同時進入此方法執行?new Singleton()
,從而保證了單例的有效。但它的致命缺陷是效率太低了,每個線程每次執行?getInstance()
?方法獲取類的實例時,都會進行同步。而事實上實例創建完成后,同步就變為不必要的開銷了,這樣做在高并發下必然會拖垮性能。所以此方法雖然可行但也不推薦。那我們將同步方法改為同步代碼塊是不是就能減少同步對性能的影響了呢:
但是這種同步卻并不能做到線程安全,同最初的懶漢模式一個道理,它可能產生多個實例,所以亦不可行。我們必須再增加一個單例不為空的判斷來確保線程安全,也就是所謂的“雙重檢查鎖定”(Double Check Lock(DCL))方式:
此方法的“Double-Check”體現在進行了兩次?if (singleton == null)
?的檢查,這樣既同步代碼塊保證了線程安全,同時實例化的代碼也只會執行一次,實例化后同步操作不會再被執行,從而效率提升很多(詳細比較見附錄 1)。
雙重檢查鎖定(DCL)方式也是延遲加載的,它唯一的問題是,由于Java 編譯器允許處理器亂序執行,在JDK版本小于1.5時會有DCL失效的問題(原因解釋詳見附錄 2)。當然,現在大家使用的JDK普遍都已超過1.4,只要在定義單例時加上1.5及以上版本具體化了的volatile關鍵字,即可保證執行的順序,從而使單例起效。所以 DCL 方式是推薦的一種方式。