日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

java線程安全和鎖機(jī)制詳解

 liang1234_ 2019-01-30
在開(kāi)始這篇blog之前應(yīng)該先了解幾個(gè)概念:

臨界區(qū)

    臨界區(qū)指的是一個(gè)訪問(wèn)共用資源(例如:共用設(shè)備或是共用存儲(chǔ)器)的程序片段,而這些共用資源又無(wú)法同時(shí)被多個(gè)線程訪問(wèn)的特性。當(dāng)有線程進(jìn)入臨界區(qū)段時(shí),其他線程或是進(jìn)程必須等待(例如:bounded waiting 等待法),有一些同步的機(jī)制必須在臨界區(qū)段的進(jìn)入點(diǎn)與離開(kāi)點(diǎn)實(shí)現(xiàn),以確保這些共用資源是被互斥獲得使用,例如:semaphore。只能被單一線程訪問(wèn)的設(shè)備,例如:打印機(jī)。

互斥量

    互斥量是一個(gè)可以處于兩態(tài)之一的變量:解鎖和加鎖。這樣,只需要一個(gè)二進(jìn)制位表示它,不過(guò)實(shí)際上,常常使用一個(gè)整型量,0表示解鎖,而其他所有的值則表示加鎖?;コ饬渴褂脙蓚€(gè)過(guò)程。當(dāng)一個(gè)線程(或進(jìn)程)需要訪問(wèn)臨界區(qū)時(shí),它調(diào)用mutex_lock。如果該互斥量當(dāng)前是解鎖的(即臨界區(qū)可用),此調(diào)用成功,調(diào)用線程可以自由進(jìn)入該臨界區(qū)。
另一方面,如果該互斥量已經(jīng)加鎖,調(diào)用線程被阻塞,直到在臨界區(qū)中的線程完成并調(diào)用mutex_unlock。如果多個(gè)線程被阻塞在該互斥量上,將隨機(jī)選擇一個(gè)線程并允許它獲得鎖。

管程

   管程 (英語(yǔ):Monitors,也稱為監(jiān)視器) 是一種程序結(jié)構(gòu),結(jié)構(gòu)內(nèi)的多個(gè)子程序(對(duì)象或模塊)形成的多個(gè)工作線程互斥訪問(wèn)共享資源。這些共享資源一般是硬件設(shè)備或一群變數(shù)。
    管程實(shí)現(xiàn)了在一個(gè)時(shí)間點(diǎn),最多只有一個(gè)線程在執(zhí)行管程的某個(gè)子程序。與那些通過(guò)修改數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)互斥訪問(wèn)的并發(fā)程序設(shè)計(jì)相比,管程實(shí)現(xiàn)很大程度上簡(jiǎn)化了程序設(shè)計(jì)
    系統(tǒng)中的各種硬件資源和軟件資源,均可用數(shù)據(jù)結(jié)構(gòu)抽象地描述其資源特性,即用少量信息和對(duì)資源所執(zhí)行的操作來(lái)表征該資源,而忽略了它們的內(nèi)部結(jié)構(gòu)和實(shí)現(xiàn)細(xì)節(jié)。
    利用共享數(shù)據(jù)結(jié)構(gòu)抽象地表示系統(tǒng)中的共享資源,而把對(duì)該共享數(shù)據(jù)結(jié)構(gòu)實(shí)施的操作定義為一組過(guò)程。

信號(hào)量

   信號(hào)量(Semaphore),有時(shí)被稱為信號(hào)燈,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來(lái)保證兩個(gè)或多個(gè)關(guān)鍵代碼段不被并發(fā)調(diào)用。在進(jìn)入一個(gè)關(guān)鍵代碼段之前,線程必須獲取一個(gè)信號(hào)量;一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號(hào)量。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待直到第一個(gè)線程釋放信號(hào)量。為了完成這個(gè)過(guò)程,需要?jiǎng)?chuàng)建一個(gè)信號(hào)量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個(gè)關(guān)鍵代碼段的首末端。確認(rèn)這些信號(hào)量VI引用的是初始創(chuàng)建的信號(hào)量。

