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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java并发编程 Synchronized及其实现原理

發(fā)布時(shí)間:2025/3/21 java 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程 Synchronized及其实现原理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Synchronized是Java中解決并發(fā)問(wèn)題的一種最常用的方法,也是最簡(jiǎn)單的一種方法。Synchronized的作用主要有三個(gè):(1)確保線程互斥的訪問(wèn)同步代碼(2)保證共享變量的修改能夠及時(shí)可見(jiàn)(3)有效解決重排序問(wèn)題。

Java中每一個(gè)對(duì)象都可以作為鎖,這是synchronized實(shí)現(xiàn)同步的基礎(chǔ):

1、普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

public?class?SynchronizedTest {

?4?????public?synchronized?void?method1(){

?5?????????System.out.println("Method 1 start");

?6?????????try?{

?7?????????????System.out.println("Method 1 execute");

?8?????????????Thread.sleep(3000);

?9?????????}?catch?(InterruptedException e) {

10?????????????e.printStackTrace();

11?????????}

12?????????System.out.println("Method 1 end");

13?????}

14

15?????public?synchronized?void?method2(){

16?????????System.out.println("Method 2 start");

17?????????try?{

18?????????????System.out.println("Method 2 execute");

19?????????????Thread.sleep(1000);

20?????????}?catch?(InterruptedException e) {

21?????????????e.printStackTrace();

22?????????}

23?????????System.out.println("Method 2 end");

24?????}

25

26?????public?static?void?main(String[] args) {

27?????????final?SynchronizedTest test =?new?SynchronizedTest();

28

29?????????new?Thread(new?Runnable() {

30?????????????@Override

31?????????????public?void?run() {

32?????????????????test.method1();

33?????????????}

34?????????}).start();

35

36?????????new?Thread(new?Runnable() {

37?????????????@Override

38?????????????public?void?run() {

39?????????????????test.method2();

40?????????????}

41?????????}).start();

42?????}

43?}

2、靜態(tài)同步方法,鎖是當(dāng)前類的class對(duì)象

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

public?class?SynchronizedTest {

?4??????public?static?synchronized?void?method1(){

?5??????????System.out.println("Method 1 start");

?6??????????try?{

?7??????????????System.out.println("Method 1 execute");

?8??????????????Thread.sleep(3000);

?9??????????}?catch?(InterruptedException e) {

10??????????????e.printStackTrace();

11??????????}

12??????????System.out.println("Method 1 end");

13??????}

14?

15??????public?static?synchronized?void?method2(){

16??????????System.out.println("Method 2 start");

17??????????try?{

18??????????????System.out.println("Method 2 execute");

19??????????????Thread.sleep(1000);

20??????????}?catch?(InterruptedException e) {

21??????????????e.printStackTrace();

22??????????}

23??????????System.out.println("Method 2 end");

24??????}

25?

26??????public?static?void?main(String[] args) {

27??????????final?SynchronizedTest test =?new?SynchronizedTest();

28??????????final?SynchronizedTest test2 =?new?SynchronizedTest();

29?

30??????????new?Thread(new?Runnable() {

31??????????????@Override

32??????????????public?void?run() {

33??????????????????test.method1();

34??????????????}

35??????????}).start();

36?

37??????????new?Thread(new?Runnable() {

38??????????????@Override

39??????????????public?void?run() {

40??????????????????test2.method2();

41??????????????}

42??????????}).start();

43??????}

44??}

3、同步方法塊,鎖是括號(hào)里面的對(duì)象

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

