synchronized 代碼塊怎麼用


加不加 synchronized 有什麼區別?

synchronized 作爲悲觀鎖,鎖住了什麼?

之前 2 篇文章我們已經知道 synchronized 的使用方法以及鎖的內容(實例對象和Class類對象),這已經涵蓋了這個關鍵字的基本內容了,今天這篇想介紹一下另一種寫法,就是同步代碼塊,它實現了更細粒度的同步方式。下面來見分曉。

先給大家介紹一下同步代碼塊怎麼寫,大體的代碼框架是這樣:

synchronized(xxx) {
    
}

xxx 可以是 this 或者 Object 或者 xxx.class,下面我們就根據這 3 種不同加鎖方式進行展開討論。

this

表示的是鎖住當前對象,和原來使用同步實例方式一樣,鎖住了當前的對象。

public class SynchronizedCodeTest {

    public static void main(String[] args) {
        SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest();
        for (int i = 0; i < 5; i ++) {
            Thread thread = new Thread(() -> {
                synchronizedCodeTest.testSynchronizedCode();
            });
            thread.start();
        }
    }

    int count = 0;
    public void testSynchronizedCode() {
        System.out.printf("%s-testSynchronizedCode-start-count=%s\n", Thread.currentThread().getName(), count);
        synchronized (this) {
            System.out.printf("%s-testSynchronizedCode-synchronized-start-count=%s\n", Thread.currentThread().getName(), count);
            count ++;
            System.out.printf("%s-testSynchronizedCode-synchronized-end-count=%s\n", Thread.currentThread().getName(), count);
        }
        System.out.printf("%s-testSynchronizedCode-end-count=%s\n", Thread.currentThread().getName(), count);
    }

}

運行結果:

我們主要關注紅色框和藍色框的這部分結果,紅色框裏面是同步塊的代碼,線程之間是互斥的,但是藍色框中Thread-0在執行同步塊的過程中,其他線程非同步塊也在執行,這裏說明了鎖的粒度確實變小了,變成了方法裏面的同步塊代碼之間互斥,非同步塊代碼不互斥,count 的值最終是 5,說明到執行到同步塊時,同一時刻只有一個線程在執行。

我們再寫個測試代碼,看一看 synchronized(this) 是鎖住什麼?

public class SynchronizedCodeTest {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i ++) {
            SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest();
            Thread thread = new Thread(() -> {
                synchronizedCodeTest.testSynchronizedCode();
            });
            thread.start();
        }
    }
}

運行結果:

觀察到紅色框裏面就可以發現,這時候同步塊不起效果了,並且 count 最終都是 1,證明 synchronized(this) 鎖住的是當前的對象,和 public synchronized void testSynchronizedMethod() 一樣。

Object

同步代碼塊帶來了靈活性,它不再只是鎖住當前對象了,可以鎖住任何我們創建的對象,下面就來看看。


public class SynchronizedCodeTest {

    public static void main(String[] args) {
        Object lock = new Object();
        SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(lock);
        for (int i = 0; i < 5; i ++) {
            Thread thread = new Thread(() -> {
                synchronizedCodeTest.testSynchroniedLock();
            });
            thread.start();
        }
    }

    int count = 0;

    Object lock = null;
    public SynchronizedCodeTest(Object lock) {
        this.lock = lock;
    }

    public void testSynchroniedLock() {
        System.out.printf("%s-testSynchroniedLock-start-count=%s\n", Thread.currentThread().getName(), count);
        synchronized (lock) {
            System.out.printf("%s-testSynchroniedLock-synchronized-start-count=%s\n", Thread.currentThread().getName(), count);
            count ++;
            System.out.printf("%s-testSynchroniedLock-synchronized-end-count=%s\n", Thread.currentThread().getName(), count);
        }
        System.out.printf("%s-testSynchroniedLock-end-count=%s\n", Thread.currentThread().getName(), count);
    }

}

運行結果:

這段代碼,我們創建了一個 lock 對象,作爲參數傳入到 synchronizedCodeTest 對象裏,我們看到結果裏面,5 個線程在同步塊代碼裏是串行執行的,count 最終也得到結果是 5。這段代碼沒有看出鎖對象帶來的靈活性,下面再看一個例子,把測試代碼稍微改一下,讓每個線程都有自己的 synchronizedCodeTest 對象。

