日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

ipa去除时间锁_Java中的锁以及sychronized实现机制(十)

發布時間:2025/3/15 java 9 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ipa去除时间锁_Java中的锁以及sychronized实现机制(十) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上節講了線程安全和原子性,其實就是并發代碼變成同步,意味著代碼只有一個人在使用,這樣就不會有問題。

(一)Java中的鎖

1.自旋鎖

為了不放棄CPU執行時間,循環的使用CAS技術對數據嘗試進行更新,直至成功。(樂觀鎖的實現)

2.悲觀鎖

假定會發生并發沖突,同步所有對數據的相關操作,從讀數據就開始上鎖。(從讀數據就開始上鎖。)

3.樂觀鎖

假定沒有沖突,在修改數據時如果發生數據和之前獲取的不一致,則讀最新數據,修改后重試修改。(假定能成功,如果不成功也是CAS的操作)

4.獨享鎖(寫)

給資源加上寫鎖,線程可以修改資源,其他線程不能再加鎖(單寫)(互斥鎖)(就 像談戀愛,被女朋友鎖定了,其他人無法獲取你)

5.共享鎖(讀)

給資源加上讀鎖只能讀不能改,其他線程只能加讀鎖,不能加寫鎖(多讀)(就像信息已經放給婚介了,誰都可以獲取我的信息)

6.可重入鎖,不可重入鎖

線程拿到一把鎖之后,可以自由進入同一把鎖所同步的其他代碼

public class ObjectSyncDemo2 {

public synchronized void test1(Object arg) {
System.out.println(Thread.currentThread() + " 我開始執行 " + arg);
if (arg == null) {
test1(new Object());
}
System.out.println(Thread.currentThread() + " 我執行結束" + arg);
}

public static void main(String[] args) throws InterruptedException {
new ObjectSyncDemo2().test1(null);
}
}


7.公平鎖,非公平鎖

爭搶鎖的順序,如果按先來后到,則為公平。
公平鎖:線程1拿到鎖,線程2在等待,線程3也在等待,線程1執行完釋放鎖,線程2就拿到這個鎖。
非公平鎖:線程1拿到鎖,線程2在等待,線程3也在等待,線程1執行完釋放鎖,線程3就拿到這個鎖,本來應該線程2拿到鎖的,線程3先拿到了。就是不公平。

(二)同步關鍵字synchronized

  • ① 介紹

最基本的線程通信機制,基于對象監視器實現的。JAVA中的每個對象都與一個監視器相關聯,線程可以鎖定或解鎖。一次只有一個線程可以鎖定監視器。試圖鎖定該監視器的任何其他線程都會被阻塞,知道它們可以獲得該監視器上的鎖定為止。

  • ② 特性

可重入,獨享,悲觀鎖。

  • ③ 鎖的范圍

類鎖,對象鎖,鎖消除,鎖粗化

同步關鍵字,不僅是實現同步,根據JMM規定還能保證可見性(讀取最新主內存數據,結束后寫入主內存)

  • ④ 鎖消除

發生在編譯器級別的一種鎖優化方式。完全不需要加鎖,卻執行了加鎖操作。

// 鎖消除(jit)
public class ObjectSyncDemo {
public void test3(Object arg) {
StringBuilder builder = new StringBuilder();
builder.append("a");
builder.append(arg);
builder.append("c");
System.out.println(arg.toString());
}

public void test2(Object arg) {
String a = "a";
String c = "c";
System.out.println(a + arg + c);
}


public void test1(Object arg) {
// jit 優化, 消除了鎖
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append(arg);
stringBuffer.append("c");
// System.out.println(stringBuffer.toString());
}

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
new ObjectSyncDemo().test1("123");
}
}
}

test1()方法中的StringBuffer數以函數內部的局部變量,進作用于方法內部,不可能逃逸出該方法,因此他就不可能被多個線程同時訪問,也就沒有資源的競爭,但是StringBuffer的append操作卻需要執行同步操作

StringBuffer 的源碼

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

去除不可能存在共享資源競爭的鎖,通過鎖消除,可以節省毫無意義的請求鎖時間。

  • ④ 鎖粗化

通常情況下,為了保證多線程間的有效并發,會要求每個線程持有鎖的時間盡可能短,但是某些情況下,一個程序對同一個鎖不間斷、高頻地請求、同步與釋放,會消耗掉一定的系統資源,因為鎖的講求、同步與釋放本身會帶來性能損耗,這樣高頻的鎖請求就反而不利于系統性能的優化了,雖然單次同步操作的時間可能很短。鎖粗化就是告訴我們任何事情都有個度,有些情況下我們反而希望把很多次鎖的請求合并成一個請求,以降低短時間內大量鎖請求、同步、釋放帶來的性能損耗。

// 鎖粗化(運行時 jit 編譯優化)
// jit 編譯后的匯編內容, jitwatch可視化工具進行查看
public class ObjectSyncDemo {
int i;

public void test1(Object arg) {
synchronized (this) {
i++;
}
synchronized (this) {
i++;
}
}

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000000; i++) {
new ObjectSyncDemo().test1("a");
}
}
}