CAS操作(Compare-and-Swap):

    CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。更詳細(xì)資料:http://zl198751./blog/1848575

重排序

    編譯器和處理器”為了提高性能,而在程序執(zhí)行時(shí)會(huì)對(duì)程序進(jìn)行的重排序。它的出現(xiàn)是為了提高程序的并發(fā)度,從而提高性能!但是對(duì)于多線程程序,重排序可能會(huì)導(dǎo)致程序執(zhí)行的結(jié)果不是我們需要的結(jié)果!重排序分為“編譯器”和“處理器”兩個(gè)方面,而“處理器”重排序又包括“指令級(jí)重排序”和“內(nèi)存的重排序”。

一、線程與內(nèi)存交互操作



    所有的變量(實(shí)例字段,靜態(tài)字段,構(gòu)成數(shù)組對(duì)象的 元素,不包括局部變量和方法參數(shù))都存儲(chǔ)在主內(nèi)存中,每個(gè)線程有自己的工作內(nèi)存,線程的工作內(nèi)存保存被線程使用到變量的主內(nèi)存副本拷貝。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存的變量。不同線程之間也不能直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞通過(guò)主內(nèi)存來(lái)完成。

Java內(nèi)存模型定義了八種操作

  • lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一個(gè)線程獨(dú)占的狀態(tài);
  • unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定;
  • read(讀?。鹤饔糜谥鲀?nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)魉偷骄€程中的工作內(nèi)存,以便隨后的load動(dòng)作使用;
  • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中;
  • use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎;
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存中的變量;
  • store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作;
  • write(寫(xiě)入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值寫(xiě)入主內(nèi)存的變量中。

volatile關(guān)鍵字作用:

    1)保證了新值能立即存儲(chǔ)到主內(nèi)存,每次使用前立即從主內(nèi)存中刷新。

    2)禁止指令重排序優(yōu)化。

    注:volatile關(guān)鍵字不能保證在多線程環(huán)境下對(duì)共享數(shù)據(jù)的操作的正確性??梢允褂迷谧约籂顟B(tài)改變之后需要立即通知所有線程的情況下。

二、并發(fā)的三個(gè)特性

原子性

    原子性是指不可再分的最小操作指令,即單條機(jī)器指令,原子性操作任意時(shí)刻只能有一個(gè)線程,因此是線程安全的。
Java內(nèi)存模型中通過(guò)read、load、assign、use、store和write這6個(gè)操作保證變量的原子性操作。

    long和double這兩個(gè)64位長(zhǎng)度的數(shù)據(jù)類型java虛擬機(jī)并沒(méi)有強(qiáng)制規(guī)定他們的read、load、store和write操作的原子性,即所謂的非原子性協(xié)定,但是目前的各種商業(yè)java虛擬機(jī)都把long和double數(shù)據(jù)類型的4中非原子性協(xié)定操作實(shí)現(xiàn)為原子性。所以java中基本數(shù)據(jù)類型的訪問(wèn)讀寫(xiě)是原子性操作。

    對(duì)于大范圍的原子性保證需要通過(guò)lock和unlock操作以及synchronized同步塊來(lái)保證。

可見(jiàn)性

    可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程可以立即得知這個(gè)修改。
Java內(nèi)存模型是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值來(lái)實(shí)現(xiàn)可見(jiàn)性的。

Java中通過(guò)volatile、final和synchronized這三個(gè)關(guān)鍵字保證可見(jiàn)性:
  • volatile:通過(guò)刷新變量值確??梢?jiàn)性。
  • synchronized:同步塊通過(guò)變量lock鎖定前必須清空工作內(nèi)存中變量值,重新從主內(nèi)存中讀取變量值,unlock解鎖前必須把變量值同步回主內(nèi)存來(lái)確??梢?jiàn)性。
  • final:被final修飾的字段在構(gòu)造器中一旦被初始化完成,并且構(gòu)造器沒(méi)有把this引用傳遞進(jìn)去,那么在其他線程中就能看見(jiàn)final字段的值,無(wú)需同步就可以被其他線程正確訪問(wèn)。

