Java并发编程 Synchronized及其实现原理
Synchronized是Java中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法。Synchronized的作用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改能夠及時可見(3)有效解決重排序問題。
Java中每一個對象都可以作為鎖,這是synchronized實現(xiàn)同步的基礎:
1、普通同步方法,鎖是當前實例對象
| 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)同步方法,鎖是當前類的class對象
| 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、同步方法塊,鎖是括號里面的對象
| 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 虛擬機中的同步(Synchronization)基于進入和退出Monitor對象實現(xiàn), 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此。在 Java 語言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法。同步方法 并不是由 monitorenter 和 monitorexit 指令來實現(xiàn)同步的,而是由方法調(diào)用指令讀取運行時常量池中方法表結構的 ACC_SYNCHRONIZED 標志來隱式實現(xiàn)的,關于這點,稍后詳細分析。
同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM需要保證每一個monitorenter都有一個monitorexit與之相對應。任何對象都有一個monitor與之相關聯(lián),當且一個monitor被持有之后,他將處于鎖定狀態(tài)。線程執(zhí)行到monitorenter指令時,將會嘗試獲取對象所對應的monitor所有權,即嘗試獲取對象的鎖;
在JVM中,對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭、實例變量和填充數(shù)據(jù)。如下:
實例變量:存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息,如果是數(shù)組的實例部分還包括數(shù)組的長度,這部分內(nèi)存按4字節(jié)對齊。
填充數(shù)據(jù):由于虛擬機要求對象起始地址必須是8字節(jié)的整數(shù)倍。填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對齊,這點了解即可。
對象頭:Hotspot虛擬機的對象頭主要包括兩部分數(shù)據(jù):Mark Word(標記字段)、Klass Pointer(類型指針)。其中Klass Point是是對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例,Mark Word用于存儲對象自身的運行時數(shù)據(jù),它是實現(xiàn)輕量級鎖和偏向鎖的關鍵。
Mark Word:用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程 ID、偏向時間戳等等。Java對象頭一般占有兩個機器碼(在32位虛擬機中,1個機器碼等于4字節(jié),也就是32bit),但是如果對象是數(shù)組類型,則需要三個機器碼,因為JVM虛擬機可以通過Java對象的元數(shù)據(jù)信息確定Java對象的大小,但是無法從數(shù)組的元數(shù)據(jù)來確認數(shù)組的大小,所以用一塊來記錄數(shù)組長度。
Monior:我們可以把它理解為一個同步工具,也可以描述為一種同步機制,它通常被描述為一個對象。與一切皆對象一樣,所有的Java對象是天生的Monitor,每一個Java對象都有成為Monitor的潛質(zhì),因為在Java的設計中 ,每一個Java對象自打娘胎里出來就帶了一把看不見的鎖,它叫做內(nèi)部鎖或者Monitor鎖。Monitor 是線程私有的數(shù)據(jù)結構,每一個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個monitor關聯(lián)(對象頭的MarkWord中的LockWord指向monitor的起始地址),同時monitor中有一個Owner字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程占用。其結構如下:
Owner:初始時為NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖后保存線程唯一標識,當鎖被釋放時又設置為NULL;
EntryQ:關聯(lián)一個系統(tǒng)互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個數(shù)。
Nest:用來實現(xiàn)重入鎖的計數(shù)。
HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因為競爭鎖失敗又被阻塞)從而導致性能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。
?Java虛擬機對synchronize的優(yōu)化:
鎖的狀態(tài)總共有四種,無鎖狀態(tài)、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖,但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現(xiàn)鎖的降級,關于重量級鎖,前面我們已詳細分析過,下面我們將介紹偏向鎖和輕量級鎖以及JVM的其他優(yōu)化手段。
偏向鎖
偏向鎖是Java 6之后加入的新鎖,它是一種針對加鎖操作的優(yōu)化手段,經(jīng)過研究發(fā)現(xiàn),在大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word 的結構也變?yōu)槠蜴i結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。所以,對于沒有鎖競爭的場合,偏向鎖有很好的優(yōu)化效果,畢竟極有可能連續(xù)多次是同一個線程申請相同的鎖。但是對于鎖競爭比較激烈的場合,偏向鎖就失效了,因為這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗后,并不會立即膨脹為重量級鎖,而是先升級為輕量級鎖。
輕量級鎖
倘若偏向鎖失敗,虛擬機并不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段(1.6之后加入的),此時Mark Word 的結構也變?yōu)檩p量級鎖的結構。輕量級鎖能夠提升程序性能的依據(jù)是“對絕大部分的鎖,在整個同步周期內(nèi)都不存在競爭”,注意這是經(jīng)驗數(shù)據(jù)。需要了解的是,輕量級鎖所適應的場景是線程交替執(zhí)行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹為重量級鎖。
自旋鎖
輕量級鎖失敗后,虛擬機為了避免線程真實地在操作系統(tǒng)層面掛起,還會進行一項稱為自旋鎖的優(yōu)化手段。這是基于在大多數(shù)情況下,線程持有鎖的時間都不會太長,如果直接掛起操作系統(tǒng)層面的線程可能會得不償失,畢竟操作系統(tǒng)實現(xiàn)線程之間的切換時需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設在不久將來,當前的線程可以獲得鎖,因此虛擬機會讓當前想要獲取鎖的線程做幾個空循環(huán)(這也是稱為自旋的原因),一般不會太久,可能是50個循環(huán)或100循環(huán),在經(jīng)過若干次循環(huán)后,如果得到鎖,就順利進入臨界區(qū)。如果還不能獲得鎖,那就會將線程在操作系統(tǒng)層面掛起,這就是自旋鎖的優(yōu)化方式,這種方式確實也是可以提升效率的。最后沒辦法也就只能升級為重量級鎖了。
鎖消除
消除鎖是虛擬機另外一種鎖的優(yōu)化,這種優(yōu)化更徹底,Java虛擬機在JIT編譯時(可以簡單理解為當某段代碼即將第一次被執(zhí)行時進行編譯,又稱即時編譯),通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節(jié)省毫無意義的請求鎖時間,如下StringBuffer的append是一個同步方法,但是在add方法中的StringBuffer屬于一個局部變量,并且不會被其他線程所使用,因此StringBuffer不可能存在共享資源競爭的情景,JVM會自動將其鎖消除。
| 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只會在append方法中使用,不可能被其他線程引用 ????????//因此sb屬于不可能共享的資源,JVM會自動消除內(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的可重入性:
從互斥鎖的設計上來說,當一個線程試圖操作一個由其他線程持有的對象鎖的臨界資源時,將會處于阻塞狀態(tài),但當一個線程再次請求自己持有對象鎖的臨界資源時,這種情況屬于重入鎖,請求將會成功,在java中synchronized是基于原子性的內(nèi)部鎖機制,是可重入的,因此在一個線程調(diào)用synchronized方法的同時在其方法體內(nèi)部調(diào)用該對象另一個synchronized方法,也就是說一個線程得到一個對象鎖后再次請求該對象鎖,是允許的,這就是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,當前實例對象鎖 ????????????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); ????} } |
正如代碼所演示的,在獲取當前實例對象鎖后進入synchronized代碼塊執(zhí)行同步代碼,并在代碼塊中調(diào)用了當前實例對象的另外一個synchronized方法,再次請求當前實例鎖時,將被允許,進而執(zhí)行方法體代碼,這就是重入鎖最直接的體現(xiàn),需要特別注意另外一種情況,當子類繼承父類時,子類也是可以通過可重入鎖調(diào)用父類的同步方法。注意由于synchronized是基于monitor實現(xiàn)的,因此每次重入,monitor中的計數(shù)器仍會加1。
線程中斷:正如中斷二字所表達的意義,在線程運行(run方法)中間打斷它,在Java中,提供了以下3個有關線程中斷的方法
| 1 2 3 4 5 6 7 8 | //中斷線程(實例方法) public?void?Thread.interrupt(); ? //判斷線程是否被中斷(實例方法) public?boolean?Thread.isInterrupted(); ? //判斷是否被中斷并清除當前中斷狀態(tài)(靜態(tài)方法) public?static?boolean?Thread.interrupted(); |
等待喚醒機制與synchronize:所謂等待喚醒機制本篇主要指的是notify/notifyAll和wait方法,在使用這3個方法時,必須處于synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常,這是因為調(diào)用這幾個方法前必須拿到當前對象的監(jiān)視器monitor對象,也就是說notify/notifyAll和wait方法依賴于monitor對象,在前面的分析中,我們知道m(xù)onitor 存在于對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字可以獲取 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
總結
以上是生活随笔為你收集整理的Java并发编程 Synchronized及其实现原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java synchronized 详解
- 下一篇: Java的标签--弱化的goto