1 鎖
如果多個(gè)變量間的變化不是彼此獨(dú)立的,某個(gè)值的改變會(huì)制約其他幾個(gè)變量,因此,更新一個(gè)變量的時(shí)候,要在同一個(gè)原子操作中更新其他幾個(gè)。為了保護(hù)狀態(tài)的一致性,要在單一的原子操作中更新相互關(guān)聯(lián)的狀態(tài)變量。 1.1 Synchronized
Java提供了強(qiáng)制原子性的內(nèi)置鎖機(jī)制,Synchronized塊:一個(gè)塊有兩部分,鎖對(duì)象的引用,以及這個(gè)鎖保護(hù)的代碼塊。同一時(shí)間,只有一個(gè)線程可以運(yùn)行特定鎖保護(hù)的代碼塊,因此由同一個(gè)鎖保護(hù)的synchronized塊會(huì)各自原子地執(zhí)行,不會(huì)相互干擾-----確保一組語句(statements)作為單獨(dú)的,不可分割的單元。 可重進(jìn)入 當(dāng)一個(gè)線程請(qǐng)求其他線程已經(jīng)占有的鎖時(shí),請(qǐng)求線程被阻塞。然后內(nèi)部鎖,對(duì)于同一個(gè)線程的代碼,內(nèi)部鎖具有可重進(jìn)入,因此線程在試圖獲得它自己占有的鎖時(shí),請(qǐng)求會(huì)成功。重進(jìn)入方便了鎖行為的封裝,簡化了開發(fā)。假設(shè)有A、B兩個(gè)類,A繼承B,分別有a、b方法,都加了synchronized關(guān)鍵字,那么在a調(diào)用b的時(shí)候,,都會(huì)試圖獲得鎖,如果沒有重進(jìn)入,那么有一個(gè)鎖就用于無法得到,一直處于等待狀態(tài)。 大家都知道可以用鎖來解決并發(fā)問題,但在具體使用上還有很多講究,比如: · 每個(gè)共享的可變變量都需要由一個(gè)個(gè)確定的鎖保護(hù)。 · 一旦使用了鎖,就意味著這段代碼的執(zhí)行就喪失了操作系統(tǒng)多道程序的特性,會(huì)在一定程度上影響性能 · 鎖不能解決在分布式環(huán)境共享變量的并發(fā)問題 1.2 Lock
JDK1.5以后的Lock是鎖的一個(gè)抽象,它允許把鎖定的實(shí)現(xiàn)作為Java類,而不是語言的特性來實(shí)現(xiàn)。就為Lock的多種實(shí)現(xiàn)留下了空間,各種實(shí)現(xiàn)可以有不同的調(diào)度算法,性能特性或者鎖定語義。 Synchronized確保了共享變量的原子性和可見性,它能夠?qū)崿F(xiàn)同步,但是,它也有一些限制:無法中斷一個(gè)正在等候獲得鎖的線程,也無法通過投票得到鎖。 1.3 ReentrantLock,可重入鎖
ReentrantLock類實(shí)現(xiàn)了Lock,它擁有與synchronized相同的并發(fā)性和內(nèi)存語義,但是他添加了類似鎖投票,定時(shí)鎖等候和可中斷鎖等候的一些特性。不過它在使用的時(shí)候必須在finally中unLock,另外JVM用synchronized管理鎖定請(qǐng)求和釋放時(shí),JVM在生成線程轉(zhuǎn)存儲(chǔ)時(shí)能夠包括鎖定信息,這樣對(duì)調(diào)試很有用。Lock只是普通的類,JVM不知道具體哪個(gè)線程擁有Lock對(duì)象。 所以在大多數(shù)情況下,使用synchronizeed更好,除非確實(shí)需要synchronized沒有的特性,比如時(shí)間鎖等候,可中斷鎖等候、無塊結(jié)構(gòu)鎖、和鎖投票等。 ReentrantLock構(gòu)造器的一個(gè)參數(shù)是boolean值,它允許選擇一個(gè)公平(fair)鎖,還是一個(gè)不公平鎖。公平鎖使線程按照請(qǐng)求順序依次獲得鎖,不公平鎖并不是按順序來的。CPU的線程調(diào)度本來就是不公平的,JVM保證了所有線程都會(huì)得到他們所等候的鎖,大多數(shù)情況下,確保所得公平性的成本非常高,比synchronized高的多,所以默認(rèn)情況下,使用false的參數(shù)就可以了。 1.4 ReadWriteLock
維護(hù)了一對(duì)相關(guān)的鎖,一個(gè)用于只讀操作,另一個(gè)用于寫入操作,只要沒有writer,讀取鎖可以由多個(gè)reader線程同時(shí)保持。寫入鎖是獨(dú)占地。 與互斥鎖相比,讀-寫鎖允許對(duì)共享數(shù)據(jù)進(jìn)行更高級(jí)別的訪問。雖然依次只有一個(gè)線程可以修改共享數(shù)據(jù),但在許多情況下,任何數(shù)量的線程可以同時(shí)讀取共享數(shù)據(jù)。 例如,某個(gè)數(shù)據(jù)填充后,不經(jīng)常對(duì)其進(jìn)行修改,因?yàn)椴唤?jīng)常對(duì)其進(jìn)行修改,所以這種情況,用讀-寫鎖筆記哦合適。 2 并發(fā)容器類3 原子變量類與CAS
在java中確保共享變量線程安全的傳統(tǒng)方式是使用同步,同步可以確定訪問一組變量的所有線程都將擁有對(duì)這些變量的獨(dú)占訪問權(quán)(原子性),并且其他線程獲得該鎖定時(shí),將可以看到對(duì)這些變量的更改(可見性)。但是鎖的代價(jià)太昂貴,特別是在競爭很厲害的時(shí)候,影響吞吐量。 3.1 CAS
支持并發(fā)的第一個(gè)處理器提供原子的測(cè)試并設(shè)置操作,通常在單位上運(yùn)行這項(xiàng)操作?,F(xiàn)在的處理器(包括 Intel 和 Sparc 處理器)使用的最通用的方法是實(shí)現(xiàn)名為 比較并轉(zhuǎn)換或 CAS 的原語。CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B) CAS 原理: 我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。 基于 CAS 的并發(fā)算法稱為 無鎖定算法,因?yàn)榫€程不必再等待鎖定(有時(shí)稱為互斥或關(guān)鍵部分,這取決于線程平臺(tái)的術(shù)語)。無論 CAS 操作成功還是失敗,在任何一種情況中,它都在可預(yù)知的時(shí)間內(nèi)完成。如果 CAS 失敗,調(diào)用者可以重試 CAS 操作或采取其他適合的操作。 3.2 非阻塞算法
個(gè)線程的失敗或掛起不應(yīng)該影響其他線程的失敗或掛起.這類算法稱之為非阻塞(nonblocking)算法。 對(duì)比阻塞算法: 3.3 原子類
JDK 5.0之后,在 java.util.concurrent.atomic 包中添加原子變量類之后,這種情況才發(fā)生了改變。所有原子變量類都公開比較并設(shè)置原語(與比較并交換類似),這些原語都是使用平臺(tái)上可用的最快本機(jī)結(jié)構(gòu)(比較并交換、加載鏈接/條件存儲(chǔ),最壞的情況下是旋轉(zhuǎn)鎖)來實(shí)現(xiàn)的。 java.util.concurrent.atomic 包中提供了原子變量的 9 種風(fēng)格。 3.4 ABA問題
假設(shè), 第一次讀取V地址的A值, 然后通過CAS來判斷V地址的值是否仍舊為A, 如果是, 就將B的值寫入V地址,覆蓋A值. 但是, 語義上, 有一個(gè)漏洞, 當(dāng)?shù)谝淮巫x取V的A值, 此時(shí), 內(nèi)存V的值變?yōu)?/font>B值, 然后在未執(zhí)行CAS前, 又變回了A值. 這種判斷值的方式來斷定內(nèi)存是否被修改過, 針對(duì)某些問題, 是不適用的.為了解決這種問題, jdk 1.5并發(fā)包提供了AtomicStampedReference(有標(biāo)記的原子引用)類, 通過控制變量值的版本來保證CAS正確性. 其實(shí), 大部分通過值的變化來CAS, 已經(jīng)夠用了. 4 Synchronizer 同步器
5 線程池
|
|