有序性

    線程的有序性是指:在線程內(nèi)部,所有的操作都是有序執(zhí)行的,而在線程之間,因?yàn)楣ぷ鲀?nèi)存和主內(nèi)存同步的延遲,操作是亂序執(zhí)行的。

    Java通過(guò)volatile和synchronized關(guān)鍵字確保線程之間操作的有序性。
  • volatile禁止指令重排序優(yōu)化實(shí)現(xiàn)有序性。
  • synchronized通過(guò)一個(gè)變量在同一時(shí)刻只允許一個(gè)線程對(duì)其進(jìn)行l(wèi)ock鎖定操作來(lái)確保有序性。

三、java線程的實(shí)現(xiàn)方式

線程實(shí)現(xiàn)的三種方式

內(nèi)核線程(Kernal thread)
   
    內(nèi)核線程(Kernel Thread, KLT)就是直接由操作系統(tǒng)內(nèi)核支持的線程,這種線程由內(nèi)核來(lái)完成線程切換,內(nèi)核通過(guò)操作調(diào)度器對(duì)線程進(jìn)行調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上。程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Light Weight Process,LWP),輕量級(jí)進(jìn)程就是我們通常意義上所講的線程,由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持,因此只有先支持內(nèi)核線程,才能有輕量級(jí)進(jìn)程。這種輕量級(jí)進(jìn)程與內(nèi)核線程之間1:1的關(guān)系稱為一對(duì)一的線程模型。輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的??臻g),而且系統(tǒng)調(diào)用的代價(jià)相對(duì)較高,因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的。

輕量級(jí)用戶進(jìn)程(Light weight process)

    廣義上來(lái)講,一個(gè)線程只要不是內(nèi)核線程,那就可以認(rèn)為是用戶線程(User Thread,UT),而狹義的用戶線程指的是完全建立在用戶空間的線程庫(kù)上,系統(tǒng)內(nèi)核不能感知到線程存在的實(shí)現(xiàn),用戶線程的建立、同步、銷毀和調(diào)度完全在用戶態(tài)中完成,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng),這種線程不需要切換到內(nèi)核態(tài),因此操作可以是非??焖偾业拖牡?,也可以支持規(guī)模更大的線程數(shù)量,部分高性能數(shù)據(jù)庫(kù)中的多線程就是由用戶線程實(shí)現(xiàn)的。這種進(jìn)程與用戶線程之間1:N的關(guān)系稱為一對(duì)多的線程模型。(Windows和Linux使用的是這種方式)

    使用用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核的支援,劣勢(shì)在于沒(méi)有系統(tǒng)內(nèi)核的支援,所有的線程操作都需要用戶程序自己處理,因而使用用戶線程實(shí)現(xiàn)的程序一般都比較復(fù)雜,現(xiàn)在使用用戶線程的程序越來(lái)越少了。

用戶線程/混合線程(User thread)

    既存在用戶線程,又存在輕量級(jí)進(jìn)程。用戶線程還是完全建立在用戶空間中,而操作系統(tǒng)所支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁。這種混合模式下,用戶線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,是M:N的關(guān)系。許多Unix系列的系統(tǒng),都提供了M:N的線程模型實(shí)現(xiàn)。

java線程調(diào)度

    Java線程在JDK1.2之前,是基于名為“綠色線程”的用戶線程實(shí)現(xiàn)的,而在JDK1.2中,線程模型被替換為基于操作系統(tǒng)原生線程模型來(lái)實(shí)現(xiàn)。因此,在目前的JDK版本中,操作系統(tǒng)支持怎樣的線程模型,在很大程度上就決定了Java虛擬機(jī)的線程是怎樣映射的,這點(diǎn)在不同的平臺(tái)上沒(méi)有辦法達(dá)成一致,虛擬機(jī)規(guī)范中也未限定Java線程需要使用哪種線程模型來(lái)實(shí)現(xiàn)。

