Java并发编程 Synchronized及其实现原理
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)題。
- 上一篇: Java synchronized 详解
- 下一篇: Java的标签--弱化的goto