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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

synchronized不能锁静态变量_肝了一下午的 Synchronized 解析!

發(fā)布時間:2025/3/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 synchronized不能锁静态变量_肝了一下午的 Synchronized 解析! 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Synchronized

歡迎來到狗哥多線程系列連載。本篇是線程相關(guān)的第九篇,前八篇分別是:

創(chuàng)建線程到底有幾種方式?線程有多少種狀態(tài)?Runnable 一定在執(zhí)行任務(wù)嗎?萬字長文,Thread 類源碼解析!wait、notify/notifyAll 解析線程之生產(chǎn)者消費者模式狗哥肝了一下午的線程池線程池的拒絕策略線程池的阻塞隊列
  • synchronized 是 Java 的一個關(guān)鍵字,它能夠?qū)⒋a塊 (方法) 鎖起來
  • synchronized 是 互斥鎖,同一時間只能有一個線程進(jìn)入被鎖住的代碼塊(方法)
  • synchronized 通過監(jiān)視器(Monitor)實現(xiàn)鎖。java 一切皆對象,每個對象都有一個監(jiān)視器(鎖標(biāo)記),而 synchronized 就是使用對象的監(jiān)視器來將代碼塊 (方法) 鎖定的

為什么用 Synchronized ?

我們加鎖的原因是為了線程安全,而線程安全最重要就是保證原子性和可見性

  • 被 Synchronized 修飾的代碼塊(方法),同一時間只能有一個線程執(zhí)行,從而保證原子性。
  • synchronized 通過使用監(jiān)視器,來實現(xiàn)對變量的同步操作,保證了其他線程對變量的可見性。

怎么用 Synchronized ?

  • 修飾普通同步方法:鎖是當(dāng)前實例對象
  • 修飾靜態(tài)同步方法:鎖是當(dāng)前類的 Class 對象
  • 修飾同步代碼塊:

修飾普通同步方法

public?class?BigBigDog?{

????//?修飾普通同步方法,普通方法屬于實例對象
????//?鎖是當(dāng)前實例對象?BigBigDog?的監(jiān)視器
????public?synchronized?void?testCommon(){
????????//?doSomething
????}

}

多個實例對象調(diào)用不會阻塞,比如:

public?class?BigBigDog?{

????//?修飾普通同步方法,普通方法屬于實例對象
????//?鎖是當(dāng)前實例對象?BigBigDog?的監(jiān)視器
????public?synchronized?void?testCommon()?{
????????int?i?=?0;
????????do?{
????????????try?{
????????????????Thread.sleep(1000);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????System.out.println("Common?function?is?locked?"?+?i);
????????}?while?(i++?10);
????}

}

測試方法:

public?class?Main?{

????public?static?void?main(String[]?args)?{
????????BigBigDog?bigBigDog?=?new?BigBigDog();
????????BigBigDog?bigBigDog1?=?new?BigBigDog();
????????new?Thread(bigBigDog::testCommon).start();
????????new?Thread(bigBigDog1::testCommon).start();
????}

}

結(jié)果:異步運(yùn)行,因為鎖的是實例對象,也就是鎖不同,所以并不會阻塞

Common function is locked 0
Common function is locked 0
Common function is locked 1
Common function is locked 1
Common function is locked 2
Common function is locked 2
Common function is locked 3
Common function is locked 3
···

修飾靜態(tài)同步方法

public?class?BigBigDog?{

????//?修飾靜態(tài)同步方法,靜態(tài)方法屬于類(粒度比普通方法大)
????//?鎖是類的鎖(類的字節(jié)碼文件對象:BigBigDog.class)
????public?static?synchronized?void?testStatic()?{
????????//?doSomething
????}

}

synchronized 修飾靜態(tài)方法獲取的是類鎖 (類的字節(jié)碼文件對象),synchronized 修飾普通方法獲取的是對象鎖。也就是說:獲取了類鎖的線程和獲取了對象鎖的線程是不沖突的!測試下:

public?class?BigBigDog?{

????//?修飾普通同步方法,普通方法屬于實例對象
????//?鎖是當(dāng)前實例對象?BigBigDog?的監(jiān)視器
????public?synchronized?void?testCommon()?{
????????int?i?=?0;
????????do?{
????????????try?{
????????????????Thread.sleep(1000);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????System.out.println("Common?function?is?locked?"?+?i);
????????}?while?(i++?10);
????}

????//?修飾靜態(tài)同步方法,靜態(tài)方法屬于類(粒度比普通方法大)
????//?鎖是類的鎖(類的字節(jié)碼文件對象:BigBigDog.class)
????public?static?synchronized?void?testStatic()?{
????????int?i?=?0;
????????do?{
????????????try?{
????????????????Thread.sleep(1000);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????System.out.println("Static?function?is?locked?"?+?i);
????????}?while?(i++?10);
????}
}
public?class?Main?{

????public?static?void?main(String[]?args)?{
????????BigBigDog?bigBigDog?=?new?BigBigDog();
????????new?Thread(bigBigDog::testCommon).start();
????????new?Thread(BigBigDog::testStatic).start();
????}

}

結(jié)果:異步運(yùn)行,并不沖突。

Common?function?is?locked?0
Static?function?is?locked?0
Common?function?is?locked?1
Static?function?is?locked?1
Common?function?is?locked?2
Static?function?is?locked?2
Common?function?is?locked?3
Static?function?is?locked?3

修飾同步代碼塊

public?class?BigBigDog?{

????public?void?test3()?{
????????//?修飾代碼塊,鎖是括號內(nèi)的對象
????????//?這里的?this?是當(dāng)前實例對象?BigBigDog?的監(jiān)視器
????????synchronized?(this)?{
????????????//?doSomething
????????}
????}
}
public?class?BigBigDog?{

????//?使用?object?的監(jiān)視器作為鎖
????private?final?Object?object?=?new?Object();

????public?void?test4()?{
????????//?修飾代碼塊,鎖是括號內(nèi)的對象
????????//?這里是當(dāng)前實例對象?object?的監(jiān)視器
????????synchronized?(object)?{
????????????//?doSomething
????????}
????}

}

除了第一種以 this 當(dāng)前對象的監(jiān)視器為鎖的情況。對于同步代碼塊,Java 還支持它持有任意對象的鎖,比如第二種的 object 。那么這兩者有何區(qū)別?這兩者并無本質(zhì)區(qū)別,但是為了代碼的可讀性。還是更加建議用第一種(第二種,無緣無故定義一個對象)

Synchronized 的原理

有以下代碼:test 是靜態(tài)同步方法,test1 是普通同步方法,test2 則是同步代碼塊。

public?class?SynchronizedTest?{

????//?修飾靜態(tài)方法
????public?static?synchronized?void?test()?{
????????//?doSomething
????}

????//?修飾方法
????public?synchronized?void?test1(){
????}

????public?void?test2(){
????????//?修飾代碼塊
????????synchronized?(this){
????????}
????}
}

通過命令看下 synchronized 關(guān)鍵字到底做了什么事情:首先用 cd 命令切換到 SynchronizedTest.java 類所在的路徑,然后執(zhí)行 javac SynchronizedTest.java,于是就會產(chǎn)生一個名為 SynchronizedTest.class 的字節(jié)碼文件,然后我們執(zhí)行 javap -c SynchronizedTest.class,就可以看到對應(yīng)的反匯編內(nèi)容,如下:

Z:\IDEAProject\review\review_java\src\main\java\com\nasus\thread\lock>javac -encoding UTF-8 SynchronizedTest.java

Z:\IDEAProject\review\review_java\src\main\java\com\nasus\thread\lock>javap -c SynchronizedTest.class
Compiled from "SynchronizedTest.java"
public class com.nasus.thread.lock.SynchronizedTest {
public com.nasus.thread.lock.SynchronizedTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static synchronized void test();
Code:
0: return
public synchronized void test1();
Code:
0: return
public void test2();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter // 監(jiān)視器進(jìn)入,獲取鎖
4: aload_1
5: monitorexit // 監(jiān)視器退出,釋放鎖
6: goto 14
9: astore_2
10: aload_1
11: monitorexit // 監(jiān)視器退出,釋放鎖
12: aload_2
13: athrow
14: return
Exception table:
from to target type
4 6 9 any
9 12 9 any
}

test2 同步代碼塊解析

主要看 test2 同步代碼塊的反編譯內(nèi)容,可以看出 synchronized 多了 monitorenter 和 monitorexit 指令。把執(zhí)行 monitorenter 理解為加鎖,執(zhí)行 monitorexit 理解為釋放鎖,每個對象維護(hù)著一個記錄著被鎖次數(shù)的計數(shù)器。未被鎖定的對象的該計數(shù)器為 0

那這里為啥只有一次 monitorenter 卻有兩次 monitorexit ?

  • JVM 要保證每個 monitorenter 必須有與之對應(yīng)的 monitorexit,monitorenter 指令被插入到同步代碼塊的開始位置,而 monitorexit 需要插入到方法正常結(jié)束處和異常處兩個地方,這樣就可以保證拋異常的情況下也能釋放鎖

執(zhí)行 monitorenter 的線程嘗試獲得 monitor 的所有權(quán),會發(fā)生以下這三種情況之一:

a. 如果該 monitor 的計數(shù)為 0,則線程獲得該 monitor 并將其計數(shù)設(shè)置為 1。然后,該線程就是這個 monitor 的所有者。b. 如果線程已經(jīng)擁有了這個 monitor ,則它將重新進(jìn)入,并且累加計數(shù)。c. 如果其他線程已經(jīng)擁有了這個 monitor,那個這個線程就會被阻塞,直到這個 monitor 的計數(shù)變成為 0,代表這個 monitor 已經(jīng)被釋放了,于是當(dāng)前這個線程就會再次嘗試獲取這個 monitor。

monitorexit

monitorexit 的作用是將 monitor 的計數(shù)器減 1,直到減為 0 為止。代表這個 monitor 已經(jīng)被釋放了,已經(jīng)沒有任何線程擁有它了,也就代表著解鎖,所以,其他正在等待這個 monitor 的線程,此時便可以再次嘗試獲取這個 monitor 的所有權(quán)。

test1 普通同步方法

它并不是依靠 monitorenter 和 monitorexit 指令實現(xiàn)的,從上面的反編譯內(nèi)容可以看到,synchronized 方法和普通方法大部分是一樣的,不同在于,這個方法會有一個叫作 ACC_SYNCHRONIZED 的 flag 修飾符,來表明它是同步方法。(在這看不出來需要看 JVM 底層實現(xiàn))

當(dāng)某個線程要訪問某個方法的時候,會首先檢查方法是否有 ACC_SYNCHRONIZED 標(biāo)志,如果有則需要先獲得 monitor 鎖,然后才能開始執(zhí)行方法,方法執(zhí)行之后再釋放 monitor 鎖

PS:想要進(jìn)一步深入了解 synchronized 就必須了解 monitor 對象,對象有自己的對象頭,存儲了很多信息,其中一個信息標(biāo)示是被哪個線程持有。可以參考這篇博客:@chenssy 大神寫的很好,建議拜讀下。

https://blog.csdn.net/chenssy/article/details/54883355

-END-

如果看到這里,喜歡這篇文章的話,請幫點個好看。微信搜索「一個優(yōu)秀的廢人」,關(guān)注后回復(fù)「?1024」送你一套完整的 java 教程(包括視頻)。回復(fù)「?電子書」送你全編程領(lǐng)域電子書?(不只Java)。

教程節(jié)選

《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的synchronized不能锁静态变量_肝了一下午的 Synchronized 解析!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。