Java 多線程學習總結4

 

  線程間的通信
    如果線程只是傻傻地一個勁的“排他的”前行,那必然是愚蠢的。線程間需要通信,他們需協作才完成某項任務。線程間通常都是各顧各地前行,當然他們有公共資源的時候,需要調成按一定秩序執行。打個比方:福特汽車的流水線,for example,有一個流程是這樣的:做好底盤的工人把底盤放在傳送帶上,傳給上輪胎的工人。兩個部分的工人只是埋頭苦幹,大部分時間不需要協調。如果做底盤的工人速度慢了,那麼上輪胎的工人就閒着了,要等。如果上輪胎的工人很慢,做底盤的可以喝口水,抽口煙了,呵呵。這和線程很像:只關心自己的事情,但是在"邊緣"的地方,雙方需要協調。這就需要線程能有"停止"和"再啓動"的能力。對應的方法是:wait(),notify();其中wait()是釋放鎖資源。 鎖資源相當於CPU的時間片資源,需要注意的是它只能用在synchronized(鎖)起來的程序片段中,不可以亂放。notify是通知那些在等的線程,然後在這些等的線程中,產生一個幸運兒,佔有這個鎖。許多書說notifyAll()好,我們就直接用notifyAll得了,反正功能差不多,偶也說不清,就不說了,呵呵。還有一點,wait能釋放鎖資源,sleep可不行哦,面試經常要問的哦。
我們還是舉例子來說吧:經典的生產者和消費者:
  原始問題描述:有一個大籃子,姑娘不停地在玉米地摘玉米棒子,丟進大籃子裏面。小夥子不停地從籃子裏面拿玉米,扔到火鍋裏煮。
  問題升級1.姑娘不止一個,小夥子也不止一個。
  問題升級2.說不定有n個大媽從火鍋裏裏面撈玉米,準備包裝上市。
  如果用順序編程的想法考慮問題就是:玉米被姑娘摘下,放到籃子裏面,玉米被小夥拿出籃子,玉米被小夥放進鍋裏面,玉米被大娘拿出,包裝。呵呵,很冗長,不是嗎?
  如果一開始籃子裏面就有100個棒子,姑娘累了,想歇歇腰,小夥子照樣有活幹。小夥子想上茅房,大籃子還很空,姑娘 照樣可以一個人單幹。這是另一種勞動模式了,極大地解放了社會主義的生產力,值得提倡,呵呵。
  那麼,我們考慮原始問題的解答:
  第一步考慮到是公共資源,是什麼呢?籃子。
  第二步"臨界點":籃子滿了,姑娘不能放玉米了,要等小夥子;小夥子不能從空籃子裏面拿玉米,小夥子要等。
  小夥子如果從滿籃子裏面拿了一個玉米,要通知姑娘,你該幹活了。同理,姑娘從空籃子裏面放一個玉米要通知小夥 開工。
  第三步:併發問題的真正瓶頸,搞清楚,哪裏該併發,哪裏不該。

 問題如圖示:

  僞代碼:
  Java代碼
        vector //模擬籃子
        對小夥子來說:
        public synchronized YuMi getYuMi()
        {   YuMi yumi=new Yumi();
            if(vector.size()==0)
            {
               wait();
               //等待完畢,請立刻取玉米,不妨取最後一個。
               yumi=vector.last();
           
            }
            //如果籃子不爲空,盡情取
           else
           {   yumi=vector.last();
               //如果是從滿籃子中取的,please notify姑娘
               if(vector.size==vector.MAX-1)
               notifyAll(); 
               }       
        }  

同理,也能很快得到姑娘放玉米的邏輯:省略
        注意:也有人喜歡這樣寫,用while做判斷,這樣是簡潔許多,但是邏輯不夠清晰:

  Java代碼

    public synchronized YuMi getYuMi()
        {   YuMi yumi=new Yumi();
            while(vector.size()==0)
            {
               wait();
               //等待完畢,請立刻取玉米,不妨取最後一個。
            }
            //如果籃子不爲空,盡情取
             yumi=vector.last();
               //如果是從滿籃子中取的,please notify姑娘
              if(vector.size==vector.MAX-1)
              notifyAll(); 
                    
        }   

生產者和消費者模型的代碼如下:

公共資源類:

package producer_and_consumer;

import java.util.Vector;

public class Basket {
    private Vector vector=new Vector();
    private int Max=20;
   
