大家好,我是王有志。關注王有志,一起聊技術,聊遊戲,從北漂生活談到國際風雲。最近搞了個抽獎送書的活動,歡迎點選連結參與。
如果Java面試有什麼是必問的,synchronized
必定佔據一席之地。初出茅廬時synchronized
的用法,成長後synchronized
的原理,可謂是Java工程師的「一生之敵」。
按照慣例,先來看synchronized
的常見問題:
根據統計資料可以總結出synchronized
的5大考點:
synchronized
的使用方式:
synchronized
是什麼?synchronized
怎麼用?synchronized
的實現原理:
synchronized
的特性是如何實現的?synchronized
鎖升級的原理。今天我們先來看synchronized
的基礎部分。
synchronized
是Java中的關鍵字,提供了原生同步機制,實現互斥語意和可見性保證,通常稱為互斥鎖。
synchronized
修飾的語句內修改共用變數可以立即被其它執行緒獲取。互斥就意味著,同一時間只有一個執行緒執行synchronized
修飾的程式碼,那麼:
synchronized
中不存在有序性問題;synchronized
中程式碼,無需考慮原子性問題。因此synchronized
中互斥就代表了對有序性問題和原子性問題的保證。不過前提是JSR-133中反覆提到的correctly synchronized(正確的同步),舉個例子:
public class IncorrectlySynchronized {
private Integer count = 0;
public void add() {
synchronized (count) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
IncorrectlySynchronized incorrectlySynchronized = new IncorrectlySynchronized();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
incorrectlySynchronized.add();
}
});
Thread t2 = new Thread(()-> {
for (int i = 0; i < 10000; i++) {
incorrectlySynchronized.add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(incorrectlySynchronized.count);
}
}
看似該加synchronized
的地方都加了,但是結果卻會出乎意料,這就典型的錯誤同步的例子。
既然是鎖,那麼synchronized
鎖的是什麼呢?
《The Java Language Specification》中描述(節選)到:
Each object in Java is associated with a monitor, which a thread can lock or unlock.
The synchronized statement computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed.
Java中每個物件都與一個監視器關聯,執行緒可以鎖定或者解鎖該監視器。synchronized
語句嘗試鎖定與物件關聯的監視器,鎖定成功後才可以繼續執行。
通常,我們將synchronized
鎖定與物件關聯的監視器理解為synchronized
鎖定物件本身。
在我們知道synchronized
鎖什麼後,再去看用法,很多內容就會一目瞭然了。
作為關鍵字,synchronized
有兩種用法:
之前有個同事特別迷信「背技術」,為了區分不同用法的效果,背了某機構的「執行緒八鎖」,但每過一段時間就會忘記。
其實,知道了synchronized
鎖什麼,不同用法的效果自然就出來了,看一個例子:
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
Thread t1 = new Thread(synchronizedDemo::lockMemberMethod1);
Thread t2 = new Thread(synchronizedDemo::lockMemberMethod2);
t1.start();
// 確保t1先執行
TimeUnit.SECONDS.sleep(1);
t2.start();
}
private synchronized void lockMemberMethod1() {
System.out.println("方法1");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized void lockMemberMethod2() {
System.out.println("方法2");
}
}
通過範例變數呼叫成員方法時,會隱式的傳遞this
。這個例子中,t1和t2想鎖定的監視器是誰的?synchronizedDemo
物件的。t1先獲取到,那麼t2只能等待t1釋放後再獲取了。
那此時的鎖定範圍是什麼?synchronizedDemo
物件。
修改下程式碼:
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();
Thread t1 = new Thread(synchronizedDemo::lockMemberMethod1);
Thread t2 = new Thread(synchronizedDemo2::lockMemberMethod2);
t1.start();
t2.start();
}
t2不再爭奪synchronizedDemo
而是爭奪synchronizedDemo2
,結果上也能看出t1和t2之間不存在競爭關係。
那麼使用synchronized
修飾靜態方法和程式碼塊是什麼效果呢?
private static synchronized void lockStaticMethod() {
System.out.println("靜態方法!");
}
private void lockCodeBlock(int count) {
synchronized (this) {
System.out.println("成員方法的程式碼塊!");
}
}
使用synchronized
修飾靜態方法,鎖定的物件是SynchronizedDemo.class
。所有SynchronizedDemo
的範例物件共用同一個SynchronizedDemo.class
,同一時間不同變數,只有一個執行緒可以執行lockStaticMethod
方法。
至於synchronized
修飾程式碼塊,就比較靈活了,括號中是誰就鎖定誰。如果是this
就鎖定範例變數,如果是SynchronizedDemo.class
效果就和修飾靜態方法一樣。
至於前面錯誤的同步的例子,它的問題是count
物件在不斷變化(Integer
實現相關)的,因此synchronized
鎖定的並不是同一個物件。
今天的內容非常基礎,難度也不大。
重點可以放在synchronized
鎖什麼的部分,以及是如何推匯出synchronized
不同用法產生的不同效果的。這樣的方式更接近於問題的本質,也能更好的舉一反三,而不是死記硬背「執行緒八鎖」這種東西。
好了,今天就到這裡了,Bye~~