多執行緒之間的競爭

pucheung發表於2015-03-29

      進行多執行緒程式設計,同步控制是非常重要的,而同步控制就涉及到了鎖。

       對程式碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,至於具體選擇什麼方式,就見仁見智了,同步塊不僅可以更加精確的控制物件鎖,也就是控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間。而且可以選擇要獲取哪個物件的物件鎖。但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的物件鎖,換句話說,也就是this物件,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因為可能包含了不需要進行同步的程式碼塊在內,也會降低程式的執行效率。而不管是同步方法還是同步塊,我們都不應該在他們的程式碼塊內包含無限迴圈,如果程式碼內部要是有了無限迴圈,那麼這個同步方法或者同步塊在獲取鎖以後因為程式碼會一直不停的迴圈著執行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的執行緒就永遠無法獲取這把鎖,這就造成了一種死鎖現象。


 
 詳細解說一下同步方法的鎖,同步方法分為靜態同步方法與非靜態同步方法。

       所有的非靜態同步方法用的都是同一把鎖——例項物件本身,也就是說如果一個例項物件的非靜態同步方法獲取鎖後,該例項物件的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,可是別的例項物件的非靜態同步方法因為跟該例項物件的非靜態同步方法用的是不同的鎖,所以毋須等待該例項物件已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。

        而所有的靜態同步方法用的也是同一把鎖——類物件本身,這兩把鎖是兩個不同的物件,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,而不管是同一個例項物件的靜態同步方法之間,還是不同的例項物件的靜態同步方法之間,只要它們同一個類的例項物件!

        總之,對與同一個例項的非靜態同步方法只對非靜態方法有效,靜態同步方法只對靜態同步方法有效,靜態同步方法和非靜態同步方法是互不干擾的

    而對於同步塊,由於其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間才有著競態條件,這就得具體情況具體分析了,但這裡有個需要注意的地方,同步塊的鎖是可以選擇的,但是不是可以任意選擇的!!!!這裡必須要注意一個物理物件和一個引用物件的例項變數之間的區別!使用一個引用物件的例項變數作為鎖並不是一個好的選擇,因為同步塊在執行過程中可能會改變它的值,其中就包括將其設定為null,而對一個null物件加鎖會產生異常,並且對不同的物件加鎖也違背了同步的初衷!這看起來是很清楚的,但是一個經常發生的錯誤就是選用了錯誤的鎖物件,因此必須注意:同步是基於實際物件而不是物件引用的!多個變數可以引用同一個物件,變數也可以改變其值從而指向其他的物件,因此,當選擇一個物件鎖時,我們要根據實際物件而不是其引用來考慮!作為一個原則,不要選擇一個可能會在鎖的作用域中改變值的例項變數作為鎖物件!!!!

 

Java執行緒:執行緒的同步與鎖

 
 
 
一、同步問題提出
 
執行緒的同步是為了防止多個執行緒訪問一個資料物件時,對資料造成的破壞。
例如:兩個執行緒ThreadA、ThreadB都操作同一個物件Foo物件,並修改Foo物件上的資料。
 
public class Foo { 
    private int x = 100; 

    public int getX() { 
        return x; 
    } 

    public int fix(int y) { 
        x = x - y; 
        return x; 
    } 
}
 
public class MyRunnable implements Runnable { 
    private Foo foo = new Foo(); 

    public static void main(String[] args) { 
        MyRunnable r = new MyRunnable(); 
        Thread ta = new Thread(r, "Thread-A"); 
        Thread tb = new Thread(r, "Thread-B"); 
        ta.start(); 
        tb.start(); 
    } 

    public void run() { 
        for (int i = 0; i < 3; i++) { 
            this.fix(30); 
            try { 
                Thread.sleep(1); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
            System.out.println(Thread.currentThread().getName() + " : 當前foo物件的x值= " + foo.getX()); 
        } 
    } 

    public int fix(int y) { 
        return foo.fix(y); 
    } 
}
 
執行結果:
Thread-A : 當前foo物件的x值= 40 
Thread-B : 當前foo物件的x值= 40 
Thread-B : 當前foo物件的x值= -20 
Thread-A : 當前foo物件的x值= -50 
Thread-A : 當前foo物件的x值= -80 
Thread-B : 當前foo物件的x值= -80 

Process finished with exit code 0
 
          從結果發現,這樣的輸出值明顯是不合理的。原因是兩個執行緒不加控制的訪問Foo物件並修改其資料所致。
 
        如果要保持結果的合理性,只需要達到一個目的,就是將對Foo的訪問加以限制,每次只能有一個執行緒在訪問。這樣就能保證Foo物件中資料的合理性了。
 
在具體的Java程式碼中需要完成一下兩個操作:
把競爭訪問的資源類Foo變數x標識為private;
同步哪些修改變數的程式碼,使用synchronized關鍵字同步方法或程式碼。
 
二、同步和鎖定
 
1、鎖的原理
 
Java中每個物件都有一個內建鎖
 
         當程式執行到非靜態的synchronized同步方法上時,自動獲得與正在執行程式碼類的當前例項(this例項)有關的鎖。獲得一個物件的鎖也稱為獲取鎖、鎖定物件、在物件上鎖定或在物件上同步。
 
當程式執行到synchronized同步方法或程式碼塊時才該物件鎖才起作用。
 
            一個物件只有一個鎖。所以,如果一個執行緒獲得該鎖,就沒有其他執行緒可以獲得鎖,直到第一個執行緒釋放(或返回)鎖。這也意味著任何其他執行緒都不能進入該物件上的synchronized方法或程式碼塊,直到該鎖被釋放。
 
釋放鎖是指持鎖執行緒退出了synchronized同步方法或程式碼塊。
 
關於鎖和同步,有一下幾個要點:
1)、只能同步方法,而不能同步變數和類;
2)、每個物件只有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個物件上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個執行緒要執行一個類中的synchronized方法,並且兩個執行緒使用相同的例項來呼叫方法,那麼一次只能有一個執行緒能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個執行緒在物件上獲得一個鎖,就沒有任何其他執行緒可以進入(該物件的)類中的任何一個同步方法
5)、如果執行緒擁有同步和非同步方法,則非同步方法可以被多個執行緒自由訪問而不受鎖的限制

6)、執行緒睡眠時,它所持的任何鎖都不會釋放。

7)、執行緒可以獲得多個重進入(synchronized )鎖。比如,在一個物件的同步方法裡面呼叫另外一個物件的同步方法,則獲取了兩個物件的同步鎖。

8)、同步損害併發性,應該儘可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分程式碼塊。
9)、在使用同步程式碼塊時候,應該指定在哪個物件上同步,也就是說要獲取哪個物件的鎖。例如:
    public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }
 
當然,同步方法也可以改寫為非同步方法,但功能完全一樣的,例如:
    public synchronized int getX() {
        return x++;
    }
    public int getX() {
        synchronized (this) {
            return x;
        }
    }
效果是完全一樣的。
 
三、靜態方法同步
 
要同步靜態方法,需要一個用於整個類物件的鎖,這個物件是就是這個類(XXX.class)。
例如:
public static synchronized int setName(String name){
      Xxx.name = name;
}
等價於
public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

 

相關文章