相關參考博客地址 &參考書籍
https://www.cnblogs.com/LemonFive/p/11246086.html?from=timeline&isappinstalled=0
https://juejin.im/post/5b4eec7df265da0fa00a118f#heading-0
https://juejin.im/post/5cfddf48e51d455d877e0d04
https://www.cnblogs.com/YDDMAX/p/5658607.html
大端模式,小端模式掃盲貼:
https://blog.csdn.net/aitangyong/article/details/23204817
《深入理解java虛擬機》第十三章線程安全與鎖優化
參考圖例:
一 監視器(monitor)
任何一個對象都有一個Monitor與之關聯,當且一個Monitor被持有後,它將處於鎖定狀態。Synchronized在JVM裏的實現都是 基於進入和退出Monitor對象來實現方法同步和代碼塊同步,雖然具體實現細節不一樣,但是都可以通過成對的MonitorEnter和MonitorExit指令來實現。
二 synchronized關鍵字優化與java對象頭
2.2 從打印java對象頭開始說起,聊synchronized的優化
2.2.1 java對象頭的組成
java對象頭由mark word(標記字段)和 klass pointer(類型指針)兩部分組成,mark word存儲了對象的對象類型指針,指向類元數據信息。而mark word則存儲同步狀態、標識、hashcode、GC狀態的信息。
**PS:**在64位的jvm中,mark word(佔64位),klass point(64位),對象頭總計128位。如果應用的對象過多,klass point會浪費大量的內存,可以使用+UseCompressedOops 將指針壓縮至32位。
2.2.2 mark word的具體組成(32位)
從上圖中我們可以看出,判斷鎖的類型,主要依據後三位。
2.2.3 使用jol工具類打印java對象頭
maven中加入依賴:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
創建並打印工具A對象的java對象頭,同時查看本機jvm中的字節序。
大端模式 (Big-Endian):就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
小端模式(Little-Endian):就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
import org.openjdk.jol.info.ClassLayout;
/**
* Created by leixingbang on 2019/9/23.
*/
public class A {
boolean flag = false;
public static void main(String[] args) throws InterruptedException {
A a = new A();
//查看jvm是大端模式還是小端模式,打印結果:LITTLE_ENDIAN
System.out.println(ByteOrder.nativeOrder());
Thread.sleep(3000L);
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
驗證本機爲32位jvm:
C:\Users\Administrator>java -d64
Error: This Java instance does not support a 64-bit JVM.
Please install the desired version.
C:\Users\Administrator>java -d32
用法: java [-options] class [args...]
(執行類)
或 java [-options] -jar jarfile [args...]
(執行 jar 文件)
輸出結果:
小端模式的高低位與上圖mark word的組成結構圖相反:
第一行(4個字節):表明對象正處於無鎖狀態。且分代年齡爲1,對象的hashcode爲0
第二行爲(4個字節):32位的klass point
第三行爲(1個字節):1個字節的boolean對象
第四行(7個字節):補齊對象用。
2.2.4 啓用偏向鎖
JVM啓動時會進行一系列的複雜活動,比如裝載配置,系統類初始化等等。在這個過程中會使用大量synchronized關鍵字對對象加鎖,且這些鎖大多數都不是偏向鎖。爲了減少初始化時間,JVM默認延時加載偏向鎖。這個延時的時間大概爲4s左右,具體時間因機器而異。
當然我們也可以設置JVM參數 -XX:BiasedLockingStartupDelay=0 來取消延時加載偏向鎖。
當我們在idea中加入上述參數後,可以發現,輸出結果發生變化。
具體爲:
LITTLE_ENDIAN
com.crossoverjie.concurrent.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 0d 00 00 00 (00001101 00000000 00000000 00000000) (13)
4 4 (object header) 28 19 04 15 (00101000 00011001 00000100 00010101) (352590120)
8 1 boolean A.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
加入jvm參數:-XX:BiasedLockingStartupDelay=0後,在沒有加入synchronized關鍵字的場景下,我們可以看到後三位爲:101,倒數第三位爲1,標誌着啓動了偏向鎖。但是我們也同時看到,剩餘25位(偏向的線程標識)全部爲0,說明沒有偏向任何線程。
可以看做爲,此時偏向鎖正處於可偏向狀態,是一種特殊的無鎖狀態。
我們將程序稍加改動:
public class A {
boolean flag = false;
/**
* jvm參數:-XX:BiasedLockingStartupDelay=0
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
A a = new A();
//查看jvm是大端模式還是小端模式,打印結果:LITTLE_ENDIAN
System.out.println(ByteOrder.nativeOrder());
synchronized (a) {
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
此時輸出結果爲:
LITTLE_ENDIAN
com.crossoverjie.concurrent.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 0d f8 22 00 (00001101 11111000 00100010 00000000) (2291725)
4 4 (object header) 28 19 04 15 (00101000 00011001 00000100 00010101) (352590120)
8 1 boolean A.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
**解析:**加入synchronized關鍵字,且加入不延遲啓動偏向鎖的jvm參數後,我們可以發現在mark word中包含了具體的偏向線程Id.
當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,以後該線程進入和退出同步塊時不需要花費CAS操作來爭奪鎖資源,只需要檢查是否爲偏向鎖、鎖標識爲以及ThreadID即可,處理流程如下:
1.檢測Mark Word是否爲可偏向狀態,即是否爲偏向鎖1,鎖標識位爲01;
2.若爲可偏向狀態,則測試線程ID是否爲當前線程ID,如果是,則執行步驟(5),否則執行步驟(3);
3.如果測試線程ID不爲當前線程ID,則通過CAS操作競爭鎖,競爭成功,則將Mark Word的線程ID替換爲當前線程ID,否則執行線程(4);
4.通過CAS競爭鎖失敗,證明當前存在多線程競爭情況,當到達全局安全點,獲得偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼塊;
5.執行同步代碼塊
偏向鎖的釋放採用了 一[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nifKbO2S-1570523174649)(C:\Users\Administrator\Desktop\9.23日技術分享\偏向鎖的撤銷流程.jpg)]種只有競爭纔會釋放鎖的機制,線程是不會主動去釋放偏向鎖,需要等待其他線程來競爭,一旦出現競爭則由偏向鎖就升級爲輕量鎖
1.2.5 在不啓用偏向鎖的前提下,默認一開始使用的就是輕量鎖
/**
* 在不啓用偏向鎖的情況下,默認一開始就是輕量鎖
*/
@Test
public void testLightWeightLock(){
A a = new A();
synchronized (a) {
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
返回結果:
com.crossoverjie.concurrent.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) c4 ee 30 02 (11000100 11101110 00110000 00000010) (36761284)
4 4 (object header) 40 ca 05 15 (01000000 11001010 00000101 00010101) (352700992)
8 1 boolean A.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
最後兩位爲00(剩餘30位爲指向棧中鎖記錄的指針),可以看出,在不啓用偏向鎖的前提下,當使用sychronized關鍵字對對象進行加鎖時默認就使用了輕量鎖。
2.2.5 輕量鎖到重量鎖膨脹
public void testHeightWeightLock() throws InterruptedException {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(){
@Override
public void run() {
synchronized (lockTest){
long start = System.currentTimeMillis();
System.out.println("thread1 locking1");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
//打印對象頭耗時約2.5s
System.out.println(String.format("cost=%s",System.currentTimeMillis()-start));
try {
//thread1退出同步代碼塊,且沒有死亡
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 locking2");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
System.out.println(String.format("thread1 end,cost=%s...........",System.currentTimeMillis()-start));
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (lockTest){
System.out.println("thread2 locking");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
//再次進入
synchronized (lockTest){
System.out.println("thread2 locking2");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
}
};
thread1.start();
//讓thread1執行完同步代碼塊中方法,線程2啓動時,線程1還沒有執行完
Thread.sleep(3000);
thread2.start();
thread1.join();
thread2.join();
}
打印結果:
thread1 locking1
com.crossoverjie.concurrent.LockTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 4c fb 13 16 (01001100 11111011 00010011 00010110) (370408268)
4 4 (object header) 48 ca 05 15 (01001000 11001010 00000101 00010101) (352701000)
8 1 boolean LockTest.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
cost=1989
thread1 locking2
com.crossoverjie.concurrent.LockTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ae dd fd 01 (10101110 11011101 11111101 00000001) (33414574)
4 4 (object header) 48 ca 05 15 (01001000 11001010 00000101 00010101) (352701000)
8 1 boolean LockTest.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
thread1 end,cost=3993...........
thread2 locking
com.crossoverjie.concurrent.LockTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ae dd fd 01 (10101110 11011101 11111101 00000001) (33414574)
4 4 (object header) 48 ca 05 15 (01001000 11001010 00000101 00010101) (352701000)
8 1 boolean LockTest.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
thread2 locking2
com.crossoverjie.concurrent.LockTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ae dd fd 01 (10101110 11011101 11111101 00000001) (33414574)
4 4 (object header) 48 ca 05 15 (01001000 11001010 00000101 00010101) (352701000)
8 1 boolean LockTest.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
從輕量鎖到重量鎖膨脹分爲兩種情況:
(1)兩個同時獲取a實例的markword(很難模擬兩個線程在無鎖狀態時同時獲取到a實例的markword),然後同時cas時,一個成功,其他失敗然後自旋超過次數,會升級 (2)後來的獲取者到達時,發現a實例已經是輕量鎖狀態,則直接升級
從打印的對象頭的信息,我們可以看出,線程1最開始是輕量量鎖(10)。線程2在線程1啓動後,CAS嘗試獲取鎖,發現失敗(鎖已經被線程1獲取到),且鎖當前已經是輕量鎖。於是將鎖膨脹爲重量鎖【爲上述說明的第二種情況】(此時線程1仍持有鎖)。當線程1釋放鎖後線程2得到的鎖是重量鎖,且線程2再次獲取時,鎖不會降級(仍然爲競爭時膨脹後的重量鎖)。
我們再在threa2.join後再加一個打印對象頭,發現對象頭中的鎖又降爲了輕量鎖,思考下爲什麼?
synchronized (lockTest){
System.out.println("main thread locking");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
打印結果:
main thread locking
com.crossoverjie.concurrent.LockTest object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f6 4a 17 02 (11110110 01001010 00010111 00000010) (35080950)
4 4 (object header) c0 ca 4d 14 (11000000 11001010 01001101 00010100) (340642496)
8 1 boolean LockTest.flag false
9 7 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
完整的爲:
@Test
public void testHeightWeightLock() throws InterruptedException {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(){
@Override
public void run() {
synchronized (lockTest){
long start = System.currentTimeMillis();
System.out.println("thread1 locking1");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
//打印對象頭耗時約2.5s
System.out.println(String.format("cost=%s",System.currentTimeMillis()-start));
try {
//thread1退出同步代碼塊,且沒有死亡
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 locking2");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
System.out.println(String.format("thread1 end,cost=%s...........",System.currentTimeMillis()-start));
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (lockTest){
System.out.println("thread2 locking");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
//再次進入
synchronized (lockTest){
System.out.println("thread2 locking2");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
}
};
thread1.start();
//讓thread1執行完同步代碼塊中方法,線程2啓動時,線程1還沒有執行完
Thread.sleep(3000);
thread2.start();
thread1.join();
thread2.join();
synchronized (lockTest){
System.out.println("main thread locking");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
}
輕量鎖到重量鎖膨脹的流程:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-syxHkLsV-1570523174651)(C:\Users\Administrator\Desktop\9.23日技術分享\輕量鎖到重量鎖膨脹的流程.jpg)]
2.3 思考:synchronized是屬於可重入鎖,在不同的鎖級別下是如何實現鎖的可重入性的?
2.3.1重量鎖
重量鎖:思想類似於GC算法中的引用計數法,每個對象都是監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權。monitorenter時候,計數器加1,monitorexit時候,計數器減1.當計數器爲0時候,意味着釋放了鎖。
eg:
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
編譯後,javap -v SynchronizedDemo.class 後的對應字節碼:
public void method();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Method 1 start
9: invokevirtual #4 // Method java/io/PrintStream.println:
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
monitorenter:每個對象都是一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
- 如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者;
- 如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1;
- 如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權;
monitorexit:執行monitorexit的線程必須是objectref所對應的monitor的所有者。指令執行時,monitor的進入數減1,如果減1後進入數爲0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。
2.3.2 輕量鎖
使用加鎖對象的對象頭中的lock word(指向棧中鎖記錄的指針)與本線程的棧的地址進行比較,如果相同則說明當前線程已經獲取鎖。
2.3.3 偏向鎖
使用對象頭中的ThreadId與當前線程的ThreadId進行對比,如果相同,則說明當前線程已經獲取鎖。
2.4 討論,Tread2在下面例子中是如何由偏向鎖逐步膨脹爲重量鎖的?
/**
* -XX:BiasedLockingStartupDelay=0
從fa的最後兩位10可知爲重量鎖, 前面爲重量鎖的指針
升級爲重量鎖兩種情況
(1)兩個同時獲取a實例的markword(很難模擬兩個線程在無鎖狀態時同時獲取到a實例的markword),然後同時cas時,一個成功,其他失敗然後自旋超過次數,會升級
(2)後來的獲取者到達時,發現a實例已經是輕量鎖狀態,則直接升級
這裏打印重量鎖的原因應該是後者,
* @throws InterruptedException
*/
@Test
public void testHeightWeightLock() throws InterruptedException {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(){
@Override
public void run() {
synchronized (lockTest){
long start = System.currentTimeMillis();
System.out.println("thread1 locking");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
//打印對象頭耗時約2.5s
System.out.println(String.format("cost=%s",System.currentTimeMillis()-start));
try {
//thread1退出同步代碼塊,且沒有死亡
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("thread1 end,cost=%s...........",System.currentTimeMillis()-start));
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (lockTest){
System.out.println("thread2 locking");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
//再次進入
synchronized (lockTest){
System.out.println("thread2 locking2");
System.out.println(ClassLayout.parseInstance(lockTest).toPrintable());
}
}
};
thread1.start();
//讓thread1執行完同步代碼塊中方法,線程2啓動時,線程1還沒有執行完
Thread.sleep(3000);
thread2.start();
thread1.join();
thread2.join();
}
三、其他對鎖的優化
3.1鎖消除(編譯時期完成)
定義: 編譯器在運行時,對對於一些在代碼上要求同步,但是檢測到不可能存在數據競爭的鎖進行消除。eg:
public class Erase {
public void testErase(){
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
}
}
StringBuffer.append方法前加入了synchronized關鍵字,但是編譯後的字節碼沒有mointorenter以及monitorexit:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
字節碼:
public void testErase();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/StringBuffer
3: dup
4: invokespecial #3 // Method java/lang/StringBuffer."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String a
11: invokevirtual #5 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
14: pop
15: aload_1
16: ldc #6 // String b
18: invokevirtual #5 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
21: pop
22: aload_1
23: ldc #7 // String c
25: invokevirtual #5 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
28: pop
29: return
3.2鎖粗化
在編寫程序代碼時候,總是推薦將同步塊作用範圍變得儘可能小,只有在共享數據的實際作用域才進行同步。但是如果一個連續的操作頻繁的對同一個對象進行加鎖和解鎖,即便沒有現成競爭也會產生不必要的損耗。jvm會將加鎖同步範圍拓展,稱爲鎖粗化
eg:
for(int i=0;i<size;i++){
synchronized(lock){
}
粗化後示例:
synchronized(lock){
for(int i=0;i<size;i++){
}
}
3.3自旋鎖與自適應鎖
線程的阻塞和喚醒需要CPU從用戶態轉爲核心態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作,勢必會給系統的併發性能帶來很大的壓力。同時我們發現在許多應用上面,對象鎖的鎖狀態只會持續很短一段時間,爲了這一段很短的時間頻繁地阻塞和喚醒線程是非常不值得的。
所以引入自旋鎖,何謂自旋鎖?
所謂自旋鎖,就是指當一個線程嘗試獲取某個鎖時,如果該鎖已被其他線程佔用,就一直循環檢測鎖是否被釋放,而不是進入線程掛起或睡眠狀態。
自旋鎖適用於鎖保護的臨界區很小的情況,臨界區很小的話,鎖佔用的時間就很短。自旋等待不能替代阻塞,雖然它可以避免線程切換帶來的開銷,但是它佔用了CPU處理器的時間。如果持有鎖的線程很快就釋放了鎖,那麼自旋的效率就非常好,反之,自旋的線程就會白白消耗掉處理的資源,它不會做任何有意義的工作,典型的佔着茅坑不拉屎,這樣反而會帶來性能上的浪費。所以說,自旋等待的時間(自旋的次數)必須要有一個限度,如果自旋超過了定義的時間仍然沒有獲取到鎖,則應該被掛起。
自旋鎖在JDK 1.4.2中引入,默認關閉,但是可以使用-XX:+UseSpinning開開啓,在JDK1.6中默認開啓。同時自旋的默認次數爲10次,可以通過參數-XX:PreBlockSpin來調整。
如果通過參數-XX:PreBlockSpin來調整自旋鎖的自旋次數,會帶來諸多不便。假如將參數調整爲10,但是系統很多線程都是等你剛剛退出的時候就釋放了鎖(假如多自旋一兩次就可以獲取鎖),是不是很尷尬。於是JDK1.6引入自適應的自旋鎖,讓虛擬機會變得越來越聰明。
JDK 1.6引入了更加聰明的自旋鎖,即自適應自旋鎖。所謂自適應就意味着自旋的次數不再是固定的,它是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。那它如何進行適應性自旋呢?
線程如果自旋成功了,那麼下次自旋的次數會更加多,因爲虛擬機認爲既然上次成功了,那麼此次自旋也很有可能會再次成功,那麼它就會允許自旋等待持續的次數更多。反之,如果對於某個鎖,很少有自旋能夠成功,那麼在以後要或者這個鎖的時候自旋的次數會減少甚至省略掉自旋過程,以免浪費處理器資源。
有了自適應自旋鎖,隨着程序運行和性能監控信息的不斷完善,虛擬機對程序鎖的狀況預測會越來越準確,虛擬機會變得越來越聰明。
上的浪費。所以說,自旋等待的時間(自旋的次數)必須要有一個限度,如果自旋超過了定義的時間仍然沒有獲取到鎖,則應該被掛起。
自旋鎖在JDK 1.4.2中引入,默認關閉,但是可以使用-XX:+UseSpinning開開啓,在JDK1.6中默認開啓。同時自旋的默認次數爲10次,可以通過參數-XX:PreBlockSpin來調整。
如果通過參數-XX:PreBlockSpin來調整自旋鎖的自旋次數,會帶來諸多不便。假如將參數調整爲10,但是系統很多線程都是等你剛剛退出的時候就釋放了鎖(假如多自旋一兩次就可以獲取鎖),是不是很尷尬。於是JDK1.6引入自適應的自旋鎖,讓虛擬機會變得越來越聰明。
JDK 1.6引入了更加聰明的自旋鎖,即自適應自旋鎖。所謂自適應就意味着自旋的次數不再是固定的,它是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。那它如何進行適應性自旋呢?
線程如果自旋成功了,那麼下次自旋的次數會更加多,因爲虛擬機認爲既然上次成功了,那麼此次自旋也很有可能會再次成功,那麼它就會允許自旋等待持續的次數更多。反之,如果對於某個鎖,很少有自旋能夠成功,那麼在以後要或者這個鎖的時候自旋的次數會減少甚至省略掉自旋過程,以免浪費處理器資源。
有了自適應自旋鎖,隨着程序運行和性能監控信息的不斷完善,虛擬機對程序鎖的狀況預測會越來越準確,虛擬機會變得越來越聰明。