線程調(diào)度有兩種方式

協(xié)同式:線程的執(zhí)行時(shí)間由線程本身來(lái)控制,線程任務(wù)執(zhí)行完成之后主動(dòng)通知系統(tǒng)切換到另一個(gè)線程去執(zhí)行。(不推薦
    優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,線程切換操作對(duì)線程本身是可知的,不存在線程同步問(wèn)題。
    缺點(diǎn):線程執(zhí)行時(shí)間不可控制,如果線程長(zhǎng)時(shí)間執(zhí)行不讓出CPU執(zhí)行時(shí)間可能導(dǎo)致系統(tǒng)崩潰。

搶占式:每個(gè)線程的執(zhí)行時(shí)間有操作系統(tǒng)來(lái)分配,操作系統(tǒng)給每個(gè)線程分配執(zhí)行的時(shí)間片,搶到時(shí)間片的線程執(zhí)行,時(shí)間片用完之后重新?lián)屨紙?zhí)行時(shí)間,線程的切換不由線程本身來(lái)決定(Java使用的線程調(diào)度方式就是搶占式調(diào)度)。
    優(yōu)點(diǎn):線程執(zhí)行時(shí)間可控制,不會(huì)因?yàn)橐粋€(gè)線程阻塞問(wèn)題導(dǎo)致系統(tǒng)崩潰。

四、Java中線程狀態(tài)的調(diào)度關(guān)系



五、java中的線程安全等級(jí)

不可變

    可以是基本類型的final;可以是final對(duì)象,但對(duì)象的行為不會(huì)對(duì)其狀態(tài)產(chǎn)生任何影響,比如String的subString就是new一個(gè)String對(duì)象各種Number類型如BigInteger和BigDecimal等大數(shù)據(jù)類型都是不可變的,但是同為Number子類型的AtomicInteger和AtomicLong則并非不可變。原因與它里面狀態(tài)對(duì)象是unsafe對(duì)象有關(guān),所做的操作都是CAS操作,可以保證原子性。

絕對(duì)線程安全

    不管運(yùn)行時(shí)環(huán)境如何,調(diào)用者都不需要任何額外的同步措施。

相對(duì)線程安全

    這是我們通常意義上的線程安全。需要保證對(duì)象單獨(dú)的操作是線程安全的。比如Vector,HashTable,synchronizedCollection包裝集合等。

線程兼容

    對(duì)象本身不是線程安全的,但可以通過(guò)同步手段實(shí)現(xiàn)。一般我們說(shuō)的不是線程安全的,絕大多數(shù)是指這個(gè)。比如ArrayList,HashMap等。

線程對(duì)立

    不管調(diào)用端是否采用了同步的措施,都無(wú)法在并發(fā)中使用的代碼。

六、線程安全的實(shí)現(xiàn)方式

互斥同步

    在多線程訪問(wèn)的時(shí)候,保證同一時(shí)間只有一條線程使用。
    臨界區(qū)(Critical Section),互斥量(Mutex),信號(hào)量(Semaphore)都是同步的一種手段
     java里最基本的互斥同步手段是synchronized,編譯之后會(huì)形成monitorenter和monitorexit這兩個(gè)字節(jié)碼指令,這兩個(gè)字節(jié)碼都需要一個(gè)reference類型的參數(shù)來(lái)指明要鎖定和解鎖的對(duì)象,還有一個(gè)鎖的計(jì)數(shù)器,來(lái)記錄加鎖的次數(shù),加鎖幾次就要同樣解鎖幾次才能恢復(fù)到無(wú)鎖狀態(tài)。

     其實(shí)在“Java與線程”里已經(jīng)提到,java的線程是映射到操作系統(tǒng)的原生線程之上的,不管阻塞還是喚醒都需要操作系統(tǒng)的幫忙完成,都需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這是很耗費(fèi)時(shí)間的,是java語(yǔ)言中的一個(gè)重量級(jí)(Heavyweight)操作,雖然虛擬機(jī)本身會(huì)做一點(diǎn)優(yōu)化的操作,比如通知操作系統(tǒng)阻塞之前會(huì)加一段自旋等待的過(guò)程,避免頻繁切換到核心態(tài)。