public?class?SynchronizedTest {

?4?????public?void?method1(){

?5?????????System.out.println("Method 1 start");

?6?????????try?{

?7?????????????synchronized?(this) {

?8?????????????????System.out.println("Method 1 execute");

?9?????????????????Thread.sleep(3000);

10?????????????}

11?????????}?catch?(InterruptedException e) {

12?????????????e.printStackTrace();

13?????????}

14?????????System.out.println("Method 1 end");

15?????}

16

17?????public?void?method2(){

18?????????System.out.println("Method 2 start");

19?????????try?{

20?????????????synchronized?(this) {

21?????????????????System.out.println("Method 2 execute");

22?????????????????Thread.sleep(1000);

23?????????????}

24?????????}?catch?(InterruptedException e) {

25?????????????e.printStackTrace();

26?????????}

27?????????System.out.println("Method 2 end");

28?????}

29

30?????public?static?void?main(String[] args) {

31?????????final?SynchronizedTest test =?new?SynchronizedTest();

32

33?????????new?Thread(new?Runnable() {

34?????????????@Override

35?????????????public?void?run() {

36?????????????????test.method1();

37?????????????}

38?????????}).start();

39

40?????????new?Thread(new?Runnable() {

41?????????????@Override

42?????????????public?void?run() {

43?????????????????test.method2();

44?????????????}

45?????????}).start();

46?????}

47?}

synchronize底層原理:

Java 虛擬機(jī)中的同步(Synchronization)基于進(jìn)入和退出Monitor對(duì)象實(shí)現(xiàn), 無(wú)論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此。在 Java 語(yǔ)言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法。同步方法 并不是由 monitorenter 和 monitorexit 指令來(lái)實(shí)現(xiàn)同步的,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中方法表結(jié)構(gòu)的 ACC_SYNCHRONIZED 標(biāo)志來(lái)隱式實(shí)現(xiàn)的,關(guān)于這點(diǎn),稍后詳細(xì)分析。

同步代碼塊:monitorenter指令插入到同步代碼塊的開(kāi)始位置,monitorexit指令插入到同步代碼塊的結(jié)束位置,JVM需要保證每一個(gè)monitorenter都有一個(gè)monitorexit與之相對(duì)應(yīng)。任何對(duì)象都有一個(gè)monitor與之相關(guān)聯(lián),當(dāng)且一個(gè)monitor被持有之后,他將處于鎖定狀態(tài)。線程執(zhí)行到monitorenter指令時(shí),將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor所有權(quán),即嘗試獲取對(duì)象的鎖;

在JVM中,對(duì)象在內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭、實(shí)例變量和填充數(shù)據(jù)。如下:

實(shí)例變量:存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息,如果是數(shù)組的實(shí)例部分還包括數(shù)組的長(zhǎng)度,這部分內(nèi)存按4字節(jié)對(duì)齊。

填充數(shù)據(jù):由于虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對(duì)齊,這點(diǎn)了解即可。

對(duì)象頭:Hotspot虛擬機(jī)的對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)、Klass Pointer(類型指針)。其中Klass Point是是對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例,Mark Word用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),它是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵。

Mark Word:用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時(shí)間戳等等。Java對(duì)象頭一般占有兩個(gè)機(jī)器碼(在32位虛擬機(jī)中,1個(gè)機(jī)器碼等于4字節(jié),也就是32bit),但是如果對(duì)象是數(shù)組類型,則需要三個(gè)機(jī)器碼,因?yàn)镴VM虛擬機(jī)可以通過(guò)Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小,但是無(wú)法從數(shù)組的元數(shù)據(jù)來(lái)確認(rèn)數(shù)組的大小,所以用一塊來(lái)記錄數(shù)組長(zhǎng)度。

Monior:我們可以把它理解為一個(gè)同步工具,也可以描述為一種同步機(jī)制,它通常被描述為一個(gè)對(duì)象。與一切皆對(duì)象一樣,所有的Java對(duì)象是天生的Monitor,每一個(gè)Java對(duì)象都有成為Monitor的潛質(zhì),因?yàn)樵贘ava的設(shè)計(jì)中 ,每一個(gè)Java對(duì)象自打娘胎里出來(lái)就帶了一把看不見(jiàn)的鎖,它叫做內(nèi)部鎖或者M(jìn)onitor鎖。Monitor 是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個(gè)線程都有一個(gè)可用monitor record列表,同時(shí)還有一個(gè)全局的可用列表。每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè)monitor關(guān)聯(lián)(對(duì)象頭的MarkWord中的LockWord指向monitor的起始地址),同時(shí)monitor中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識(shí),表示該鎖被這個(gè)線程占用。其結(jié)構(gòu)如下:

Owner:初始時(shí)為NULL表示當(dāng)前沒(méi)有任何線程擁有該monitor record,當(dāng)線程成功擁有該鎖后保存線程唯一標(biāo)識(shí),當(dāng)鎖被釋放時(shí)又設(shè)置為NULL;
EntryQ:關(guān)聯(lián)一個(gè)系統(tǒng)互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個(gè)數(shù)。
Nest:用來(lái)實(shí)現(xiàn)重入鎖的計(jì)數(shù)。
HashCode:保存從對(duì)象頭拷貝過(guò)來(lái)的HashCode值(可能還包含GC age)。
Candidate:用來(lái)避免不必要的阻塞或等待線程喚醒,因?yàn)槊恳淮沃挥幸粋€(gè)線程能夠成功擁有鎖,如果每次前一個(gè)釋放鎖的線程喚醒所有正在阻塞或等待的線程,會(huì)引起不必要的上下文切換(從阻塞到就緒然后因?yàn)楦?jìng)爭(zhēng)鎖失敗又被阻塞)從而導(dǎo)致性能嚴(yán)重下降。Candidate只有兩種可能的值0表示沒(méi)有需要喚醒的線程1表示要喚醒一個(gè)繼任線程來(lái)競(jìng)爭(zhēng)鎖。

?Java虛擬機(jī)對(duì)synchronize的優(yōu)化:

鎖的狀態(tài)總共有四種,無(wú)鎖狀態(tài)、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競(jìng)爭(zhēng),鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖,但是鎖的升級(jí)是單向的,也就是說(shuō)只能從低到高升級(jí),不會(huì)出現(xiàn)鎖的降級(jí),關(guān)于重量級(jí)鎖,前面我們已詳細(xì)分析過(guò),下面我們將介紹偏向鎖和輕量級(jí)鎖以及JVM的其他優(yōu)化手段。

偏向鎖

偏向鎖是Java 6之后加入的新鎖,它是一種針對(duì)加鎖操作的優(yōu)化手段,經(jīng)過(guò)研究發(fā)現(xiàn),在大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,因此為了減少同一線程獲取鎖(會(huì)涉及到一些CAS操作,耗時(shí))的代價(jià)而引入偏向鎖。偏向鎖的核心思想是,如果一個(gè)線程獲得了鎖,那么鎖就進(jìn)入偏向模式,此時(shí)Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu),當(dāng)這個(gè)線程再次請(qǐng)求鎖時(shí),無(wú)需再做任何同步操作,即獲取鎖的過(guò)程,這樣就省去了大量有關(guān)鎖申請(qǐng)的操作,從而也就提供程序的性能。所以,對(duì)于沒(méi)有鎖競(jìng)爭(zhēng)的場(chǎng)合,偏向鎖有很好的優(yōu)化效果,畢竟極有可能連續(xù)多次是同一個(gè)線程申請(qǐng)相同的鎖。但是對(duì)于鎖競(jìng)爭(zhēng)比較激烈的場(chǎng)合,偏向鎖就失效了,因?yàn)檫@樣場(chǎng)合極有可能每次申請(qǐng)鎖的線程都是不相同的,因此這種場(chǎng)合下不應(yīng)該使用偏向鎖,否則會(huì)得不償失,需要注意的是,偏向鎖失敗后,并不會(huì)立即膨脹為重量級(jí)鎖,而是先升級(jí)為輕量級(jí)鎖。

輕量級(jí)鎖

