JDK官網對synchronized關鍵字有個比較權威的解釋。java
Synchronized keyword enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object's variables ard done through synchronized methods.git
上述解釋的意思是:synchronized關鍵字能夠實現一個簡單的策略來防止線程干擾和內存一致性錯誤,若是一個對象對多個線程是可見的,那麼對該對象的全部讀或者寫都將經過同步的方式來進行,具體表現以下:github
synchronized能夠用於對代碼塊或方法進行修飾,而不可以用於對class以及變量進行修飾。緩存
public synchronized void sync() {
//...
}
複製代碼
private final Object lock = new Object();
public void sync() {
synchronized(lock) {
//...
}
}
複製代碼
關於同步代碼塊和同步方法的區別以前寫過一個關於這個對比,具體能夠看這篇文章。 java中的synchronized(同步代碼塊和同步方法的區別)bash
synchronized關鍵字提供了一種互斥機制,也就是說在同一時刻,只能有一個線程訪問同步資源。app
看下面這段程序:ide
import java.util.concurrent.TimeUnit;
public class TestSync {
private final static Object lock = new Object();
public void accessResource() {
synchronized(lock) {
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final TestSync sync = new TestSync();
for(int i =0;i<5;i++) {
new Thread(){
@Override
public void run() {
sync.accessResource();
}
}.start();
}
}
}
複製代碼
上面的代碼定義一個方法accessResource,而且使用synchronized來對代碼進行同步,同時定義了5個線程調用accessResource方法,因爲synchronized的互斥性,只能有一個線程得到lock的monitor鎖,其餘線程只能進入阻塞狀態,等待獲取lock的monitor鎖。性能
針對這個monitor鎖咱們如何從線程堆棧信息來看呢?優化
其實,jstack命令在Java中能夠用來打印進程的線程堆棧信息。ui
咱們來運行這個Java程序,在終端經過top命令查看運行起來的Java程序的進程id,而後執行jstack ‘pid’。
咱們來看下打印出來的信息:
經過截圖能夠看到Thread-0持有monitor<0x00000007955f2130>的鎖而且處於休眠狀態中,而其餘幾個線程則是處於BLOCKED狀態中,它們是在等待着獲取monitor<0x00000007955f2130>的鎖。
從JVM指令角度再來分析synchronized關鍵字。
咱們可使用javap這個命令來對上面這個TestSync類生成的class字節碼進行反編譯,獲得下面的JVM指令。
Compiled from "TestSync.java"
public class main.TestSync {
static {};
Code:
0: new #3 // class java/lang/Object
3: dup
4: invokespecial #10 // Method java/lang/Object."<init>":()V
7: putstatic #13 // Field lock:Ljava/lang/Object;
10: return
public main.TestSync();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void accessResource();
Code:
0: getstatic #13 // Field lock:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
6: getstatic #20 // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit;
9: ldc2_w #26 // long 10l
12: invokevirtual #28 // Method java/util/concurrent/TimeUnit.sleep:(J)V
15: goto 23
18: astore_2
19: aload_2
20: invokevirtual #32 // Method java/lang/InterruptedException.printStackTrace:()V
23: aload_1
24: monitorexit
25: goto 31
28: aload_1
29: monitorexit
30: athrow
31: return
Exception table:
from to target type
6 15 18 Class java/lang/InterruptedException
6 25 28 any
28 30 28 any
public static void main(java.lang.String[]);
Code:
0: new #1 // class main/TestSync
3: dup
4: invokespecial #44 // Method "<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: goto 27
13: new #45 // class main/TestSync$1
16: dup
17: aload_1
18: invokespecial #47 // Method main/TestSync$1."<init>":(Lmain/TestSync;)V
21: invokevirtual #50 // Method main/TestSync$1.start:()V
24: iinc 2, 1
27: iload_2
28: iconst_5
29: if_icmplt 13
32: return
}
複製代碼
從上面的指令中能夠看到,在accessResource()方法中,前後出現了一個monitor enter和兩個monitor exit。
咱們主要選取accessResource()這部分代碼塊來重點分析。
public void accessResource();
Code:
0: getstatic #13 //①獲取lock
3: dup
4: astore_1
5: monitorenter //②執行monitorenter JVM指令
6: getstatic #20 // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit;
9: ldc2_w #26 // long 10l
12: invokevirtual #28 // Method java/util/concurrent/TimeUnit.sleep:(J)V
15: goto 23 //③跳轉到23行
18: astore_2
19: aload_2
20: invokevirtual #32 // Method java/lang/InterruptedException.printStackTrace:()V
23: aload_1 //④
24: monitorexit //⑤ 執行monitor exit JVM指令
25: goto 31
28: aload_1
29: monitorexit
30: athrow
31: return
複製代碼
首先①獲取到lock引用,而後執行②monitorenter JVM指令,休眠結束後goto至③monitorexit的位置 (astore_n表示存儲引用到本地變量表;aload_n表示從本地變量表加載應用;getstatic表示從class中獲取靜態屬性)
monitorenter
每個對象都與一個monitor相關聯,一個monitor的lock的鎖只能被一個線程在同一時間得到,在一個線程嘗試得到與對象關聯的monitor的全部權時會發生以下的幾件事情。
monitorexit
釋放對monitor的全部權,想要釋放某個對象關聯的monitor全部權的前提是,你曾經擁有了全部權。釋放monitor全部權的過程比較簡單,就是將monitor的計數器減一,若是計數器的結果爲0,則意味着該線程不在擁有對該monitor的全部權,通俗地講就是解鎖。
在虛擬機規範對monitorenter和monitorexit的行爲描述中,有兩點是須要特別注意的,首先,synchronized同步塊對於同一條線程是可重入的,不會出現本身鎖死本身的問題。其次,同步課在已進入的線程執行完之前,會阻塞後面其餘線程的進入。
Java的線程是映射到操做系統線程上的,吐過要阻塞或喚醒一個線程,都須要操做系統來幫忙完成,這就須要從用戶態切到核心態,所以狀態轉換須要耗費不少的處理器時間,對於簡單的同步塊(如被synchronized修飾的getter或setter方法),狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長。因此synchronized是Java語言中的一個重量級的操做。
其實大多數時候,共享數據的鎖定狀態通常只會持續很短的一段時間,爲了這段時間去掛起和恢復線程其實並不值得。
若是物理機上有多個處理器,可讓多個線程同時執行的話。咱們就可讓後面來的線程「稍微等一下」,可是並不放棄處理器的執行時間,看看持有鎖的線程會不會很快釋放鎖。這個「稍微等一下」的過程就是自旋。
自旋鎖在JDK 1.4中已經引入,在JDK 1.6中默認開啓。只是將當前線程不停地執行循環體,不進行線程狀態的改變,因此響應速度更快,由於上面剛說到,線程的狀態切換會耗費不少CPU時間。但當線程數不停增長時,性能降低明顯,由於每一個線程都須要執行,佔用CPU時間。若是線程競爭不激烈,而且保持鎖的時間段,適合使用自旋鎖。
最後,歡迎你們關注個人KnowledgeSummary,主要是關於Java以及Android相關知識的總結以及一些進階的文章記錄。