ReentrantLock相比于synchronized的優(yōu)勢(shì):
  • 等待可中斷:在持有鎖的線程長(zhǎng)時(shí)間不釋放鎖的時(shí)候,等待的線程可以選擇放棄等待.
  • 公平鎖:按照申請(qǐng)鎖的順序來(lái)一次獲得鎖稱為公平鎖.synchronized的是非公平鎖,ReentrantLock可以通過(guò)構(gòu)造函數(shù)實(shí)現(xiàn)公平鎖.    new RenentrantLock(boolean fair)
  • 鎖綁定多個(gè)條件:通過(guò)多次newCondition可以獲得多個(gè)Condition對(duì)象,可以簡(jiǎn)單的實(shí)現(xiàn)比較復(fù)雜的線程同步的功能.通過(guò)await(),signal();

非阻塞同步

    互斥和同步最主要的問(wèn)題就是阻塞和喚醒所帶來(lái)的性能問(wèn)題,所以這通常叫阻塞同步(悲觀的并發(fā)策略)。隨著硬件指令集的發(fā)展,我們有另外的選擇:基于沖突檢測(cè)的樂(lè)觀并發(fā)策略,通俗講就是先操作,如果沒(méi)有其他線程爭(zhēng)用共享的數(shù)據(jù),操作就成功,如果有,則進(jìn)行其他的補(bǔ)償(最常見(jiàn)就是不斷的重試),這種樂(lè)觀的并發(fā)策略許多實(shí)現(xiàn)都不需要把線程掛起,這種同步操作被稱為非阻塞同步。

這類的指令有:
    1)測(cè)試并設(shè)置(test-and-set)
    2)獲取并增加
    3)交換
    4)比較并交換(CAS)
    5)加載鏈接/條件儲(chǔ)存(Load-Linked/Store-Conditional  LL/SC)

    后面兩條是現(xiàn)代處理器新增的處理器指令,在JDK1.5之后,java中才可以使用CAS操作,就是傳說(shuō)中的sun.misc.Unsafe類里面的compareAndSwapInt()和compareAndSwapLong()等幾個(gè)方法的包裝提供,虛擬機(jī)對(duì)這些方法做了特殊的處理,及時(shí)編譯出來(lái)的結(jié)果就是一條平臺(tái)相關(guān)的處理器CAS指令,沒(méi)有方法調(diào)用的過(guò)程,可以認(rèn)為是無(wú)條件的內(nèi)聯(lián)進(jìn)去。

    原來(lái)需要對(duì)i++進(jìn)行同步,但現(xiàn)在有了這種CAS操作來(lái)保證原子性,比如用AtomicInteger。 但是CAS存在一個(gè)ABA的問(wèn)題??梢酝ㄟ^(guò)AtomicStampedReference來(lái)解決(雞肋)。

無(wú)同步

    有一些代碼天生就是線程安全的,不需要同步。其中有如下兩類:

    可重入代碼(Reentrant Code):純代碼,具有不依賴存儲(chǔ)在堆上的數(shù)據(jù)和公用的系統(tǒng)資源,用到的狀態(tài)量都由參數(shù)中傳入,不調(diào)用非可重入的方法等特征,它的返回結(jié)果是可以預(yù)測(cè)的。

    線程本地存儲(chǔ)(Thread Local Storage):把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一個(gè)線程之內(nèi),這樣就無(wú)須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭(zhēng)用問(wèn)題。可以通過(guò)java.lang.ThreadLocal類來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)的功能。

七、java中的鎖機(jī)制

