Java的同步機制:synchronized關鍵字

 1、引言ide

因爲同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。Java語言提供了專門機制以解決這種衝突,有效避免了同一個數據對象被多個線程同時訪問的問題。函數

這套機制就是synchronized關鍵字,它包括兩種用法:synchronized 方法(同步方法)和synchronized語句塊(同步語句塊)。this

 

2、synchronized不一樣的修飾狀況spa

一、synchronized方法(同步方法):synchronized修飾類中的方法,以下所示:線程

class P implements Runnable { public synchronized void methodPA() { //...
 } }
View Code

二、synchronized語句塊(同步語句塊):帶有某具體對象的synchronized修飾類中方法內的語句,以下所示:3d

class P implements Runnable { public void methodPA(SomeObject so) { synchronized(so) {//so爲對象鎖得引用 //...
 } } }
View Code

 

3、幾個重要概念code

對象鎖:每一個對象都只有一個鎖,稱爲對象鎖;對象

synchronized鎖定的是哪一個對象:調用該synchronized方法的對象或者synchronized指定的對象;blog

線程拿到鎖(對象鎖)同上;進程

取得對象鎖的線程:某個對象有可能在不一樣的線程中調用同步方法,只有拿到對象鎖得線程,纔可使用該對象來調用同步方法。

 

4、對象鎖的理解

①不管synchronized加在方法上仍是加在對象上,線程拿到的鎖都是對象(關鍵要分析出synchronized鎖定的是哪一個對象),不能把某個函數或一段代碼看成鎖;

②每一個對象只有一個鎖;

③實現同步須要很大的系統開銷,甚至可能形成死鎖,因此儘可能避免無謂的同步。

 

5、理解線程同步(synchronized)

       用個例子來說解比較清晰:假設P一、P2是同一個類的不一樣對象,這個類中定義瞭如下幾種狀況的同步方法或同步塊,P1對象、P2對象可以執行它們。

一、synchronized方法(同步方法)

class P implements Runnable { public synchronized void methodPA() { //...
 } }
View Code

此時synchronized鎖定的是哪一個對象呢(對象鎖)?

synchronized鎖定的是調用該同步方法的對象,即對象P1在不一樣線程中調用該同步方法,這些線程間會造成互斥關係,只有拿到P1對象鎖的線程,纔可以調用該同步方法。

可是對於P1對象所屬類所產生的另外一對象P2而言,仍是可以任意調用這個被該同步方法,所以程式也可能在這種情形下襬脫同步機制的控制,形成數據混亂。

上邊的示例代碼等同於以下代碼:

class P implements Runnable { public synchronized void methodPA() { synchronized(this) { //...
 } } }
View Code

上述代碼中的this指的是調用這個方法的對象即P1。

 二、synchronized語句塊(同步語句塊)

class P implements Runnable { public void methodPA(SomeObject so) { synchronized(so) { //...
 } } }
View Code

此時鎖就是so這個對象,只有拿到這個鎖的線程,纔可以運行該鎖對象控制的這段代碼。當有一個明確的對象做爲鎖時,就可以這樣寫程式,但當沒有明確的對象做爲鎖,只是想讓一段代碼同步時,可以建立一個特別的instance變量對象來充當鎖:

class P implements Runnable { private byte[] lock = new byte[0];//特別的instance變量 

    public void methodPA(){ synchronized(lock){ //...
 } } }
View Code

三、synchronized修飾static函數

class P implements Runnable { public synchronized static void methodPA() { //...
 } public void methodPB(){ synchronized(P.class){ //...
 } } }
View Code

 

6、線程訪問對象鎖的規則—這裏比較特殊使用的是synchronized(this)

    ①當兩個線程訪問同一個對象的synchronized(this)代碼塊時,同一時間內只能有一個線程獲得執行,另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊;

    ②然而,當一個線程訪問對象的一個synchronized(this)代碼塊時,其餘線程仍然能夠訪問該對象的非synchronized(this)的代碼;

    ③尤爲關鍵的是,當一個線程訪問對象的一個synchronized(this)代碼塊時,其餘線程對該對象中其它全部synchronized(this)代碼塊的訪問將被阻塞(由於前一個線程得到了該對象的鎖,致使其餘線程沒法得到該對象的鎖)。

  例以下面的程序:當有線程進入同步代碼1時,其餘全部的線程都不能進入同步代碼1和同步代碼2,由於此時鎖1處於鎖上的狀態,但能夠進入同步代碼3,由於同步代碼3使用的是鎖2,與鎖1的狀態沒有關係。

synchronized(鎖1) { 同步代碼1 } synchronized(鎖1) { 同步代碼2 } synchronized(鎖2) { 同步代碼3 }
View Code

  使用如下代碼來說解一下線程同步的執行步驟:

public class MyThreadDemo { public static void main(String[] args) { //任意定義一個對象,傳給線程做爲鎖,能夠是任意對象,例如int myLock = 1;也能夠是內存中已經存在的對象,例如Object.class
        String myLock = "hi"; Thread t1 = new LockThread("t1",myLock); Thread t2 = new LockThread("t2",myLock); Thread t3 = new LockThread("t3",myLock); t1.start(); t2.start(); t3.start(); } } class LockThread extends Thread { private Object MyLock; public LockThread(String name, Object MyLock){ super(name); this.MyLock = MyLock; } public void run(){ System.out.println(currentThread().getName()+":我得到cpu了,準備進入同步代碼塊....."); synchronized(MyLock){ System.out.println(currentThread().getName()+":我進來了!"); System.out.println(currentThread().getName()+":歇會"); try { Thread.sleep(100); }catch(Exception e){ e.printStackTrace(); } System.out.println(currentThread().getName()+":走了!"); } } }
View Code

  執行結果以下,能夠看到當t1先進入同步代碼塊後,即便t3,t2得到了cpu,也沒有辦法進入到同步代碼塊中,直到t1出來,並開鎖後t2才能進入:

t1:我得到cpu了,準備進入同步代碼塊.....

t1:我進來了!

t1:歇會

t2:我得到cpu了,準備進入同步代碼塊.....

t3:我得到cpu了,準備進入同步代碼塊.....

t1:走了!

t3:我進來了!

t3:歇會

t3:走了!

t2:我進來了!

t2:歇會

t2:走了!
View Code
相關文章
相關標籤/搜索