如果多個(gè)變量間的變化不是彼此獨立的,某個(gè)值的改變會(huì )制約其他幾個(gè)變量,因此,更新一個(gè)變量的時(shí)候,要在同一個(gè)原子操作中更新其他幾個(gè)。為了保護狀態(tài)的一致性,要在單一的原子操作中更新相互關(guān)聯(lián)的狀態(tài)變量。
Java提供了強制原子性的內置鎖機制,Synchronized塊:一個(gè)塊有兩部分,鎖對象的引用,以及這個(gè)鎖保護的代碼塊。同一時(shí)間,只有一個(gè)線(xiàn)程可以運行特定鎖保護的代碼塊,因此由同一個(gè)鎖保護的synchronized塊會(huì )各自原子地執行,不會(huì )相互干擾-----確保一組語(yǔ)句(statements)作為單獨的,不可分割的單元。
可重進(jìn)入
當一個(gè)線(xiàn)程請求其他線(xiàn)程已經(jīng)占有的鎖時(shí),請求線(xiàn)程被阻塞。然后內部鎖,對于同一個(gè)線(xiàn)程的代碼,內部鎖具有可重進(jìn)入,因此線(xiàn)程在試圖獲得它自己占有的鎖時(shí),請求會(huì )成功。重進(jìn)入方便了鎖行為的封裝,簡(jiǎn)化了開(kāi)發(fā)。假設有A、B兩個(gè)類(lèi),A繼承B,分別有a、b方法,都加了synchronized關(guān)鍵字,那么在a調用b的時(shí)候,,都會(huì )試圖獲得鎖,如果沒(méi)有重進(jìn)入,那么有一個(gè)鎖就用于無(wú)法得到,一直處于等待狀態(tài)。
大家都知道可以用鎖來(lái)解決并發(fā)問(wèn)題,但在具體使用上還有很多講究,比如:
· 每個(gè)共享的可變變量都需要由一個(gè)個(gè)確定的鎖保護。
· 一旦使用了鎖,就意味著(zhù)這段代碼的執行就喪失了操作系統多道程序的特性,會(huì )在一定程度上影響性能
· 鎖不能解決在分布式環(huán)境共享變量的并發(fā)問(wèn)題
JDK1.5以后的Lock是鎖的一個(gè)抽象,它允許把鎖定的實(shí)現作為Java類(lèi),而不是語(yǔ)言的特性來(lái)實(shí)現。就為Lock的多種實(shí)現留下了空間,各種實(shí)現可以有不同的調度算法,性能特性或者鎖定語(yǔ)義。
Synchronized確保了共享變量的原子性和可見(jiàn)性,它能夠實(shí)現同步,但是,它也有一些限制:無(wú)法中斷一個(gè)正在等候獲得鎖的線(xiàn)程,也無(wú)法通過(guò)投票得到鎖。
ReentrantLock類(lèi)實(shí)現了Lock,它擁有與synchronized相同的并發(fā)性和內存語(yǔ)義,但是他添加了類(lèi)似鎖投票,定時(shí)鎖等候和可中斷鎖等候的一些特性。不過(guò)它在使用的時(shí)候必須在finally中unLock,另外JVM用synchronized管理鎖定請求和釋放時(shí),JVM在生成線(xiàn)程轉存儲時(shí)能夠包括鎖定信息,這樣對調試很有用。Lock只是普通的類(lèi),JVM不知道具體哪個(gè)線(xiàn)程擁有Lock對象。
所以在大多數情況下,使用synchronizeed更好,除非確實(shí)需要synchronized沒(méi)有的特性,比如時(shí)間鎖等候,可中斷鎖等候、無(wú)塊結構鎖、和鎖投票等。

