synchronized(修飾方法和代碼塊)
1. 含義
-
synchronized 是同步鎖,用來實現互斥同步。
-
在 Java 中,關鍵字 synchronized 可以保證在同一個時刻,只有一個線程可以執行某個方法或者某個代碼塊(主要是對方法或者代碼塊中存在共享數據的操作)。
-
synchronized 還可以保證一個線程的變化(主要是共享數據的變化)被其他線程所看到(保證可見性,完全可以替代 volatile 功能,但是 volatile 更輕量,還是要分場景使用)。
2. 用法
synchronized 包括三種用法:
- 修飾實例方法
- 修飾靜態方法
- 修飾代碼塊
2.1 修飾實例方法
所謂的實例對象鎖就是用 synchronized 修飾實例對象中的實例方法,注意是實例方法不包括靜態方法,如下:
public synchronized void increase() {
i++;
}
2.2 修飾靜態方法
當 synchronized 作用於靜態方法時,其鎖就是當前類的 class 對象鎖。由於靜態成員不專屬於任何一個實例對象,是類成員,因此通過 class 對象鎖可以控制靜態成員的並發操作。需要注意的是如果一個線程 A 調用一個實例對象的非 static synchronized 方法,而線程 B 需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因為訪問靜態 synchronized 方法占用的鎖是當前類的 class 對象,而訪問非靜態 synchronized 方法占用的鎖是當前實例對象鎖,二者的鎖並不一樣,所以不沖突。
public static synchronized void increase() {
i++;
}
2.3 修飾代碼塊
在某些情況下,我們編寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,可能會得不償失,此時我們可以使用同步代碼塊的方法對需要同步的代碼進行包裹,這樣就無需對整個方法進行同步操作了。
我們可以使用如下幾種對象來作為鎖的對象:
成員鎖
鎖的對象是變量
public Object synMethod(Object a1) {
synchronized(a1) {
// 操作
}
}
實例對象鎖
this 代表當前實例
synchronized(this) {
for (int j = 0; j < 100; j++) {
i++;
}
}
當前類的 class 對象鎖
synchronized(AccountingSync.class) {
for (int j = 0; j < 100; j++) {
i++;
}
}
3. 什么是可重入鎖
含義
所謂可重入鎖,指的是以線程為單位,當一個線程獲取對象鎖之后,這個線程可以再次獲取本對象上的鎖,而其他的線程是不可以的。(同一個加鎖線程自己調用自己不會發生死鎖情況)
意義
防止死鎖。
實現原理
通過為每個鎖關聯一個請求計數和一個占有它的線程。當計數為 0 時,認為鎖是未被占有的。線程請求一個未被占有的鎖時,jvm 將記錄鎖的占有者,並且將請求計數器置為 1 。如果同一個線程再次請求這個鎖,計數將遞增;每次占用線程退出同步塊,計數器值將遞減。直到計數器為0,鎖被釋放。
應用
synchronized 和 ReentrantLock 都是可重入鎖。
ReentrantLock 表現為 API 層面的互斥鎖(lock() 和 unlock() 方法配合 try/finally 語句塊來完成),synchronized 表現為原生語法層面的互斥鎖。
4. 互斥同步的缺點
互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題,因此這種同步也被稱為阻塞同步。而且加鎖方式屬於悲觀鎖(不管操作是否成功都加鎖)。