了解Java的朋友們都知道jdk提供的用于保證線程安全的鎖有兩類:内部鎖synchronized和顯示鎖Lock,本文對内部鎖synchronized做一些簡要的分析彙總。
内部鎖的使用範式
1.同步執行個體方法
int count;
synchronized void syncA() {
count++;
}
等效于:
int count;
void syncA() {
synchronized (this) {
count++;
}
}
上述兩個等效的同步執行個體方法都是同步在this目前對象。
2.同步靜态方法(類方法)
public class Foo {
static int classCount;
static synchronized void syncB() {
classCount++;
}
}
等效于:
public class Foo {
static int classCount;
static void syncB() {
synchronized (Foo.class) {
classCount++;
}
}
}
上述兩個同步的類方法都是同步在類對象Foo.class上面,類對象也是對象。
實際中我們也會經常這樣使用:
private final Object lock = new Object();
int count;
void syncA() {
synchronized (lock) {
count++;
}
}
作為鎖對象(鎖句柄)使用的lock最好要聲明為不可變對象,因為對多個線程來說,隻有同步在相同的鎖(同一把鎖)上才有意義,才能保證共享資料的安全。
内部鎖的特點
- 是互斥的,
- 是可重入的
- 是非公平的
互斥是指鎖一次隻能被一個線程持有:
内部鎖互斥
可重入是指一個線程持有鎖A,那麼它還可以繼續執行被鎖A保護的其它方法(代碼):
public class Foo2 {
private static final Object lock = new Object();
static void syncA() {
synchronized (lock) {
System.out.println("syncA: do something");
syncB();
}
}
static void syncB() {
synchronized (lock) {
System.out.println("syncB: do something");
}
}
public static void main(String[] args) {
syncA();
}
}
執行main方法可看到如下輸出;
内部鎖是可重入的
内部鎖的可重入是由JVM實作的,在對象頭中會記錄重入的次數,重入時隻需加1即可,無需再次走申請鎖的耗費資源的流程。
非公平是指多個線程在搶占鎖時JVM并不會保證線程先來後到的順序,非公平性可以提升吞吐量,因為少了維護線程順序的開銷.
内部鎖的簡要原理
内部鎖synchronized在JVM中的實作被稱為monitor,即螢幕,是以也叫螢幕鎖。對應的位元組碼指令為:
monitorenter:配置設定鎖
monitorexit:釋放鎖
synchronized對應的位元組碼指令monitorenter和monitorexit總是成對出現(申請到鎖就能釋放鎖),是以你在代碼中使用synchronized無需手動釋放鎖,釋放鎖由JVM保證。如下簡單的代碼
public class Foo {
private static final Object lock = new Object();
static int count;
static void syncA() {
synchronized (lock) {
count++;
}
}
public static void main(String[] args) {
syncA();
System.out.println(count);
}
}
方法syncA的位元組碼指令:
内部鎖的位元組碼指令
官方對monitor的描述:Java中每一個對象都有monitor
内部鎖的monitor模型
這也就回答了為什麼Object類中會有wait/notify/notifyAll等方法
内部鎖的優化和細分類型
内部鎖在代碼層面對應的是synchronized關鍵字,從Java7開始JVM已經開始對synchronized進行優化,并不會像早期實作中直接進入重量級鎖模式。JVM對内部鎖的優化有:
- 支援鎖消除,即無鎖(JIT編譯器利用逃逸分析和内聯優化進行運作時的優化處理)
- 支援偏向鎖,對象頭中有記錄目前鎖是否是偏向鎖及偏向線程的id
- 支援鎖自适應,搶鎖的線程可以自旋也可以直接更新為重量級鎖
内部鎖的優化細分類型