    public synchronized String getYuMi()
    {
        String yumi=new String();
        while(vector.size()==0)
        {
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        yumi=(String) vector.elementAt(vector.size()-1);
        vector.remove(vector.size()-1);
        System.out.println(Thread.currentThread()+" "+"Get   "+ yumi);
        if(vector.size()==(Max-1))
        notifyAll();
           
        return yumi;
    }
   
    public synchronized void putYuMi(String str)
    {
        System.out.println(Thread.currentThread()+"  "+"Put  "+ str);
        while(vector.size()==Max)
        {
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        vector.add(vector.size(),str);
        if(vector.size()==1)
        notifyAll();
           
       
    }

}

生產者線程:

java代碼:

package producer_and_consumer;

public class Producer extends Thread{
    private Basket basket;
    private String prefix="YuMi";
    private String yumi;
    private int i=0;
   
    public Producer(Basket basket)
    {
        this.basket=basket;
    }
   
    public void run()
    {
        for(int j=0;j<100;j++)
        {  i++;
           yumi=prefix+i;
           try {
            Thread.sleep((int) ((long)1000* Math.random()));
            //爲了模擬生產者隔一段時間生產一個,同理也可以在消費線程中
            //加上這個隨機模擬,可以觀察輸出。
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
           basket.putYuMi(yumi);
        }
    }

}

消費者線程:

package producer_and_consumer;

public class Consumer extends Thread{
    private Basket basket;
   
   
    public Consumer(Basket basket)
    {
        this.basket=basket;
    }
    public void run()
    {   while(true)
    { 
        basket.getYuMi();
    }
    }
       

}

程序入口:

package producer_and_consumer;

public class main {
    public static void main(String args[])
    {
        Basket ball= new Basket();
        Thread producer=new Producer(ball);
        Thread consumer=new Consumer(ball);
        producer.start();
        consumer.start();
    }

}

輸入在自己機器上看,因爲太隨機了!

在繼續下面的時候,我們得討論一下鎖的對象和鎖的大小即鎖的粒度。

      class Object1
      {
       private Object2 obj2;
       public Object3 obj3=new Object3();
      
       public synchronize void function1()
       {
      
       }
      
       public void function2()
       {
           synchronized(obj3)
           {
          
          
           }
      
       }  
      } 
     

 

對象Object1中包含了對象Object2和Object3,function1()加鎖的粒度是object1,
      function2()加鎖的粒度是obj3,可以說,obj3要小一些。當然,不意味着obj1加鎖,就
      給obj2,obj3加了鎖,雖然類的組織是個包含關係,可是,鎖是加在對象或者類上的,(加在類上的
      是靜態方法,這裏不予討論,因爲我自己都沒研究過,呵呵。)

 

 

    爲什麼要這樣分呢,我們下面討論。
      從上面的那個生產者消費者可以看出鎖是加在整個vector上的,所以put和get方法,一次只能進一個。所以
      有同志覺得這不行,效率不高.如果我們現在考慮現在有n個生產者,n個消費者,我們希望把鎖的粒度降低爲
      vector中的一個個節點,各個結點顏色不同,不是可以併發,效率更好了嗎?猛然一想,這樣覺得確實挺酷。
      但是我覺得很難:線程都是埋頭苦幹型的,他最得意的時候就是不停地做,而不要關心其他線程在幹什麼,但是
      有時候不得不考慮:我們假設有一個容器,放東西的線程在放的時候,需要不需要考慮其他線程。當然需要,我們
      定位的時候,當然需要知道哪些位置是放過東西的,那些是沒放過的,這是有先後因果關係的,所以這些線程必須是順 序執行!,必須一個一個進,不能同時涌進來。當然,這是我個人看法,因爲我沒有找到,讓多個讓多個物體併發放入容器的方法。證明過程如下: 如果一個線程要把東西塞進容器,它需要知道容器的size,容器知道size,需要檢索每一個節點,每一個精確的檢索,不能讓其他線程破壞,所以證明沒有同時多個線程往容器中放東西的方法。當然也不是不可能,呵呵,如果換了一種數據結構,或者用hash技術,或者其他好的數據結構,希望有人能告訴我,呵呵。 至於圖9提出的想法,只能多個1對1的生產者消費者模型的n倍翻版,因爲任何一個節點滿了,都會導致放的線程 停止,而不會讓他自己去尋找其他的空白節點,呵呵。所以我們要討論一下,我們要在何時併發,哪裏能併發。

 

 

真正的瓶頸在哪裏?
      還是拿上面姑娘摘玉米,小夥子煮玉米,大娘搞包裝來說,姑娘們把玉米放在籃子很簡單,小夥子從籃子拿出玉米也簡單,呵呵,同理從火鍋裏拿放玉米也簡單,正在花費時間的是姑娘摘玉米,小夥子剝玉米皮,洗玉米,大娘搞包裝袋 這些花費的時間是拿、取的好幾個數量級,不是嗎,這些人可以併發嗎?當然,一個小夥子拿起一個自己的玉米,我想,他自己幹自己的,就行了,根本就不要和其他小夥子協作。所以這裏是真正要併發的地方。

 

  還有最後一個加強問題,如果大媽後面還有許多人,那怎麼搞?一直用線程嗎?我的建議是沒必要搞那麼多線程,可以把一些步驟合成一個順序執行,只在關鍵點,搞成併發的線程。不要爲了多線程而線程。

 

 所以,有了多線程,我們考慮問題的模式就變了:如果你一上來,就把一個"實體"的方法寫一個synchronizd, 然後口口聲聲地說,這個是線程安全的。你一定是綿羊座,O型血的人,您的世界觀一定很狹窄。真的猛士是不懼怕併發的。 我們應該以一種完全併發的角度來考慮問題,因爲併發太可愛了,他高效,直白。然後再考慮競爭條件,哪裏不能併發,併發有沒有意義。其中競爭條件是公共資源,併發的意義是搞清楚真正耗時的步驟。就像我一開始就沒搞清楚,從數據庫load數據到內存是個查詢的瓶頸一樣,所以搞清真正的瓶頸很關鍵。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章