public static void main(String[] args) {
	Object lock = new Object();
	for (int i = 0; i < 5; i ++) {
	SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(lock);
		Thread thread = new Thread(() -> {
			synchronizedCodeTest.testSynchroniedLock();
		});
		thread.start();
	}
}

運行結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cnBW3uMb-1588035891488)(http://www.liebrother.com/upload/d1b3abb8b71c41dabc3010e8ad05cea2_image.png)]

結果我們發現,雖然我們爲每個線程創建一個 synchronizedCodeTest 對象,但是不管怎麼運行,5 個線程的同步代碼塊都是串行執行的,原因在於,我們只創建了一個 lock 對象,這 5 個 synchronizedCodeTest 的 lock 對象都是同一個,因此競爭資源是同一個,纔出現這種情況。看是不是比同步方法靈活多了,上一篇中,我們要讓多個實例同步執行,我們需要使用靜態同步方法,現在不用了,使用同步代碼塊就可以,只需要滿足鎖住同一個實例對象就行。

另外,這個例子的結果是每個實例的 count 最終都爲 1,這是因爲每個 synchronizedCodeTest 對象都有自己獨立的變量 count,所以線程之間互不影響。

xxx.class

再來看看最後一種代碼塊鎖 Class 類,這和 public static synchronized testSynchronizedStatic() 的作用是一樣的,區別就只是代碼塊的鎖範圍可變。我們直接看看代碼例子。

public class SynchronizedCodeTest {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i ++) {
            SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest();
            Thread thread = new Thread(() -> {
                synchronizedCodeTest.testSynchronizedCodeClass();
            });
            thread.start();
        }
    }

    int count = 0;
    public void testSynchronizedCodeClass() {
        System.out.printf("%s-testSynchronizedCodeClass-start-count=%s\n", Thread.currentThread().getName(), count);
        synchronized (SynchronizedCodeTest.class) {
            System.out.printf("%s-testSynchronizedCodeClass-synchronized-start-count=%s\n", Thread.currentThread().getName(), count);
            count ++;
            System.out.printf("%s-testSynchronizedCodeClass-synchronized-end-count=%s\n", Thread.currentThread().getName(), count);
        }
        System.out.printf("%s-testSynchronizedCodeClass-end-count=%s\n", Thread.currentThread().getName(), count);
    }

}

運行結果:

每個線程都有自己的實例,但是鎖住 Class 會使每個線程實例對象的同步塊都是串行執行,這個結果和上面的多個實例鎖住同一個 Object 對象的結果是一樣的。我個人更傾向於使用鎖同一個 Object 對象,而不是鎖 Class 類對象。

總結

這篇介紹了synchronizd 代碼塊的 3 種使用方式,並詳細介紹了各自的使用方式和區別。簡單的列個表。

類型 使用方式 鎖作用範圍
this synchronized(this){} 鎖住當前的實例對象
object synchronized(lock){} 鎖住其他實例對象,比較靈活
xxx.class synchronized(xxx.class){} 鎖住 Class 對象

總共用了 3 篇文章來給大家介紹 synchronized 的具體用法,主要是因爲之前有些文章一下子就進入 Java 源代碼和 JVM 源碼,讓不少朋友感覺有點吃力,甚至有朋友一看標題就不進來看看了,所以這次先介紹一些比較簡單的用法,讓大家先了解了解,後面幾篇會比較深入 synchronized 原理性的文章,希望大家也能看看,會有非常大的收穫的。

原創不易,大家多幫忙轉發,點在看。

推薦閱讀

synchronized 作爲悲觀鎖,鎖住了什麼?

加不加 synchronized 有什麼區別?

從 JVM 視角看看 Java 守護線程

寫了那麼多年 Java 代碼,終於 debug 到 JVM 了

全網最新最簡單的 openjdk13 代碼編譯

瞭解Java線程優先級,更要知道對應操作系統的優先級,不然會踩坑

線程最最基礎的知識

老闆叫你別阻塞了

吃個快餐都能學到串行、並行、併發

泡一杯茶,學一學同異步

進程知多少?

設計模式看了又忘,忘了又看?

後臺回覆『設計模式』可以獲取《一故事一設計模式》電子書

覺得文章有用幫忙轉發&點贊,多謝朋友們!

LieBrother

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