悲觀鎖

    假定會(huì)發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。悲觀鎖假定其他線程企圖訪問(wèn)或者改變你正在訪問(wèn)、更改的對(duì)象的概率是很高的,因此在悲觀鎖的環(huán)境中,在你開(kāi)始改變此對(duì)象之前就將該對(duì)象鎖住,并且直到你提交了所作的更改之后才釋放鎖。

樂(lè)觀鎖

    假設(shè)不會(huì)發(fā)生并發(fā)沖突。輕易不加鎖。

自旋鎖與自適應(yīng)自旋

    線程掛起和恢復(fù)的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性能帶來(lái)了很大的壓力,在許多應(yīng)用中,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,為了這段時(shí)間去掛起和恢復(fù)線程并不值得,可以讓后請(qǐng)求鎖的線程等待一會(huì)兒,但不放棄處理器的執(zhí)行時(shí)間,讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)。

    自旋鎖默認(rèn)的自旋次數(shù)值是10次,可以使用參數(shù)-XX:PreBlockSpin更改。

    自適應(yīng)自旋意味著自旋的時(shí)間不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。

鎖清除

    虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。鎖消除的主要判定依據(jù)來(lái)源于逃逸分析的數(shù)據(jù)支持。

鎖粗化

    如果虛擬機(jī)探測(cè)到有一系列連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。

鎖升級(jí)

    Java SE1.6為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”,所以在Java SE1.6里鎖一共有四種狀態(tài),無(wú)鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略,目的是為了提高獲得鎖和釋放鎖的效率。



偏向鎖

    Hotspot的作者經(jīng)過(guò)以往的研究發(fā)現(xiàn)大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。當(dāng)一個(gè)線程訪問(wèn)同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要花費(fèi)CAS操作來(lái)加鎖和解鎖,而只需簡(jiǎn)單的測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖,如果測(cè)試成功,表示線程已經(jīng)獲得了鎖,如果測(cè)試失敗,則需要再測(cè)試下Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1(表示當(dāng)前是偏向鎖),如果沒(méi)有設(shè)置,則使用CAS競(jìng)爭(zhēng)鎖,如果設(shè)置了,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前線程。

    偏向鎖的撤銷:偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動(dòng)狀態(tài),則將對(duì)象頭設(shè)置成無(wú)鎖狀態(tài),如果線程仍然活著,擁有偏向鎖的棧會(huì)被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對(duì)象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無(wú)鎖或者標(biāo)記對(duì)象不適合作為偏向鎖,最后喚醒暫停的線程。下圖中的線程1演示了偏向鎖初始化的流程,線程2演示了偏向鎖撤銷的流程。



關(guān)閉偏向鎖:偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來(lái)關(guān)閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò)JVM參數(shù)關(guān)閉偏向鎖-XX:-UseBiasedLocking=false,那么默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。

輕量級(jí)鎖

    輕量級(jí)鎖加鎖:線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競(jìng)爭(zhēng)鎖,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖。

    輕量級(jí)鎖解鎖:輕量級(jí)解鎖時(shí),會(huì)使用原子的CAS操作來(lái)將Displaced Mark Word替換回到對(duì)象頭,如果成功,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生。如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖就會(huì)膨脹成重量級(jí)鎖。下圖是兩個(gè)線程同時(shí)爭(zhēng)奪鎖,導(dǎo)致鎖膨脹的流程圖。



    因?yàn)樽孕龝?huì)消耗CPU,為了避免無(wú)用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級(jí)成重量級(jí)鎖,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線程試圖獲取鎖時(shí),都會(huì)被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會(huì)喚醒這些線程,被喚醒的線程就會(huì)進(jìn)行新一輪的奪鎖之爭(zhēng)。

重量級(jí)鎖

    重量鎖在JVM中又叫對(duì)象監(jiān)視器(Monitor),它至少包含一個(gè)競(jìng)爭(zhēng)鎖的隊(duì)列,和一個(gè)信號(hào)阻塞隊(duì)列(wait隊(duì)列),前者負(fù)責(zé)做互斥,后一個(gè)用于做線程同步。 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多