Java synchronized關鍵字詳解

synchronizedJava中的同步關鍵字。

如果一個對象可能被多個線程同時訪問,我們將這個資源稱之為臨界資源。當多個線程同時訪問一個資源時,可能出現數據不一致或不完整的情況。所以需要采取同步措施,保證同一時間內只有一個線程訪問該資源。

synchronized修飾符實現的同步機制叫互斥鎖機制,它所獲得的鎖叫做互斥鎖,每個對象都有一個鎖標記。

synchronized 可以修飾方法,代碼塊。

修飾代碼塊

看一個栗子:


/**
 * @author zhaojiabao 2017/8/31
 */

public class TestSynchronized {

    private void func1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println("func1: " + Thread.currentThread());
            }
        }
    }

    public static void main(String[] args) {
        TestSynchronized obj1 = new TestSynchronized();

        new Thread(obj1::func1).start();

        new Thread(obj1::func1).start();
    }
}

輸出:

func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]

可以看到,雖然Thread1Thread2 是并發執行的,但等到Thread1執行完畢后Thread2才開始執行。
是因為這兩個線程同時訪問了同一個對象的synchronized代碼塊。所以Thread2的訪問被阻塞了。直到Thread1訪問完畢,Thread2才可以訪問synchronized代碼塊。

注意這里鎖定的是this:

synchronized (this) 

規則是這樣的:
則當一個線程在訪問某個對象的synchronized (this)代碼塊時,其他試圖訪問這個對象中所有被
synchronized (this)修飾的代碼塊的線程都會被阻塞。

也可以用一個特定的對象來作為線程鎖:

Object syncObject = new Object();
synchronized (syncObject) {
    //TODO
}

this也是一個對象,本質上是一樣的。

修飾方法

當一個方法被synchronized修飾時,線程想要執行該方法,必須拿到這個方法所屬對象的鎖。
看一個例子:

/**
 * @author zhaojiabao 2017/8/31
 */

public class TestSynchronized {

    private synchronized void func2() {
        for (int i = 0; i < 10; i++) {
            System.out.println("func2: " + Thread.currentThread());
        }
    }

    private void func1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println("func1: " + Thread.currentThread());
            }
        }
    }

    public static void main(String[] args) {
        TestSynchronized obj1 = new TestSynchronized();

        new Thread(obj1::func2).start();
        new Thread(obj1::func1).start();
    }
}

輸出:

func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]

可以看到,由于Thread1拿到了obj1對象鎖,故Thread2的執行被阻塞, 直到Thread1執行完畢后Thread2才開始執行。

類鎖

類鎖的兩種寫法:

synchronized (Foo.class) {

}

2.同步的static方法:

synchronized static void foo() {

}

類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步。
對象鎖是用來控制實例方法之間的同步。
這就是區別。

PS:
其實我感覺第一種和第二種寫法其實是不等價的。因為靜態方法只能訪問類的靜態變量,所以第一種同步的其實是類的靜態變量;而第二種方法同步是可以訪問類的靜態和非靜態變量,所以相當于將靜態/非靜態變量都同步了,綜上,兩種寫法其實應該是不等價的。

使用一個局部變量作為線程鎖

正常情況下,這種寫法是沒有意義的。
因為是局部變量,多線程是無法共享的。借用圣騎士Wind博客中的一句話:

如果一個變量是局部變量,那么每個線程都會有一個該局部變量的拷貝(即便是同一個對象中的方法的局部變量,也會對每一個線程有一個拷貝),一個線程對該局部變量的改變不會影響到其他線程。

既然局部變量不會被多個線程共享,那么多個線程拿到根本不是同一個對象,也就意味著不是同一把對象鎖。綜上,局部變量作為線程鎖是沒有意義的。

但是,某些時候你會發現,有人會把一個局部變量作為線程鎖,看下Stack Overflow上的這個問題:

use local variable as thread sync lock

對象鎖的選擇

這里稍微裝下逼,當我們需要一個對象鎖時,一般都會這樣寫:

Object mSyncObj = new Object();

咳咳,其實有一種逼格更高的寫法:

byte[] mSyncObj = new byte[0];

零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯后的字節碼:生成零長度的byte[]對象只需3條操作碼,而

Object mSyncObj = new Object();

則需要7行操作碼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,726評論 0 11
  • 一:java概述:1,JDK:Java Development Kit,java的開發和運行環境,java的開發工...
    ZaneInTheSun閱讀 2,686評論 0 11
  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,482評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,981評論 1 18
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,739評論 18 399