參考鏈接:http://smallbug-vip.iteye.com/blog/2275743
在多線程開(kāi)發(fā)的過(guò)程中,開(kāi)發(fā)人員經(jīng)常面臨共享資源只能被一個(gè)線程訪問(wèn)的情況,這個(gè)時(shí)候,需要對(duì)這個(gè)共享的資源加上一把鎖!
java中用synchronized關(guān)鍵字來(lái)表示鎖,可用來(lái)給對(duì)象和方法或者代碼塊加鎖,當(dāng)它鎖定一個(gè)方法或者一個(gè)代碼塊的時(shí)候,同一時(shí)刻最多只有一個(gè)線程執(zhí)行這段代碼。當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的這個(gè)加鎖同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。然而,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)加鎖代碼塊時(shí),另一個(gè)線程仍然可以訪問(wèn)該object中的非加鎖代碼塊。
在詳細(xì)介紹之前,說(shuō)明一下幾個(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,也稱(chēng)為監(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í)被稱(chēng)為信號(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,否則什么都不做。可參考java1.5之后的concurrent包中的源碼,更詳細(xì)資料:http://zl198751.iteye.com/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ù)的操作的正確性。可以使用在自己狀態(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ù)類(lèi)型java虛擬機(jī)并沒(méi)有強(qiáng)制規(guī)定他們的read、load、store和write操作的原子性,即所謂的非原子性協(xié)定,但是目前的各種商業(yè)java虛擬機(jī)都把long和double數(shù)據(jù)類(lèi)型的4中非原子性協(xié)定操作實(shí)現(xiàn)為原子性。所以java中基本數(shù)據(jù)類(lèi)型的訪問(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)系稱(chēng)為一對(duì)一的線程模型。輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的棧空間),而且系統(tǒng)調(diào)用的代價(jià)相對(duì)較高,因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的。
輕量級(jí)用戶(hù)進(jìn)程(Light weight process)
廣義上來(lái)講,一個(gè)線程只要不是內(nèi)核線程,那就可以認(rèn)為是用戶(hù)線程(User
Thread,UT),而狹義的用戶(hù)線程指的是完全建立在用戶(hù)空間的線程庫(kù)上,系統(tǒng)內(nèi)核不能感知到線程存在的實(shí)現(xiàn),用戶(hù)線程的建立、同步、銷(xiāo)毀和調(diào)度完全在用戶(hù)態(tài)中完成,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng),這種線程不需要切換到內(nèi)核態(tài),因此操作可以是非常快速且低消耗的,也可以支持規(guī)模更大的線程數(shù)量,部分高性能數(shù)據(jù)庫(kù)中的多線程就是由用戶(hù)線程實(shí)現(xiàn)的。這種進(jìn)程與用戶(hù)線程之間1:N的關(guān)系稱(chēng)為一對(duì)多的線程模型。(Windows和Linux使用的是這種方式)
使用用戶(hù)線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核的支援,劣勢(shì)在于沒(méi)有系統(tǒng)內(nèi)核的支援,所有的線程操作都需要用戶(hù)程序自己處理,因而使用用戶(hù)線程實(shí)現(xiàn)的程序一般都比較復(fù)雜,現(xiàn)在使用用戶(hù)線程的程序越來(lái)越少了。
用戶(hù)線程/混合線程(User thread)
既存在用戶(hù)線程,又存在輕量級(jí)進(jìn)程。用戶(hù)線程還是完全建立在用戶(hù)空間中,而操作系統(tǒng)所支持的輕量級(jí)進(jìn)程則作為用戶(hù)線程和內(nèi)核線程之間的橋梁。這種混合模式下,用戶(hù)線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,是M:N的關(guān)系。許多Unix系列的系統(tǒng),都提供了M:N的線程模型實(shí)現(xiàn)。
java線程調(diào)度
Java線程在JDK1.2之前,是基于名為“綠色線程”的用戶(hù)線程實(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中的線程安全等級(jí)
不可變:
可以是基本類(lèi)型的final;可以是final對(duì)象,但對(duì)象的行為不會(huì)對(duì)其狀態(tài)產(chǎn)生任何影響,比如String的subString就是new一個(gè)String對(duì)象各種Number類(lèi)型如BigInteger和BigDecimal等大數(shù)據(jù)類(lèi)型都是不可變的,但是同為Number子類(lèi)型的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類(lèi)型的參數(shù)來(lái)指明要鎖定和解鎖的對(duì)象,還有一個(gè)鎖的計(jì)數(shù)器,來(lái)記錄加鎖的次數(shù),加鎖幾次就要同樣解鎖幾次才能恢復(fù)到無(wú)鎖狀態(tài)。
其實(shí)在“Java與線程”里已經(jīng)提到,java的線程是映射到操作系統(tǒng)的原生線程之上的,不管阻塞還是喚醒都需要操作系統(tǒng)的幫忙完成,都需要從用戶(hù)態(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)一次獲得鎖稱(chēng)為公平鎖.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)都不需要把線程掛起,這種同步操作被稱(chēng)為非阻塞同步。
這類(lèi)的指令有:
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類(lèi)里面的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ú)同步
有一些代碼天生就是線程安全的,不需要同步。其中有如下兩類(lèi):
可重入代碼(Reentrant Code):純代碼,具有不依賴(lài)存儲(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)來(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í)的策略,目的是為了提高獲得鎖和釋放鎖的效率。
importorg.junit.Test;
importjava.util.Date;
/**
* Created by kewei.zhang on 2017/6/20.
*/
public classTestSynchronized {
private static? finalPersonglobalP=newPerson();
static classPersonimplementsRunnable {
privateStringname;
private intage;
privateDatebirthday;
publicString getName() {
returnname;
}
public voidsetName(String name) {
this.name= name;
}
public intgetAge() {
returnage;
}
public voidsetAge(intage) {
this.age= age;
}
publicDate getBirthday() {
returnbirthday;
}
public voidsetBirthday(Date birthday) {
this.birthday= birthday;
}
@Override
publicString toString() {
return"Person{"+
"name='"+name+'\''+
", age="+age+
", birthday="+birthday+
'}';
}
@Override
public voidrun() {
Person localP =newPerson();
synchronized(localP){
System.out.println("localP為鎖 ThreadName = "+ Thread.currentThread().getName());
try{
Thread.sleep(3600);
System.out.println("localP為鎖 ThreadName = "+ Thread.currentThread().getName() +"休息1秒");
synchronized(globalP){
System.out.println("globalP為鎖 ThreadName = "+ Thread.currentThread().getName());
Thread.sleep(3600);
System.out.println("globalP為鎖 ThreadName = "+ Thread.currentThread().getName() +"休息1秒");
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
public static voidmain(String[] args) {
Person p =newPerson();
Thread t1 =newThread(p);
Thread t2 =newThread(p);
t1.start();
t2.start();
}
}
結(jié)果:
localP為鎖 ThreadName = Thread-1
localP為鎖 ThreadName = Thread-0休息1秒
globalP為鎖 ThreadName = Thread-0
localP為鎖 ThreadName = Thread-1休息1秒
globalP為鎖 ThreadName = Thread-0休息1秒
globalP為鎖 ThreadName = Thread-1
globalP為鎖 ThreadName = Thread-1休息1秒
如上原因,是因?yàn)閘ocalP對(duì)象在每個(gè)線程進(jìn)去之后,都會(huì)創(chuàng)建一個(gè)新的對(duì)象,globalP是t1和t2共享的對(duì)象,所以,t1,t2會(huì)同時(shí)進(jìn)入第一個(gè)synchronized代碼塊,不會(huì)同時(shí)進(jìn)入第二個(gè)synchronized修飾的代碼塊,這就是鎖中同一個(gè)對(duì)象訪問(wèn)共享資源,才會(huì)有鎖的效果。
在java1.5之后,引入concurrent包,該包中引入一種新的鎖機(jī)制Lock,Lock和synchronized的不同之處在于:
ReentrantLock相比于synchronized的優(yōu)勢(shì):
等待可中斷:在持有鎖的線程長(zhǎng)時(shí)間不釋放鎖的時(shí)候,等待的線程可以選擇放棄等待.
公平鎖:按照申請(qǐng)鎖的順序來(lái)獲得鎖稱(chēng)為公平鎖.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();