倘若偏向鎖失敗,虛擬機(jī)并不會(huì)立即升級(jí)為重量級(jí)鎖,它還會(huì)嘗試使用一種稱為輕量級(jí)鎖的優(yōu)化手段(1.6之后加入的),此時(shí)Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級(jí)鎖的結(jié)構(gòu)。輕量級(jí)鎖能夠提升程序性能的依據(jù)是“對(duì)絕大部分的鎖,在整個(gè)同步周期內(nèi)都不存在競(jìng)爭(zhēng)”,注意這是經(jīng)驗(yàn)數(shù)據(jù)。需要了解的是,輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線程交替執(zhí)行同步塊的場(chǎng)合,如果存在同一時(shí)間訪問(wèn)同一鎖的場(chǎng)合,就會(huì)導(dǎo)致輕量級(jí)鎖膨脹為重量級(jí)鎖。

自旋鎖

輕量級(jí)鎖失敗后,虛擬機(jī)為了避免線程真實(shí)地在操作系統(tǒng)層面掛起,還會(huì)進(jìn)行一項(xiàng)稱為自旋鎖的優(yōu)化手段。這是基于在大多數(shù)情況下,線程持有鎖的時(shí)間都不會(huì)太長(zhǎng),如果直接掛起操作系統(tǒng)層面的線程可能會(huì)得不償失,畢竟操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高,因此自旋鎖會(huì)假設(shè)在不久將來(lái),當(dāng)前的線程可以獲得鎖,因此虛擬機(jī)會(huì)讓當(dāng)前想要獲取鎖的線程做幾個(gè)空循環(huán)(這也是稱為自旋的原因),一般不會(huì)太久,可能是50個(gè)循環(huán)或100循環(huán),在經(jīng)過(guò)若干次循環(huán)后,如果得到鎖,就順利進(jìn)入臨界區(qū)。如果還不能獲得鎖,那就會(huì)將線程在操作系統(tǒng)層面掛起,這就是自旋鎖的優(yōu)化方式,這種方式確實(shí)也是可以提升效率的。最后沒(méi)辦法也就只能升級(jí)為重量級(jí)鎖了。

鎖消除

消除鎖是虛擬機(jī)另外一種鎖的優(yōu)化,這種優(yōu)化更徹底,Java虛擬機(jī)在JIT編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯),通過(guò)對(duì)運(yùn)行上下文的掃描,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過(guò)這種方式消除沒(méi)有必要的鎖,可以節(jié)省毫無(wú)意義的請(qǐng)求鎖時(shí)間,如下StringBuffer的append是一個(gè)同步方法,但是在add方法中的StringBuffer屬于一個(gè)局部變量,并且不會(huì)被其他線程所使用,因此StringBuffer不可能存在共享資源競(jìng)爭(zhēng)的情景,JVM會(huì)自動(dòng)將其鎖消除。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/**

?* Created by zejian on 2017/6/4.

?* Blog : http://blog.csdn.net/javazejian

?* 消除StringBuffer同步鎖

?*/

public?class?StringBufferRemoveSync {

?

????public?void?add(String str1, String str2) {

????????//StringBuffer是線程安全,由于sb只會(huì)在append方法中使用,不可能被其他線程引用

????????//因此sb屬于不可能共享的資源,JVM會(huì)自動(dòng)消除內(nèi)部的鎖

????????StringBuffer sb =?new?StringBuffer();

????????sb.append(str1).append(str2);

????}

?

????public?static?void?main(String[] args) {

????????StringBufferRemoveSync rmsync =?new?StringBufferRemoveSync();

????????for?(int?i =?0; i <?10000000; i++) {

????????????rmsync.add("abc",?"123");

????????}

????}

?

}

synchronize的可重入性:

從互斥鎖的設(shè)計(jì)上來(lái)說(shuō),當(dāng)一個(gè)線程試圖操作一個(gè)由其他線程持有的對(duì)象鎖的臨界資源時(shí),將會(huì)處于阻塞狀態(tài),但當(dāng)一個(gè)線程再次請(qǐng)求自己持有對(duì)象鎖的臨界資源時(shí),這種情況屬于重入鎖,請(qǐng)求將會(huì)成功,在java中synchronized是基于原子性的內(nèi)部鎖機(jī)制,是可重入的,因此在一個(gè)線程調(diào)用synchronized方法的同時(shí)在其方法體內(nèi)部調(diào)用該對(duì)象另一個(gè)synchronized方法,也就是說(shuō)一個(gè)線程得到一個(gè)對(duì)象鎖后再次請(qǐng)求該對(duì)象鎖,是允許的,這就是synchronized的可重入性。如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public?class?AccountingSync?implements?Runnable{

????static?AccountingSync instance=new?AccountingSync();

????static?int?i=0;

????static?int?j=0;

????@Override

????public?void?run() {

????????for(int?j=0;j<1000000;j++){

?

????????????//this,當(dāng)前實(shí)例對(duì)象鎖

????????????synchronized(this){

????????????????i++;

????????????????increase();//synchronized的可重入性

????????????}

????????}

????}

?

????public?synchronized?void?increase(){

????????j++;

????}

?

?

????public?static?void?main(String[] args)?throws?InterruptedException {

????????Thread t1=new?Thread(instance);

????????Thread t2=new?Thread(instance);

????????t1.start();t2.start();

????????t1.join();t2.join();

????????System.out.println(i);

????}

}

  正如代碼所演示的,在獲取當(dāng)前實(shí)例對(duì)象鎖后進(jìn)入synchronized代碼塊執(zhí)行同步代碼,并在代碼塊中調(diào)用了當(dāng)前實(shí)例對(duì)象的另外一個(gè)synchronized方法,再次請(qǐng)求當(dāng)前實(shí)例鎖時(shí),將被允許,進(jìn)而執(zhí)行方法體代碼,這就是重入鎖最直接的體現(xiàn),需要特別注意另外一種情況,當(dāng)子類繼承父類時(shí),子類也是可以通過(guò)可重入鎖調(diào)用父類的同步方法。注意由于synchronized是基于monitor實(shí)現(xiàn)的,因此每次重入,monitor中的計(jì)數(shù)器仍會(huì)加1。

線程中斷:正如中斷二字所表達(dá)的意義,在線程運(yùn)行(run方法)中間打斷它,在Java中,提供了以下3個(gè)有關(guān)線程中斷的方法

1

2

3

4

5

6

7

8

//中斷線程(實(shí)例方法)

public?void?Thread.interrupt();

?

//判斷線程是否被中斷(實(shí)例方法)

public?boolean?Thread.isInterrupted();

?

//判斷是否被中斷并清除當(dāng)前中斷狀態(tài)(靜態(tài)方法)

public?static?boolean?Thread.interrupted();

 等待喚醒機(jī)制與synchronize:所謂等待喚醒機(jī)制本篇主要指的是notify/notifyAll和wait方法,在使用這3個(gè)方法時(shí),必須處于synchronized代碼塊或者synchronized方法中,否則就會(huì)拋出IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用這幾個(gè)方法前必須拿到當(dāng)前對(duì)象的監(jiān)視器monitor對(duì)象,也就是說(shuō)notify/notifyAll和wait方法依賴于monitor對(duì)象,在前面的分析中,我們知道m(xù)onitor 存在于對(duì)象頭的Mark Word 中(存儲(chǔ)monitor引用指針),而synchronized關(guān)鍵字可以獲取 monitor ,這也就是為什么notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調(diào)用的原因。

?

本篇參考資料:http://blog.csdn.net/javazejian/article/details/72828483?locationNum=5&fps=1

http://www.cnblogs.com/pureEve/p/6421273.html

http://www.cnblogs.com/paddix/p/5367116.html

?

from:?https://www.cnblogs.com/mingyao123/p/7424911.html

總結(jié)

以上是生活随笔為你收集整理的Java并发编程 Synchronized及其实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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