風人園

          弱水三千,只取一瓢,便能解渴;佛法無邊,奉行一法,便能得益。
          隨筆 - 99, 文章 - 181, 評論 - 56, 引用 - 0
          數據加載中……

          多線程編程的設計模式 不變模式(zt)

          http://blog.csdn.net/axman/category/231455.aspx
          多線程編程的設計模式 不變模式(一)
          雖然這個模式的名稱已經有人介紹過,但我仍然要以我的方式來介紹它,因為我對這個模式要說的
          東西比現有的我所能看到的介紹更全面更深入.
          ?
          一.不變模式需要的基礎.
          模式雖然顯得高深,但學習它的難度很低,即使你只是一個中級水平的程度員,也可以在很短的時候
          學會一個模式(當然要能正確地運用那需要豐富的經驗積累).對于基礎知識的深入才是一個優秀的
          程序員所具備有必要條件.
          ?
          我下面要說的話不是說國內沒有基礎深厚的java技術人員,而是說沒有基礎深厚卻又能說清楚讓別
          人也深厚起來的人.簡單說高手隨處可見,但可以做師父的高手我沒有見到.那么就讓我這個不是高
          手的人來補一堂Java最最基礎的知識吧.
          ?
          基礎類型和引用類型的內存布局:
          int x = 1;
          int y = x;
          x ++;
          System.out.println(y);
          有99.99%的Java程序員都知道y為1.當把類型為int的變量x作為類型為y的變量的值時,堆棧中會產生一個與x同樣大小的值,但無法改變它們是兩個不同的副本事實.它們互不相關,僅僅是在”創建” y的依據,完成后它們之間沒有任何關系,既然沒有任何關系,當然修改其中一個就不會影響另一個.這時它們有內存布局為:
          ??????
          為了簡化說明引用類型我們用Integer來作為例子.它的值作為它最重要的屬性被表示出來:
          ?
          Integer x ?= ?new Integer(100);
          Integer y ?= ?x;
          這時正確的表述為產生了兩個指向同一對象的變量(或引用).只要看它們的內存而局就知道它們的意義了:
          ?
          ?
          對于引用類型,改變引用本身并不影響其它指向同一對象的引用,改變引用本身就是把引用指點向另一個對象,簡單說就是重新賦值.假如 x = new Integer(101);那么它并不影響y指向原來的100.
          但改變對象的內容則同時影響其它指向同一引用的變量,因為Integer被設計為不變模式,所以我們目前不能修改它的內容,也就是不能個性100為101.下面的內容中會介紹修改對象內容.
          對于引用參數傳遞,方法外的引用和方法內的引用雖然變量名在語法相同,但實際變量本身因為作用范圍原因是不
          同的,但它們都指向同一對象.
          StringBuffer sb = new StringBuffer("xxxx");
          amethod(sb){
          ??
          }
          這是發生的內存而局如下:
          ?
          在沒有以不變模式來介紹String的時候,我們先來看String類的其它幾個屬性,其實String類要說的東西太多,這里只說幾個必要的知識:
          ?????? String s1 = “1111”;
          String s2 = “1111”;
          String 是唯一可以直接賦常量值的類(這只是對于程序員而言,也就從語法而言),對于這樣的字面值賦值,其底層就是調用String s1 = new String(“1111”). .intern();
          即先在字符串緩存池中查找是否有字符常量”1111”,如果有測將s1指向這個對象.沒有則先創建對象放入字符串緩存池,然后將s1指向它,并銷毀堆中的對象,當String s2 = “1111”;時仍然是調用
          String s2 = new String(“1111”). .intern();這時已經找到字符常量”1111”,所以s2也指向了字符串緩存池中那個對象,并銷毀了堆中的”1111”,這樣做的結果就是直接賦字面值的語句如果字面值相同它們就都指向同一對象.
          ?
          因為字符串類是不變模式最典型的代表,所以其它的知識將在下面繼續介紹.

          多線程編程的設計模式 不變模式(二)

          ?不變模式(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,也不能保證安全,它們需要在同一對象上互斥.

          但同步也并不一定能保證傳入的參數不可變:

          我曾以下面這個例子來作為對一個Java程序員的終極測試,終極測試的意思是說,如果你不懂并不說明你水平
          差,但如何你懂這個問題那就沒有必要測試其它問題了.

          ??? public static void test(Object[] objs){
          ??????? java.security.BasicPermission bp? =? xxxxx;

          ??????? for(Object o: objs){
          ??????????? bp.checkGuard(o);
          ??????? }
          ??????? for(Object o: abjs){
          ??????????? o.xxx;
          ??????? }
          ??? }
          當一個數據被傳入后我們需要對其中的每個元素做安全性檢查,如果通不過bp.checkGuard(o);自己會拋出
          異常的.但如果objs[0]被bp.checkGuard(o);過后,外面的線程通過objs去修改objs[0],這時就會把一個沒有
          經過安全檢查的對象繞過bp.checkGuard(o);而直接被調用.假如Runtime.exec(String[] args)就是這樣實
          現我們可以想象會出現什么問題.

          所以對于這樣的傳入參數,我們可以將其在方法類復制為本地變量(數組).或使用它的深度clone,打斷與方法
          外的聯系:

          ??? public static void test(Object[] objs){
          ?Object tmp = new Object[objs.lenth];
          ?System.arrayCopy(objs,tmp,0,0,objs.lenth);
          ?java.security.BasicPermission bp? =? xxxxx;

          ??????? for(Object o: tmp){
          ??????????? bp.checkGuard(o);
          ??????? }
          ??????? for(Object o: tmp){
          ??????????? o.xxx;
          ??????? }
          ??? }


          先說到這里吧.休息一下.?

          posted on 2006-12-16 14:16 風人園 閱讀(293) 評論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 吉木萨尔县| 饶阳县| 方城县| 峡江县| 资溪县| 澎湖县| 呼图壁县| 张家港市| 塔城市| 天津市| 芜湖市| 泸西县| 英山县| 新乡县| 凤凰县| 临漳县| 禹城市| 谢通门县| 大化| 若尔盖县| 景泰县| 普格县| 岑溪市| 贞丰县| 吉安市| 柳林县| 湾仔区| 巨野县| 扎鲁特旗| 皋兰县| 汝南县| 襄垣县| 合山市| 垣曲县| 五华县| 麦盖提县| 林州市| 温泉县| 安达市| 繁昌县| 长葛市|