synchronized使用及實現原理

1.Synchronized的基本使用

Synchronized是Java中解決併發問題的一種最經常使用的方法,也是最簡單的一種方法。Synchronized的做用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改可以及時可見(3)有效解決重排序問題。從語法上講,Synchronized總共有三種用法:html

  1. 修飾實例方法,做用於當前實例加鎖,進入同步代碼前要得到當前實例的鎖;
  2. 修飾靜態方法,做用於當前類對象加鎖,進入同步代碼前要得到當前類對象的鎖;
  3. 修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要得到給定對象的鎖。

1)、沒有同步的狀況:java

/**
 * 沒有同步的狀況
 *
 * Created by Jiacheng on 2018/6/28.
 */
public class SynchronizedTest {

    public void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
        new Thread(() -> test.method1()).start();
        new Thread(() -> test.method2()).start();
    }
}

2)、對普通方法同步:編程

/**
 * 同步實例方法
 * 
 * Created by Jiacheng on 2018/6/28.
 */
public class SynchronizedMethod {

    public synchronized void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedMethod test = new SynchronizedMethod();
        new Thread(() -> test.method1()).start();
        new Thread(() -> test.method2()).start();
    }
}

雖然method1和method2是不一樣的方法,可是這兩個方法都進行了同步,而且是經過同一個對象去調用的,因此調用以前都須要先去競爭同一個對象上的鎖(monitor),也就只能互斥的獲取到鎖,所以,method1和method2只能順序的執行。多線程

3)、靜態方法(類)同步:併發

/**
 * 同步靜態方法
 *
 * Created by Jiacheng on 2018/6/28.
 */
public class SynchronizedStatic {

    public static synchronized void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public static synchronized void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedStatic test = new SynchronizedStatic();
        final SynchronizedStatic test2 = new SynchronizedStatic();
        new Thread(() -> test.method1()).start();
        new Thread(() -> test2.method2()).start();
    }
}

雖然test和test2屬於不一樣對象,可是test和test2屬於同一個類的不一樣實例,因爲method1和method2都屬於靜態同步方法,對靜態方法的同步本質上是對類的同步(靜態方法本質上是屬於類的方法,而不是對象上的方法),因此調用的時候須要獲取同一個類上monitor(每一個類只對應一個class對象),因此也只能順序的執行。函數

4)、代碼塊同步:高併發

/**
 * 同步方法塊
 *
 * Created by Jiacheng on 2018/6/28.
 */
public class SynchronizedBlock {

    public void method1() {
        System.out.println("Method 1 start");
        try {
            synchronized (this) {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2() {
        System.out.println("Method 2 start");
        try {
            synchronized (this) {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedBlock test = new SynchronizedBlock();
        new Thread(() -> test.method1()).start();
        new Thread(() -> test.method2()).start();
    }
}

對於代碼塊的同步實質上須要獲取Synchronized關鍵字後面括號中對象的monitor,因爲這段代碼中括號的內容都是this,而method1和method2又是經過同一的對象去調用的,因此進入同步塊以前須要去競爭同一個對象上的鎖,所以只能順序執行同步塊。性能

2.用在代碼塊和方法上的區別?

  1. synchronized用在代碼塊鎖的是調用該方法的對象(this),也能夠選擇鎖住任何一個對象。
  2. synchronized用在方法上鎖的是調用該方法的對象,
  3. synchronized用在代碼塊能夠減少鎖的粒度,從而提升併發性能。
  4. 不管用在代碼塊上仍是用在方法上,都是獲取對象的鎖;每個對象只有一個鎖與之相關聯;實現同步須要很大的系統開銷做爲代價,甚至可能形成死鎖,因此儘可能避免無謂的同步控制。

3.synchronized實現原理

每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:this

1)若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。spa

2)若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.

3)若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。

synchronized 方法控制對類成員變量的訪問:每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)。
在 Java 中,不光是類實例,每個類也對應一把鎖,這樣咱們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。synchronized方法實際上等同於用一個synchronized塊包住方法中的全部語句,而後在synchronized塊的括號中傳入this關鍵字。固然若是是靜態方法,須要鎖定的則是class對象。
可能一個方法中只有幾行代碼涉及到線程同步的問題,因此synchronized塊比synchronized方法更近細粒度的控制了多個線程的訪問,只有synchronized塊中的內容不能同時被多個線程訪問,方法中的其餘語句仍然能夠同時被多個線程所訪問(包括synchronized塊以前和以後的)。

4.優缺點

使用synchronized,當多個線程嘗試獲取鎖時,未獲取到鎖的線程會不斷的嘗試獲取鎖,而不會發生中斷,這樣會形成性能消耗。

 

參考資料

《Java多線程編程實戰指南》

Java併發編程:Synchronized及其實現原理

Java Synchronized及實現原理

相關文章
相關標籤/搜索