ReentrantLock構造器的一個(gè)參數是boolean值,它允許選擇一個(gè)公平(fair)鎖,還是一個(gè)不公平鎖。公平鎖使線(xiàn)程按照請求順序依次獲得鎖,不公平鎖并不是按順序來(lái)的。CPU的線(xiàn)程調度本來(lái)就是不公平的,JVM保證了所有線(xiàn)程都會(huì )得到他們所等候的鎖,大多數情況下,確保所得公平性的成本非常高,比synchronized高的多,所以默認情況下,使用false的參數就可以了。
維護了一對相關(guān)的鎖,一個(gè)用于只讀操作,另一個(gè)用于寫(xiě)入操作,只要沒(méi)有writer,讀取鎖可以由多個(gè)reader線(xiàn)程同時(shí)保持。寫(xiě)入鎖是獨占地。
與互斥鎖相比,讀-寫(xiě)鎖允許對共享數據進(jìn)行更高級別的訪(fǎng)問(wèn)。雖然依次只有一個(gè)線(xiàn)程可以修改共享數據,但在許多情況下,任何數量的線(xiàn)程可以同時(shí)讀取共享數據。
例如,某個(gè)數據填充后,不經(jīng)常對其進(jìn)行修改,因為不經(jīng)常對其進(jìn)行修改,所以這種情況,用讀-寫(xiě)鎖筆記哦合適。
在java中確保共享變量線(xiàn)程安全的傳統方式是使用同步,同步可以確定訪(fǎng)問(wèn)一組變量的所有線(xiàn)程都將擁有對這些變量的獨占訪(fǎng)問(wèn)權(原子性),并且其他線(xiàn)程獲得該鎖定時(shí),將可以看到對這些變量的更改(可見(jiàn)性)。但是鎖的代價(jià)太昂貴,特別是在競爭很厲害的時(shí)候,影響吞吐量。
支持并發(fā)的第一個(gè)處理器提供原子的測試并設置操作,通常在單位上運行這項操作?,F在的處理器(包括 Intel 和 Sparc 處理器)使用的最通用的方法是實(shí)現名為 比較并轉換或 CAS 的原語(yǔ)。CAS 操作包含三個(gè)操作數 —— 內存位置(V)、預期原值(A)和新值(B)
CAS 原理:
我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現在的值即可。
基于 CAS 的并發(fā)算法稱(chēng)為 無(wú)鎖定算法,因為線(xiàn)程不必再等待鎖定(有時(shí)稱(chēng)為互斥或關(guān)鍵部分,這取決于線(xiàn)程平臺的術(shù)語(yǔ))。無(wú)論 CAS 操作成功還是失敗,在任何一種情況中,它都在可預知的時(shí)間內完成。如果 CAS 失敗,調用者可以重試 CAS 操作或采取其他適合的操作。
個(gè)線(xiàn)程的失敗或掛起不應該影響其他線(xiàn)程的失敗或掛起.這類(lèi)算法稱(chēng)之為非阻塞(nonblocking)算法。
對比阻塞算法:
如果有一類(lèi)并發(fā)操作, 其中一個(gè)線(xiàn)程優(yōu)先得到對象監視器的鎖, 當其他線(xiàn)程到達同步邊界時(shí), 就會(huì )被阻塞.直到前一 個(gè)線(xiàn)程釋放掉鎖后, 才可以繼續競爭對象鎖.(當然,這里的競爭也可是公平的, 按先來(lái)后到的次序)
JDK 5.0之后,在 java.util.concurrent.atomic 包中添加原子變量類(lèi)之后,這種情況才發(fā)生了改變。所有原子變量類(lèi)都公開(kāi)比較并設置原語(yǔ)(與比較并交換類(lèi)似),這些原語(yǔ)都是使用平臺上可用的最快本機結構(比較并交換、加載鏈接/條件存儲,最壞的情況下是旋轉鎖)來(lái)實(shí)現的。 java.util.concurrent.atomic 包中提供了原子變量的 9 種風(fēng)格。

假設, 第一次讀取V地址的A值, 然后通過(guò)CAS來(lái)判斷V地址的值是否仍舊為A, 如果是, 就將B的值寫(xiě)入V地址,覆蓋A值.
但是, 語(yǔ)義上, 有一個(gè)漏洞, 當第一次讀取V的A值, 此時(shí), 內存V的值變?yōu)?/font>B值, 然后在未執行CAS前, 又變回了A值.
此時(shí), CAS再執行時(shí), 會(huì )判斷其正確的, 并進(jìn)行賦值.
這種判斷值的方式來(lái)斷定內存是否被修改過(guò), 針對某些問(wèn)題, 是不適用的.為了解決這種問(wèn)題, jdk 1.5并發(fā)包提供了AtomicStampedReference(有標記的原子引用)類(lèi), 通過(guò)控制變量值的版本來(lái)保證CAS正確性.
其實(shí), 大部分通過(guò)值的變化來(lái)CAS, 已經(jīng)夠用了.
聯(lián)系客服