摘要:前言同步代碼塊是中最基礎(chǔ)的實(shí)現(xiàn)線程間的同步與通信的機(jī)制之一,本篇我們將對(duì)同步代碼塊以及監(jiān)視器鎖的概念進(jìn)行討論。離開同步代碼塊后,所獲得的鎖會(huì)被自動(dòng)釋放。
前言
同步代碼塊(Synchronized Block) 是java中最基礎(chǔ)的實(shí)現(xiàn)線程間的同步與通信的機(jī)制之一,本篇我們將對(duì)同步代碼塊以及監(jiān)視器鎖的概念進(jìn)行討論。
系列文章目錄
什么是同步代碼塊(Synchronized Block)同步代碼塊簡(jiǎn)單來(lái)說(shuō)就是將一段代碼用一把鎖給鎖起來(lái), 只有獲得了這把鎖的線程才訪問, 并且同一時(shí)刻, 只有一個(gè)線程能持有這把鎖, 這樣就保證了同一時(shí)刻只有一個(gè)線程能執(zhí)行被鎖住的代碼.
這里有兩個(gè)關(guān)鍵字需要注意: 一段代碼和鎖.
一段代碼一般來(lái)說(shuō), 由 synchronized 鎖住的代碼都是拿{}括起來(lái)的代碼塊:
synchronized(this) { //由鎖保護(hù)的代碼 }
但值得注意的是, synchronized 也可以用來(lái)修飾一個(gè)方法, 則對(duì)應(yīng)的被鎖保護(hù)的一段代碼很自然就是整個(gè)方法體.
public class Foo { public synchronized void doSomething() { // 由鎖保護(hù)的代碼 } }鎖
其實(shí)鎖這個(gè)東西說(shuō)起來(lái)很抽象, 你可以就把它想象成現(xiàn)實(shí)中的鎖, 所以它只不過是一塊令牌, 一把尚方寶劍, 它是木頭做的還是金屬做的并不重要, 你可以拿任何東西當(dāng)作鎖, 重要的是它代表的含義: 誰(shuí)持有它, 誰(shuí)就有獨(dú)立訪問臨界區(qū)(即上面所說(shuō)的一段代碼)的權(quán)利.
在java中, 我們可以拿一個(gè)對(duì)象當(dāng)作鎖.
這里引用<
每個(gè)java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖, 這些鎖被稱為內(nèi)置鎖(Intrinsic Lock)或者監(jiān)視器鎖(Monitor Lock). 線程在進(jìn)入同步代碼塊之前會(huì)自動(dòng)獲得鎖, 并且在退出同步代碼塊時(shí)自動(dòng)釋放鎖.獲得內(nèi)置鎖的唯一途徑就是進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或方法.
所以, synchronized 同步代碼塊的標(biāo)準(zhǔn)寫法應(yīng)該是:
synchronized(reference-to-lock) { //臨界區(qū) }
其中, 括號(hào)里面的reference-to-lock就是鎖的引用, 它只要指向一個(gè)Java對(duì)象就行, 你可以自己隨便new一個(gè)不相關(guān)的對(duì)象, 將它作為鎖放進(jìn)去, 也可以像之前的例子一樣, 直接使用this, 代表使用當(dāng)前對(duì)象作為鎖.
有的同學(xué)就要問了, 我們前面說(shuō)可以用synchronized修飾一個(gè)方法, 并且也知道對(duì)應(yīng)的由鎖保護(hù)的代碼塊就是整個(gè)方法體, 但是, 它的鎖是什么呢?
要回答這個(gè)問題,首先要區(qū)分synchronized 所修飾的方法是否是靜態(tài)方法:
如果synchronized所修飾的是靜態(tài)方法, 則其所用的鎖為Class對(duì)象
如果synchronized所修飾的是非靜態(tài)方法, 則其所用的鎖為方法調(diào)用所在的對(duì)象
當(dāng)使用synchronized 修飾非靜態(tài)方法時(shí), 以下兩種寫法是等價(jià)的:
//寫法1 public synchronized void doSomething() { // 由鎖保護(hù)的代碼 } //寫法2 public void doSomething() { synchronized(this) { // 由鎖保護(hù)的代碼 } }到底拿什么鎖住了同步代碼塊
同步代碼塊中最難理解的部分就是拿什么作為了鎖, 上面我們已經(jīng)提到了三個(gè) this, Class對(duì)象, 方法調(diào)用所在的對(duì)象, 并且我們也說(shuō)明了可以拿任何java對(duì)象作為鎖.
this 和 方法調(diào)用所在的對(duì)象這兩個(gè)其實(shí)是一個(gè)意思, 我們需要特別注意的是, 一個(gè)Class可以有多個(gè)實(shí)例(Instance), 每一個(gè)Instance都可以作為鎖, 不同Instance就是不同的鎖, 同一個(gè)Instance就是同一個(gè)鎖, this 和 方法調(diào)用所在的對(duì)象 指代的都是調(diào)用這個(gè)同步代碼塊的對(duì)象.
這么說(shuō)可能比較抽象, 我們直接上例子: (以下例子轉(zhuǎn)載自博客Java中Synchronized的用法)
class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public void run() { synchronized(this) { for (int i = 0; i < 5; i++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { SyncThread syncThread = new SyncThread(); //線程1和線程2使用了SyncThread類的同一個(gè)對(duì)象實(shí)例 //因此, 這兩個(gè)線程中的synchronized(this), 持有的是同一把鎖 Thread thread1 = new Thread(syncThread, "SyncThread1"); Thread thread2 = new Thread(syncThread, "SyncThread2"); thread1.start(); thread2.start(); } }
運(yùn)行結(jié)果:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
這里兩個(gè)線程SyncThread1 和 SyncThread2 持有同一個(gè)對(duì)象syncThread的鎖, 因此同一時(shí)刻, 只有一個(gè)線程能訪問同步代碼塊, 線程SyncThread2 只有等 SyncThread1 執(zhí)行完同步代碼塊后, SyncThread1線程自動(dòng)釋放了鎖, 隨后 SyncThread2才能獲取同一把鎖, 進(jìn)入同步代碼塊.
我們也可以修改一下main函數(shù), 讓兩個(gè)線程持有同一Class對(duì)象的不同實(shí)例的鎖:
public static void main(String[] args) { Thread thread1 = new Thread(new SyncThread(), "SyncThread1"); Thread thread2 = new Thread(new SyncThread(), "SyncThread2"); thread1.start(); thread2.start(); }
上面這段等價(jià)于:
public static void main(String[] args) { SyncThread syncThread1 = new SyncThread(); SyncThread syncThread2 = new SyncThread(); Thread thread1 = new Thread(syncThread1, "SyncThread1"); Thread thread2 = new Thread(syncThread2, "SyncThread2"); thread1.start(); thread2.start(); }
運(yùn)行結(jié)果:
SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread1:6
SyncThread2:7
SyncThread1:8
SyncThread2:9
可見, 兩個(gè)線程這次都能訪問同步代碼塊, 這是因?yàn)榫€程1執(zhí)行的是syncThread1對(duì)象的同步代碼塊, 線程2執(zhí)行的是syncThread2的同步代碼塊, 雖然這兩個(gè)同步代碼塊一樣, 但是他們?cè)诓煌膶?duì)象實(shí)例里面, 即雖然它們都用this作為鎖, 但是this指代的對(duì)象在這兩個(gè)線程中不是同一個(gè)對(duì)象, 兩個(gè)線程各自都能獲得鎖, 因此各自都能執(zhí)行這一段同步代碼塊.
這告訴我們, 當(dāng)一段代碼用同步代碼塊包起來(lái)的時(shí)候, 并不絕對(duì)意味著這段代碼同一時(shí)刻只能由一個(gè)線程訪問, 這種情況只發(fā)生在多個(gè)線程訪問的是同一個(gè)Instance, 也就是說(shuō), 多個(gè)線程請(qǐng)求的是同一把鎖.
再回顧我們上面兩個(gè)例子, 第一個(gè)例子中, 兩個(gè)線程使用的是同一個(gè)對(duì)象實(shí)例, 他們需要同一把對(duì)象鎖 syncThread,
第二個(gè)例子中, 兩個(gè)線程分別使用了一個(gè)對(duì)象實(shí)例, 他們分別請(qǐng)求的是自己訪問的對(duì)象實(shí)例的鎖syncThread1, syncThread2, 因此都能訪問同步代碼塊.
導(dǎo)致不同線程可以同時(shí)訪問同步代碼塊的最根本原因就是我們使用的是當(dāng)前實(shí)例對(duì)象鎖(this), 因?yàn)轭惖膶?shí)例可以有多個(gè), 這導(dǎo)致了同步代碼塊散布在類的多個(gè)實(shí)例中, 雖然同一個(gè)實(shí)例中的同步代碼塊只能由持有鎖的單個(gè)線程訪問(this對(duì)象鎖保護(hù)), 但是我們可以每個(gè)線程訪問自己的對(duì)象實(shí)例, 而每一個(gè)對(duì)象實(shí)例的同步代碼塊都是一致的, 這就間接導(dǎo)致了多個(gè)線程同時(shí)訪問了"同一個(gè)"同步代碼塊.
上面這種情況在某些條件下是沒有問題的, 例如同步代碼塊中不存在對(duì)靜態(tài)變量(共享的狀態(tài)量)的修改.
但是, 對(duì)于上面的例子, 這樣的情況明顯違背了我們加同步代碼塊的初衷.
要解決上面的情況, 一種可行的辦法就是像第一個(gè)例子一樣, 多個(gè)線程使用同一個(gè)對(duì)象實(shí)例, 例如在單例模式下, 本身就只有一個(gè)對(duì)象實(shí)例, 所以多個(gè)線程必將請(qǐng)求同一把鎖, 從而實(shí)現(xiàn)同步訪問.
另一種方法就是我們下面要講的: 使用Class鎖.
使用Class級(jí)別鎖前面我們提到:
如果synchronized所修飾的是靜態(tài)方法, 則其所用的鎖為Class對(duì)象
這是因?yàn)殪o態(tài)方法是屬于類的而不屬于對(duì)象的, 因此synchronized修飾的靜態(tài)方法鎖定的是這個(gè)類的所有對(duì)象。我們來(lái)看下面一個(gè)例子(以下例子同樣轉(zhuǎn)載自博客Java中Synchronized的用法):
class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } // synchronized 關(guān)鍵字加在一個(gè)靜態(tài)方法上 public synchronized static void staticMethod() { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public void run() { staticMethod(); } public static void main(String[] args) { SyncThread syncThread1 = new SyncThread(); SyncThread syncThread2 = new SyncThread(); Thread thread1 = new Thread(syncThread1, "SyncThread1"); Thread thread2 = new Thread(syncThread2, "SyncThread2"); thread1.start(); thread2.start(); } }
運(yùn)行結(jié)果:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
可見, 靜態(tài)方法鎖定了類的所有對(duì)象, 用我們之前的話來(lái)說(shuō), 如果說(shuō)"因?yàn)轭惖膶?shí)例可以有多個(gè), 這導(dǎo)致了同步代碼塊散布在類的多個(gè)實(shí)例中", 那么類的靜態(tài)方法就是阻止同步代碼塊散布在類的實(shí)例中, 因?yàn)轭惖撵o態(tài)方法只屬于類本身.
其實(shí), 上面的例子的本質(zhì)就是拿Class對(duì)象作為鎖, 我們前面也提到了, 可以拿任何對(duì)象作為鎖, 如果我們直接拿類的Class對(duì)象作為鎖, 同樣可以保證所以線程請(qǐng)求的都是同一把鎖, 因?yàn)镃lass對(duì)象只有一個(gè).
類鎖實(shí)際上是通過對(duì)象鎖實(shí)現(xiàn)的,即類的 Class 對(duì)象鎖。每個(gè)類只有一個(gè) Class 對(duì)象,所以每個(gè)類只有一個(gè)類鎖。
class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public void run() { // 這里直接拿Class對(duì)象作為鎖 synchronized(SyncThread.class) { for (int i = 0; i < 5; i++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { SyncThread syncThread1 = new SyncThread(); SyncThread syncThread2 = new SyncThread(); Thread thread1 = new Thread(syncThread1, "SyncThread1"); Thread thread2 = new Thread(syncThread2, "SyncThread2"); thread1.start(); thread2.start(); } }
這樣所得到的結(jié)果與上面的類的靜態(tài)方法加鎖是一致的。
幾點(diǎn)補(bǔ)充其實(shí)到這里, 重要的部分已經(jīng)講完了, 下面補(bǔ)充說(shuō)明幾點(diǎn):
(1) 當(dāng)一個(gè)線程訪問對(duì)象的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該對(duì)象中的非synchronized(this)同步代碼塊。
這個(gè)結(jié)論是顯而易見的, 在沒有加鎖的情況下, 所有的線程都可以自由地訪問對(duì)象中的代碼, 而synchronized關(guān)鍵字只是限制了線程對(duì)于已經(jīng)加鎖的同步代碼塊的訪問, 并不會(huì)對(duì)其他代碼做限制.
這里也提示我們:
同步代碼塊應(yīng)該越短小越好
(2) 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
這個(gè)結(jié)論也是顯而易見的, 因?yàn)閟ynchronized(this)拿的都是當(dāng)前對(duì)象的鎖, 如果一個(gè)線程已經(jīng)進(jìn)入了一個(gè)同步代碼塊, 說(shuō)明它已經(jīng)拿到了鎖, 而訪問同一個(gè)object中的其他同步代碼塊同樣需要當(dāng)前對(duì)象的鎖, 所以它們會(huì)被阻塞.
(3) synchronized關(guān)鍵字不能繼承。
對(duì)于父類中用synchronized 修飾的方法,子類在覆蓋該方法時(shí),默認(rèn)情況下不是同步的,必須顯式的使用 synchronized 關(guān)鍵字修飾才行, 當(dāng)然子類也可以直接調(diào)用父類的方法, 這樣就間接實(shí)現(xiàn)了同步.
(4) 在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。
(5) 構(gòu)造方法不能使用synchronized關(guān)鍵字,但可以使用synchronized代碼塊來(lái)進(jìn)行同步。
(6) 離開同步代碼塊后,所獲得的鎖會(huì)被自動(dòng)釋放。
synchronized關(guān)鍵字通過一把鎖鎖住一段代碼, 使得線程只有在持有鎖的時(shí)候才能訪問這段代碼
任何java對(duì)象都可以作為這把鎖
可以在synchronized后面用()顯式的指定鎖. 也可以直接作用在方法上
作用于普通方法時(shí), 相當(dāng)于以this對(duì)象作為鎖, 此時(shí)同步代碼塊散布于類的所有實(shí)例中, 每一個(gè)實(shí)例的同步代碼塊的鎖 為該實(shí)例對(duì)象自身。
作用于靜態(tài)方法時(shí), 相當(dāng)于以Class對(duì)象作為鎖, 此時(shí)對(duì)象的所有實(shí)例只能爭(zhēng)搶同一把鎖。
內(nèi)置鎖的一個(gè)重要的特性是當(dāng)離開同步代碼塊之后, 會(huì)自動(dòng)釋放鎖,而其他的高級(jí)鎖(如ReentrantLock)需要顯式釋放鎖。
思考題前面我們說(shuō)明了synchronized 的使用方法,但對(duì)一些底層的細(xì)節(jié)并不了解,如:
前面說(shuō)“獲得內(nèi)置鎖的唯一途徑就是進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或方法.”, 這句話看上去很有道理,其實(shí)是廢話,同步代碼塊究竟是怎么獲得鎖的?
我們說(shuō),JAVA中任何對(duì)象都可以作為鎖,那么鎖信息是怎么被記錄和存儲(chǔ)的?
為什么代碼離開了同步代碼塊鎖就被釋放了,誰(shuí)釋放了鎖,怎樣叫釋放了鎖?
這些問題,我們后續(xù)的文章再研究。
(完)
查看更多系列文章:系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/76699.html
摘要:由此可見,自旋鎖和各有優(yōu)劣,他們分別適用于競(jìng)爭(zhēng)不多和競(jìng)爭(zhēng)激烈的場(chǎng)景中。每一個(gè)試圖進(jìn)入同步代碼塊的線程都會(huì)被封裝成對(duì)象,它們或在對(duì)象的中,或在中,等待成為對(duì)象的成為的對(duì)象即獲取了監(jiān)視器鎖。 前言 系列文章目錄 前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機(jī)制,大致知道了這些關(guān)鍵字和方法是干什么的,以及怎么用。 但是,知其然,并不知其所以然。 例如...
摘要:本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問,線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對(duì)象的對(duì)象鎖的其他線程。 本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問,線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。 寫在前面 花了一周時(shí)...
摘要:在從返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖。就緒隊(duì)列存儲(chǔ)了將要獲得鎖的線程,阻塞隊(duì)列存儲(chǔ)了被阻塞的線程。當(dāng)線程呈狀態(tài),調(diào)用線程對(duì)象的方法會(huì)出現(xiàn)異常。在執(zhí)行同步代碼塊過程中,遇到異常而導(dǎo)致線程終止,鎖也會(huì)被釋放。 方法wait()的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,wait()方法是Object類的方法,該方法用來(lái)將當(dāng)前線程置入預(yù)執(zhí)行隊(duì)列中,并且在wait()所在的代碼行處停止執(zhí)行,直...
摘要:為了避免一篇文章的篇幅過長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...
摘要:為了避免一篇文章的篇幅過長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...
閱讀 2307·2021-11-23 09:51
閱讀 3784·2021-10-20 13:49
閱讀 1780·2021-09-06 15:13
閱讀 1882·2021-09-06 15:02
閱讀 3358·2021-09-02 15:11
閱讀 962·2019-08-29 15:37
閱讀 1803·2019-08-29 13:24
閱讀 2325·2019-08-29 11:28