?
?不變模式(Immutable Pattern)顧名思義,它的狀態在它的生命周期內是永恒的(暈,永恒的日月星晨,對象如人,
太渺小,談不上永恒!),不會改變的.對于其中的不變類(Immutable Class),它的實例可以在運行期間保持狀態永遠不會被
改變,所以不需要采取共享互斥機制來保護,如果運用得當可以節省大量的時間成本.
?請注意上面這段話,不變模式其中的不變類,說明不變類只是不變模式中一個組成部分,不變類和與之相輔的可變
類,以及它們之間的關系才共同構成不變模式!所以在涉及不變模式的時候一定要研究一個類是不變的還是可變的(Mutable).
在jdk中的String類和StringBuffer類就組成了一個不變模式.
還是先看具體的例子:
final class Dog{
??? private final String name;
??? private final int age;
??? public Dog(String name,int age){
??????? this.name = name;
??????? this.age = age;
??? }
???
??? public String getName(){return this.name;}
??? public int getAge(){return this.age;}
???
??? public String toString(){
??????? return "Dog's name = " + this.name + ",age = " + this.age;
??? }
}
1.Dog類本身被聲明為final,可以保證它本身的狀態不會被子類擴展方法所改變.
2.Dog類的所有成員變量都是final的,保證它在構造后不會被重新賦值.而且Dog類所有屬性是private的,只提供getter訪問.
3.Dog類的能傳入的參數本身是Immutable的.這一點非常重要將在下面具體說明.
以上條件都是必要條件,而不是充要條件.
class DisplayDog extends Thread{
??? private Dog dog;
??? public DisplayDog(Dog dog){
??????? this.dog = dog;
??? }
???
??? public void run(){
??????? while(true){
??????????? System.out.println(this.getName() + " display: " + dog);
??????? }
??? }
}
DisplayDog類是把一個Dog對象傳入后,不斷顯示這個dog的屬性.我們會同時用多個線程來顯示同一dog對象,看看它們在共享
同一對象時對象的狀態:
public class Test {
??? public static void main(String[] args) throws Exception {
??????? Dog dog = new Dog("Sager",100);
??????? new DisplayDog(dog).start();
??????? new DisplayDog(dog).start();
??????? new DisplayDog(dog).start();
??? }
}
運行這個例子你可以等上一個月,雖然運行一年都正常并不能說明第366天不出現異常,但我們可以把這樣的結果認為是一種
說明.多個線程共享一個不變類的實例時,這個實例的狀態不會發生改變.事實上它沒有地方讓你去改變.
在臨界區模式中有些操作必須只允許有一個線程操作,而這個類本身以及對它的訪問類中并不需要進行臨界區保護,這就讓多
個線程不必等待從而提高了性能.
既然有這么好的優勢,那我們在需要臨界區保護的對象為什么不都設計成不變類呢?
1.不變類設計起來有一定難度.對于上面這個用來示例的Dog,由于其本身的屬性,方法都很簡單,我們還可以充分地考慮到可
以改變它狀態的各種情況.但對于復雜的類,要保證它的不變性,是一個非常吃力的工作.
?
不變類中,任何一個必要都件都不是充要條件,雖然連老骨灰都沒有這么說過,但我還是要真誠地目光深邃語氣凝重地告訴你.
沒有任何條件是充要條件的意思就是如果任何一個必要條件你沒考慮到,那它就會無法保證類的不可變性.沒有規范,沒有模
板,完全看一人設計人員的經驗和水平.也許你自以為考慮很全面的一個"不變類"在其他高手面前輕而易舉地就"可變"了.
2.不變類的種種必要條件限制了類設計的全面性,靈活性.這點不用多說,簡單說因為是不變類,所以你不能A,因為是不變類,你
不能B.
當然,如果你是一人很有經驗的設計者,你能成功地設計一個不變類,但因為它的限制而失去一些功能,你就要以使用與之相輔
的可變類.并且它們之間可以相互轉換,在需要不變性操作的時候以不變類提供給用戶,在需要可變性操作的時候以可變類提供
給用戶.
在jdk中String被設計為不可變類,一旦生成一個String對象,它的所有屬性就不會被變,任何方法要么返回這個對象本身的原
始狀態,要么拋棄原來的字符串返回一個新字符串,而絕對不會返回被修改了的字符串對象.
但是很多時候返回新字符串拋棄原來的字符串對象這樣的操作太浪費資源了.特別是在循環地操作的時候:
?String s = "Axman";
?for(int i=0;i<1000*1000;i++) s += "x";這樣的操作是致命的.
那么這種時候需要將原始的不變的s包裝成可變的StringBuffer來操作,性能的改變可能是成千上萬倍:
??????? StringBuffer sb = new StringBuffer(s); //將不變的String包裝成可變的String;
??????? for(int i=0;i<1000*1000;i++)
??????????? sb.append("x");
??????? s = new String(sb); //將可變類封裝成不變類.雖然可以調用toString(),但那不是可變到不變的轉換.
在將可變類封裝到不變類的時候要特別小心.因為你傳入的引用在外面是可以被修改的.所以即使你不變類本身不能去改變屬
性但屬性有一個外部引用.可以在外面修改:
final class MutableDog{
??? private String name;
??? private int age;
??? public MutableDog(String name,int age){
??????? this.name = name;
??????? this.age = age;
??? }
??? public synchronized void setDog(String name,int age){
??????? this.name = name;
??????? this.age = age;
??? }
??? public String getName(){return this.name;}
??? public int getAge(){return this.age;}
??? public synchronized String toString(){
??????? return "Dog's name = " + this.name + ",age = " + this.age;
??? }
???
???? public synchronized ImmatableDog getImmatableDog(){
???????? return new ImmatableDog(this);
???? }
}
final class ImmatableDog{
??? private final String name;
??? private final int age;
??? public ImmatableDog(String name,int age){
??????? this.name = name;
??????? this.age = age;
??? }
???
??? public ImmatableDog(MutableDog dog){
??????? this.name = dog.getName();
??????? this.age = dog.getAge();
??? }???
???
??? public String getName(){return this.name;}
??? public int getAge(){return this.age;}
???
??? public String toString(){
??????? return "Dog's name = " + this.name + ",age = " + this.age;
??? }
}
MutableDog類是可變的,可以滿足我們利用對象的緩沖來讓對象成為表示另一個實體的功能.但它們之間
隨時可以根據需要相互轉換,但是我們發現:
??? public ImmatableDog(MutableDog dog){
??????? this.name = dog.getName();
??????? this.age = dog.getAge();
??? }
這個方法是不安全的.當一個屬性為"Sager",100的dog被傳進來后,執行this.name = dog.getName();后,
如果線程切換到其它線程執行,那么dog的屬性就可能是"p4",80,這時再執行this.age = dog.getAge();
我們就會得到一個屬性為"Sager",80的這樣一個錯誤的不可變對象.這是一個非常危險的陷井.在這里我們
可以通過同上來解決:
??? public ImmatableDog(MutableDog dog){
??????? synchronized(dog){
??????????? this.name = dog.getName();
??????????? this.age = dog.getAge();
??????? }
??? }
注意這里同步的MutableDog,它將會和MutableDog的setDog產生互斥.它們都需要獲取同一MutableDog對象的
鎖,如果MutableDog的setDog不是方法同步(synchronized(this)),即使ImmatableDog(MutableDog dog)中同步
了dog,也不能保證安全,它們需要在同一對象上互斥.