在test1() 同步操作的代碼之間,需要做一些其它的工作,而這些工作只會花費很少的時間,那么我們就可以把這些工作代碼放入鎖內,將兩個同步代碼塊合并成一個,以降低多次鎖請求、同步、釋放帶來的系統性能消耗,合并后的代碼如下

前提中間不需要同步的代碼能夠很快速地完成,如果不需要同步的代碼需要花很長時間,就會導致同步塊的執行需要花費很長的時間,這樣做也不合理。

public void test1(Object arg) {
synchronized (this) {
i++;
i++;
}
}

(三)同步關鍵字加鎖原理

  • ① JAVA對象頭

鎖的實現機制與java對象頭息息相關,鎖的所有信息,都記錄在java的對象頭中。用2字(32位JVM中1字=32bit=4baye)存儲對象頭,如果是數組類型使用3字存儲(還需存儲數組長度)。對象頭中記錄了hash值、GC年齡、鎖的狀態、線程擁有者、類元數據的指針。

  • ② 幾種鎖類型

線程的阻塞和喚醒需要CPU從用戶態轉為核心態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

  • ③ 偏向鎖(A線程獨占鎖,不用上下文切換。對象頭標識)

大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。偏向鎖的目的是在某個線程獲得鎖之后,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程得到了偏護。另外,JVM對那種會有多線程加鎖,但不存在鎖競爭的情況也做了優化,聽起來比較拗口,但在現實應用中確實是可能出現這種情況,因為線程之前除了互斥之外也可能發生同步關系,被同步的兩個線程(一前一后)對共享對象鎖的競爭很可能是沒有沖突的。對這種情況,JVM用一個epoch表示一個偏向鎖的時間戳(真實地生成一個時間戳代價還是蠻大的,因此這里應當理解為一種類似時間戳的identifier)

偏向鎖的獲取

當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要花費CAS操作來加鎖和解鎖,而只需簡單的測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖,如果測試成功,表示線程已經獲得了鎖,如果測試失敗,則需要再測試下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖),如果沒有設置,則使用CAS競爭鎖,如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態,則將對象頭設置成無鎖狀態,如果線程仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word,要么重新偏向于其他線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。

偏向鎖的設置

關閉偏向鎖:偏向鎖在Java 6和Java 7里是默認啟用的,但是它在應用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數來關閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應用程序里所有的鎖通常情況下處于競爭狀態,可以通過JVM參數關閉偏向鎖-XX:-UseBiasedLocking=false,那么默認會進入輕量級鎖狀態。

  • ④自旋鎖(一個線程嘗試獲取某個鎖時,如果該鎖已被其他線程占用,就一直循環檢測鎖是否被釋放,而不是進入線程掛起或睡眠狀態。一定要有次數限制)

線程的阻塞和喚醒需要CPU從用戶態轉為核心態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作。同時我們可以發現,很多對象鎖的鎖定狀態只會持續很短的一段時間,例如整數的自加操作,在很短的時間內阻塞并喚醒線程顯然不值得,為此引入了自旋鎖。

所謂“自旋”,就是讓線程去執行一個無意義的循環,循環結束后再去重新競爭鎖,如果競爭不到繼續循環,循環過程中線程會一直處于running狀態,但是基于JVM的線程調度,會出讓時間片,所以其他線程依舊有申請鎖和釋放鎖的機會。

自旋鎖省去了阻塞鎖的時間空間(隊列的維護等)開銷,但是長時間自旋就變成了“忙式等待”,忙式等待顯然還不如阻塞鎖。所以自旋的次數一般控制在一個范圍內,例如10,100等,在超出這個范圍后,自旋鎖會升級為阻塞鎖。

  • ④ 輕量級鎖(A線程擁有鎖,B獲取,競爭,自旋)
    加鎖

線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,則自旋獲取鎖,當自旋獲取鎖仍然失敗時,表示存在其他線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。

解鎖

輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到對象頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他線程嘗試過獲取該鎖,則要在釋放鎖的同時喚醒被掛起的線程。

  • ⑤ 重量級鎖(B線程自旋獲取不到鎖,膨脹重量鎖,阻塞A線程。直到B執行完。)

重量鎖在JVM中又叫對象監視器(Monitor),它很像C中的Mutex,除了具備Mutex(0|1)互斥的功能,它還負責實現了Semaphore(信號量)的功能,也就是說它至少包含一個競爭鎖的隊列,和一個信號阻塞隊列(wait隊列),前者負責做互斥,后一個用于做線程同步。

PS:有數據表明,除去大型互聯網公司,80%的系統不存在多線程的競爭的情況,一定要熟悉這幾種鎖,對以后面試鍍金(面試)真的很有用。

總結

以上是生活随笔為你收集整理的ipa去除时间锁_Java中的锁以及sychronized实现机制(十)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。