摘要:而導(dǎo)致這個(gè)問(wèn)題的原因是線程并行執(zhí)行操作并不是原子的,存在線程安全問(wèn)題。如果已經(jīng)有線程持有了鎖,那這個(gè)線程會(huì)獨(dú)占鎖,直到鎖釋放完畢之前,其他線程都會(huì)被阻塞。當(dāng)鎖處于重量級(jí)鎖狀態(tài),其他線程嘗試獲取鎖時(shí),都會(huì)被阻塞,也就是狀態(tài)。
1. 什么時(shí)候需要用Synchronized
Synchronized主要作用是在多個(gè)線程操作共享數(shù)據(jù)的時(shí)候,保證對(duì)共享數(shù)據(jù)訪問(wèn)的線程安全性。比如兩個(gè)線程對(duì)于i這個(gè)共享變量同時(shí)做i++遞增操作,那么這個(gè)時(shí)候?qū)τ趇這個(gè)值來(lái)說(shuō)就存在一個(gè)不確定性,也就是說(shuō)理論上i的值應(yīng)該是2,但是也可能是1。而導(dǎo)致這個(gè)問(wèn)題的原因是線程并行執(zhí)行i++操作并不是原子的,存在線程安全問(wèn)題。所以通常來(lái)說(shuō)解決辦法是通過(guò)加鎖來(lái)實(shí)現(xiàn)線程的串行執(zhí)行,而synchronized就是java中鎖的實(shí)現(xiàn)的關(guān)鍵字。
synchronized在并發(fā)編程中是一個(gè)非常重要的角色,在JDK1.6之前,它是一個(gè)重量級(jí)鎖的角色,但是在JDK1.6之后對(duì)synchronized做了優(yōu)化,優(yōu)化以后性能有了較大的提升
2.Synchronized的使用
synchronized有三種使用方法,這三種使用方法分別對(duì)應(yīng)三種不同的作用域,代碼如下:
①修飾普通同步方法
將synchronized修飾在普通同步方法,那么該鎖的作用域是在當(dāng)前實(shí)例對(duì)象范圍內(nèi),也就是說(shuō)對(duì)于 SyncDemosd=newSyncDemo();這一個(gè)實(shí)例對(duì)象sd來(lái)說(shuō),多個(gè)線程訪問(wèn)access方法會(huì)有鎖的限制。如果access已經(jīng)有線程持有了鎖,那這個(gè)線程會(huì)獨(dú)占鎖,直到鎖釋放完畢之前,其他線程都會(huì)被阻塞。
public SyncDemo{
Object lock =new Object();
//形式1
public synchronized void access(){
//
}
//形式2,作用域等同于形式1
public void access1(){
synchronized(lock){
//
}
}
②修飾靜態(tài)同步方法
修飾靜態(tài)同步方法或者靜態(tài)對(duì)象、類,那么這個(gè)鎖的作用范圍是類級(jí)別。舉個(gè)簡(jiǎn)單的例子,{SyncDemo sd=SyncDemo();SyncDemo sd2=new SyncDemo();} 兩個(gè)不同的實(shí)例sd和sd2, 如果sd這個(gè)實(shí)例訪問(wèn)access方法并且成功持有了鎖,那么sd2這個(gè)對(duì)象如果同樣來(lái)訪問(wèn)access方法,那么它必須要等待sd這個(gè)對(duì)象的鎖釋放以后,sd2這個(gè)對(duì)象的線程才能訪問(wèn)該方法,這就是類鎖;也就是說(shuō)類鎖就相當(dāng)于全局鎖的概念,作用范圍是類級(jí)別。
public SyncDemo{
static Object lock=new Object();
//形式1
public synchronized static void access(){
//
}
//形式2等同于形式1
public void access1(){
synchronized(lock){
//
}
}
//形式3等同于前面兩種
public void access2(){
synchronzied(SyncDemo.class){
//
}
}
}
③.同步方法塊
同步方法塊,是范圍最小的鎖,鎖的是synchronized括號(hào)里面配置的對(duì)象。這種鎖在實(shí)際工作中使用得比較頻繁,畢竟鎖的作用范圍越大,那么對(duì)性能的影響就越嚴(yán)重。
public SyncDemo{
Object lock=new Object();
public void access(){
//do something
synchronized(lock){
//
}
}
}
3.Synchronized的實(shí)現(xiàn)原理分析
synchronized實(shí)現(xiàn)的鎖是存儲(chǔ)在Java對(duì)象頭里,什么是對(duì)象頭呢?在Hotspot虛擬機(jī)中,對(duì)象在內(nèi)存中的存儲(chǔ)布局,可以分為三個(gè)區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)、對(duì)齊填充(Padding) 當(dāng)我們?cè)贘ava代碼中,使用new創(chuàng)建一個(gè)對(duì)象實(shí)例的時(shí)候,(hotspot虛擬機(jī))JVM層面實(shí)際上會(huì)創(chuàng)建一個(gè) instanceOopDesc對(duì)象。instanceOopDesc的定義在Hotspot源碼中的 instanceOop.hpp文件中,另外,arrayOopDesc的定義對(duì)應(yīng) arrayOop.hpp
從instanceOopDesc代碼中可以看到 instanceOopDesc繼承自oopDesc,oopDesc的定義載Hotspot源碼中的 oop.hpp文件中。
在普通實(shí)例對(duì)象中,oopDesc的定義包含兩個(gè)成員,分別是 _mark和 _metadata,其中_mark表示對(duì)象標(biāo)記、屬于markOop類型,也就是Mark World,它記錄了對(duì)象和鎖有關(guān)的信。_metadata表示類元信息,類元信息存儲(chǔ)的是對(duì)象指向它的類元數(shù)據(jù)(Klass)的首地址,其中Klass表示普通指針、 _compressed_klass表示壓縮類指針。
Mark Word
前面說(shuō)的普通對(duì)象的對(duì)象頭由兩部分組成,分別是markOop以及類元信息,markOop官方稱為Mark Word 。在Hotspot中,markOop的定義在 markOop.hpp文件中,代碼如下
Mark word記錄了對(duì)象和鎖有關(guān)的信息,當(dāng)某個(gè)對(duì)象被synchronized關(guān)鍵字當(dāng)成同步鎖時(shí),那么圍繞這個(gè)鎖的一系列操作都和Mark word有關(guān)系。Mark Word在32位虛擬機(jī)的長(zhǎng)度是32bit、在64位虛擬機(jī)的長(zhǎng)度是64bit。 Mark Word里面存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。
鎖標(biāo)志位的表示意義
1.鎖標(biāo)識(shí) lock=00 表示輕量級(jí)鎖
2.鎖標(biāo)識(shí) lock=10 表示重量級(jí)鎖
3.偏向鎖標(biāo)識(shí) biased_lock=1表示偏向鎖
4.偏向鎖標(biāo)識(shí) biased_lock=0且鎖標(biāo)識(shí)=01表示無(wú)鎖狀態(tài)
4.鎖的升級(jí)
前面提到了鎖的幾個(gè)概念,偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。在JDK1.6之前,synchronized是一個(gè)重量級(jí)鎖,性能比較差。從JDK1.6開始,為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗,synchronized進(jìn)行了優(yōu)化,引入了 偏向鎖和 輕量級(jí)鎖的概念。所以從JDK1.6開始,鎖一共會(huì)有四種狀態(tài),鎖的狀態(tài)根據(jù)競(jìng)爭(zhēng)激烈程度從低到高分別是:無(wú)鎖狀態(tài)->偏向鎖狀態(tài)->輕量級(jí)鎖狀態(tài)->重量級(jí)鎖狀態(tài)。這幾個(gè)狀態(tài)會(huì)隨著鎖競(jìng)爭(zhēng)的情況逐步升級(jí)。為了提高獲得鎖和釋放鎖的效率,鎖可以升級(jí)但是不能降級(jí)。
偏向鎖
在大多數(shù)的情況下,鎖不僅不存在多線程的競(jìng)爭(zhēng),而且總是由同一個(gè)線程獲得。因此為了讓線程獲得鎖的代價(jià)更低引入了偏向鎖的概念。偏向鎖的意思是如果一個(gè)線程獲得了一個(gè)偏向鎖,如果在接下來(lái)的一段時(shí)間中沒(méi)有其他線程來(lái)競(jìng)爭(zhēng)鎖,那么持有偏向鎖的線程再次進(jìn)入或者退出同一個(gè)同步代碼塊,不需要再次進(jìn)行搶占鎖和釋放鎖的操作。偏向鎖可以通過(guò) -XX:+UseBiasedLocking開啟或者關(guān)閉。
偏向鎖的獲取
偏向鎖的獲取過(guò)程非常簡(jiǎn)單,當(dāng)一個(gè)線程訪問(wèn)同步塊獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)偏向鎖的線程ID,表示哪個(gè)線程獲得了偏向鎖,結(jié)合前面分析的Mark Word來(lái)分析一下偏向鎖的獲取邏輯
1首先獲取目標(biāo)對(duì)象的Mark Word,根據(jù)鎖的標(biāo)識(shí)為和epoch去判斷當(dāng)前是否處于可偏向的狀態(tài)
2如果為可偏向狀態(tài),則通過(guò)CAS操作將自己的線程ID寫入到MarkWord,如果CAS操作成功,則表示當(dāng)前線程成功獲取到偏向鎖,繼續(xù)執(zhí)行同步代碼塊
3如果是已偏向狀態(tài),先檢測(cè)MarkWord中存儲(chǔ)的threadID和當(dāng)前訪問(wèn)的線程的threadID是否相等,如果相等,表示當(dāng)前線程已經(jīng)獲得了偏向鎖,則不需要再獲得鎖直接執(zhí)行同步代碼;如果不相等,則證明當(dāng)前鎖偏向于其他線程,需要撤銷偏向鎖。
偏向鎖的撤銷
當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放偏向鎖,撤銷偏向鎖的過(guò)程需要等待一個(gè)全局安全點(diǎn)(所有工作線程都停止字節(jié)碼的執(zhí)行)。
1首先,暫停擁有偏向鎖的線程,然后檢查偏向鎖的線程是否為存活狀態(tài)
2如果線程已經(jīng)死了,直接把對(duì)象頭設(shè)置為無(wú)鎖狀態(tài)
3如果還活著,當(dāng)達(dá)到全局安全點(diǎn)時(shí)獲得偏向鎖的線程會(huì)被掛起,接著偏向鎖升級(jí)為輕量級(jí)鎖,然后喚醒被阻塞在全局安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼
輕量級(jí)鎖
前面我們知道,當(dāng)存在超過(guò)一個(gè)線程在競(jìng)爭(zhēng)同一個(gè)同步代碼塊時(shí),會(huì)發(fā)生偏向鎖的撤銷。偏向鎖撤銷以后對(duì)象會(huì)可能會(huì)處于兩種狀態(tài)
1.一種是不可偏向的無(wú)鎖狀態(tài),簡(jiǎn)單來(lái)說(shuō)就是已經(jīng)獲得偏向鎖的線程已經(jīng)退出了同步代碼塊,那么這個(gè)時(shí)候會(huì)撤銷偏向鎖,并升級(jí)為輕量級(jí)鎖
2.一種是不可偏向的已鎖狀態(tài),簡(jiǎn)單來(lái)說(shuō)就是已經(jīng)獲得偏向鎖的線程正在執(zhí)行同步代碼塊,那么這個(gè)時(shí)候會(huì)升級(jí)到輕量級(jí)鎖并且被原持有鎖的線程獲得鎖
輕量級(jí)鎖加鎖
1.JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建用于存儲(chǔ)鎖記錄的空間(LockRecord)
2.將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,稱為Displaced Mark Word.
3.線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針
4.如果替換成功,表示當(dāng)前線程獲得輕量級(jí)鎖,如果失敗,表示存在其他線程競(jìng)爭(zhēng)鎖,那么當(dāng)前線程會(huì)嘗試使用CAS來(lái)獲取鎖,當(dāng)自旋超過(guò)指定次數(shù)(可以自定義)時(shí)仍然無(wú)法獲得鎖,此時(shí)鎖會(huì)膨脹升級(jí)為重量級(jí)鎖
輕量鎖解鎖
1.嘗試CAS操作將所記錄中的Mark Word替換回到對(duì)象頭中
2.如果成功,表示沒(méi)有競(jìng)爭(zhēng)發(fā)生
3.如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖會(huì)膨脹成重量級(jí)鎖
一旦鎖升級(jí)成重量級(jí)鎖,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。當(dāng)鎖處于重量級(jí)鎖狀態(tài),其他線程嘗試獲取鎖時(shí),都會(huì)被阻塞,也就是 BLOCKED狀態(tài)。當(dāng)持有鎖的線程釋放鎖之后會(huì)喚醒這些現(xiàn)場(chǎng),被喚醒之后的線程會(huì)進(jìn)行新一輪的競(jìng)爭(zhēng)
重量級(jí)鎖
重量級(jí)鎖依賴對(duì)象內(nèi)部的monitor鎖來(lái)實(shí)現(xiàn),而monitor又依賴操作系統(tǒng)的MutexLock(互斥鎖),假設(shè)Mutex變量的值為1,表示互斥鎖空閑,這個(gè)時(shí)候某個(gè)線程調(diào)用lock可以獲得鎖,而Mutex的值為0表示互斥鎖已經(jīng)被其他線程獲得,其他線程調(diào)用lock只能掛起等待。
為什么重量級(jí)鎖的開銷比較大呢?原因是當(dāng)系統(tǒng)檢查到是重量級(jí)鎖之后,會(huì)把等待想要獲取鎖的線程阻塞,被阻塞的線程不會(huì)消耗CPU,但是阻塞或者喚醒一個(gè)線程,都需要通過(guò)操作系統(tǒng)來(lái)實(shí)現(xiàn),也就是相當(dāng)于從用戶態(tài)轉(zhuǎn)化到內(nèi)核態(tài),而轉(zhuǎn)化狀態(tài)是需要消耗時(shí)間的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/74435.html
摘要:而導(dǎo)致這個(gè)問(wèn)題的原因是線程并行執(zhí)行操作并不是原子的,存在線程安全問(wèn)題。表示自旋鎖,由于線程的阻塞和喚醒需要從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對(duì)來(lái)說(shuō)性能開銷很大。 文章簡(jiǎn)介 synchronized想必大家都不陌生,用來(lái)解決線程安全問(wèn)題的利器。同時(shí)也是Java高級(jí)程序員面試比較常見的面試題。這篇文正會(huì)帶大家徹底了解synchronized的實(shí)現(xiàn)。 內(nèi)容導(dǎo)航 什么時(shí)候需要用Synchr...
摘要:多線程編程這篇文章分析了多線程的優(yōu)缺點(diǎn),如何創(chuàng)建多線程,分享了線程安全和線程通信線程池等等一些知識(shí)。 中間件技術(shù)入門教程 中間件技術(shù)入門教程,本博客介紹了 ESB、MQ、JMS 的一些知識(shí)... SpringBoot 多數(shù)據(jù)源 SpringBoot 使用主從數(shù)據(jù)源 簡(jiǎn)易的后臺(tái)管理權(quán)限設(shè)計(jì) 從零開始搭建自己權(quán)限管理框架 Docker 多步構(gòu)建更小的 Java 鏡像 Docker Jav...
摘要:的主要功能和關(guān)鍵字一致,均是用于多線程的同步。而僅支持通過(guò)查詢當(dāng)前線程是否持有鎖。由于和使用的是同一把可重入鎖,所以線程可以進(jìn)入方法,并再次獲得鎖,而不會(huì)被阻塞住。公平與非公平公平與非公平指的是線程獲取鎖的方式。 1.簡(jiǎn)介 可重入鎖ReentrantLock自 JDK 1.5 被引入,功能上與synchronized關(guān)鍵字類似。所謂的可重入是指,線程可對(duì)同一把鎖進(jìn)行重復(fù)加鎖,而不會(huì)被阻...
摘要:由此可見,自旋鎖和各有優(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)鍵字和方法是干什么的,以及怎么用。 但是,知其然,并不知其所以然。 例如...
摘要:一言以蔽之,被修飾的變量能夠保證每個(gè)線程能夠獲取該變量的最新值,從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。為了實(shí)現(xiàn)內(nèi)存語(yǔ)義時(shí),編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類型的處理器重排序。volatile原理volatile簡(jiǎn)介Java內(nèi)存模型告訴我們,各個(gè)線程會(huì)將共享變量從主內(nèi)存中拷貝到工作內(nèi)存,然后執(zhí)行引擎會(huì)基于工作內(nèi)存中的數(shù)據(jù)進(jìn)行操作處理。 線程在工作內(nèi)存進(jìn)行操作后何時(shí)會(huì)寫到主內(nèi)存中...
閱讀 1552·2021-09-22 15:52
閱讀 3459·2021-09-22 14:59
閱讀 2843·2021-09-02 15:12
閱讀 971·2021-08-20 09:35
閱讀 1578·2019-08-30 14:09
閱讀 2709·2019-08-30 13:56
閱讀 1646·2019-08-26 18:27
閱讀 3363·2019-08-26 13:37