理解 Java synchronized

多線程開發離不開鎖機制,現在的Java語言中,提供了2種鎖,一種是語言特性提供的內置鎖,還有一種是 java.util.concurrent.locks 包中的鎖。對於synchronized的一些基本概念這裏就不重複了,很多博客都寫到了。這篇文章針對一個例子,講一下我的理解。

這是一個調用同步方法的同時,調用該對象非同步方法(但是內部含有同步塊)的例子

package tmp;

/**
 * Created by Gracecoder on 2017/11/24.
 */
public class Service {

    private String anyString = new String();

    public void a(){
        try {
            synchronized (anyString)
            {
                System.out.println("a begin");

                Thread.sleep(3000);

                System.out.println("a end");

            }
        }catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    synchronized public void b()
    {
        System.out.println("b begin");

//        synchronized (anyString) {
//           anyString = new String("hello");
//            System.out.println(anyString);
//        }
        System.out.println("b end");

    }

}
package tmp;

/**
 * Created by Gracecoder on 2017/11/24.
 */
public class ThreadA extends Thread {

    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.a();
    }
}
package tmp;

/**
 * Created by Gracecoder on 2017/11/24.
 */
public class ThreadB extends Thread{
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.b();
    }

}
package tmp;

/**
 * Created by Gracecoder on 2017/11/24.
 */
public class Run {

    public static void main(String[] args){
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        ThreadB b = new ThreadB(service);

        a.setName("A");
        a.start();

        b.setName("B");
        b.start();



    }
}
運行結果:
a begin
b begin
b end
a end

運行這段代碼首先線程A先獲取成員變量anyString的內置鎖,然後線程B訪問同步方法,獲取當前對象service的鎖,這裏anyString是對象service的成員變量,結果顯示它們異步調用了。

按照我一開始的想法,如果反一下,由線程B先訪問同步方法,獲取當前對象service的鎖之後,其他線程是否能獲取其對象成員的鎖? 我覺得 不能!但是通過運行代碼驗證,發現就算B先獲得service的鎖,A依舊能獲得其成員anyString的鎖,它們仍然是異步調用!

就很疑惑,我們獲取一個對象的鎖,但是不代表我們擁有其成員域對象的鎖!

小結論:

每個對象都可以用作一個實現同步的鎖,這些鎖被稱爲內置鎖!
對象的內置鎖和對象的狀態之間沒有內在的關聯,雖然大多數類都講內置鎖用作一種有效的加鎖機制,但對象的域並不一定通過內置鎖來保護。當獲取到與對象關聯的內置鎖時,並不能阻止其他線程訪問該對象,只能阻止其他線程獲取同一個鎖!
那顯然每個對象都有自己的內置鎖,儘管anyString是service的成員域,但它們兩各自的內置鎖也是不同的。所以就算反過來,B先獲取this(synchronized方法相當於同步塊synchronized(this))對象的鎖,A還是可以獲取anyString的鎖。除非我們在B的同步方法中,再次請求獲取anyString對象的鎖,那就會同步,阻塞到A完成了。


這裏還有一個類鎖的概念,在synchronized同步方法前面加個static,那麼訪問該方法,就要獲取當前類的鎖。

//同步的類方法
 synchronized public static void printA()
 {
    //todo
 }

注意:同樣的,類鎖和對象鎖也不存在關聯,它們是不同的鎖。類鎖並不包括該類具體對象的鎖,如同對象鎖並不包括其對象域的鎖。在具體代碼中,如果線程A先調用同步類方法(獲取了類鎖),線程B這時候調用同步方法,調用同步方法的前提是獲得當前對象鎖,而此時當前對象鎖空閒,所以可以異步執行。如果還有一個線程C要調用另一個同步類方法或者同步方法,則必須等A或B執行完畢後,才能獲得鎖,同步執行!

而類鎖對類的所有對象實例起作用的本質是:
實例對象如果要調用synchronized類方法,那麼就要獲取類鎖,此時不同對象獲取的類鎖都是同一個,那麼就會產生同步!

總結:

synchronized 同步的本質就是:是否使用同一個對象監視器
任何對象都有一個自己的內置鎖,同步或者異步,主要就是看多線程競爭的是否是同一把鎖。區分這一點纔是關鍵,當獲取到與對象關聯的內置鎖時,並不能阻止其他線程訪問該對象,只能阻止其他線程獲取同一個鎖!

synchronized(非this對象x)格式的寫法是將x對象本身作爲“對象監視器”,這樣就可以得出以下3個結論:

  1. 當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果(競爭this當前對象的鎖)
  2. 當其他線程執行x對象中synchronized同步方法時呈同步效果(競爭x對象)
  3. 當其他線程執行x對象方法裏面的synchronized(this)代碼塊時也是同步

需要注意的是:如果其他線程調用不加synchronized關鍵字的方法時,還是異步調用,因爲其他線程沒有去競爭鎖。

線程安全包括可見性和原子性,synchronized實現了原子性,同時間接現實可見性。而volatile只有可見性,沒有原子性。

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