Java并發(fā)編程
來自Java并發(fā)編程的藝術(shù)
個人博客: http://blog.csdn.net/qq_22329521/article/details/52576788
并發(fā)一定比串行快么外恕?
這個問題肯定是錯的尽棕,并發(fā)比串行慢的原因在于:線程有創(chuàng)建和上下文切換的開銷
上下文切換
即使是單核處理器也支持多線程執(zhí)行代碼地啰,CPU通過給每個線程分配CPU時間片來實現(xiàn)這個機制莹菱。CPU通過時間片分配的算法來循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個時間片后會切換到下一個任務(wù)召庞。但是俩檬,在切換前會保持上一個任務(wù)的狀態(tài)窑业,以便下次切換回這個任務(wù)時愿汰,可以再加之這個任務(wù)的狀態(tài)困后。所以任務(wù)從保存到再加載的過程就是一次上下文切換。
如何減少上下文切換
--
- 無鎖并發(fā)編程:多線程競爭鎖衬廷,會引起上下文切換,所以多線程處理數(shù)據(jù)時汽绢,可以用一些辦法避免使用鎖吗跋,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)
- CAS算法:Java的Atomic包使用CAS算法來更新數(shù)據(jù)宁昭,而不需要加鎖跌宛。
- 使用最少線程:避免創(chuàng)建不需要的線程,比如任務(wù)很少积仗,但是創(chuàng)建了很多線程來處理疆拘,這樣會造成大量線程處于等待
- 協(xié)程:在單行線程中實現(xiàn)多任務(wù)調(diào)度,并在單線程中維持多個任務(wù)的切換
避免死鎖的幾種方式
- 避免一個線程同時獲取多個鎖
- 避免一個線程在鎖內(nèi)同時占用多個資源寂曹,盡量保證每個鎖只占用一個資源
- 嘗試使用定時鎖哎迄,使用lock.tryLock(timeout)來替代使用內(nèi)部鎖機制排拷。
- 對于數(shù)據(jù)庫鎖孵滞,加鎖和解鎖必須在一個數(shù)據(jù)庫連接中里,否則會出現(xiàn)解鎖失敗的情況弦牡。
資源限制
資源限制指的是程序的執(zhí)行速度受限于計算機硬件資源或軟件資源渺氧,如服務(wù)器的帶寬只有2Mb/s,某個資源的下載速度為1Mb/s,系統(tǒng)啟動10個線程去下載資源旨涝,下載速度不會變成10Mb/s,所以在進行并發(fā)的時候回考慮資源的限制侣背。硬件資源限制有帶寬的上傳/下載速度白华、硬盤的讀寫速度和CPU的處理速度。軟件資源限制有數(shù)據(jù)庫的連接數(shù)和socket連接數(shù)等贩耐。
資源限制引來的問題:為了將代碼執(zhí)行速度加快將代碼中串行執(zhí)行的部分變成并發(fā)執(zhí)行弧腥,因為資源受限,仍然在串行執(zhí)行憔杨,這時候程序不僅不會加快鸟赫,反而會變慢,因為增加了上下文切換和資源調(diào)度的時間。
如何解決資源限制問題:可以使用集群并行執(zhí)行程序抛蚤,既然單機的資源有限台谢,那么可以讓程序在多機上運行,比如使用ODPS岁经、Hadoop或者自己搭個服務(wù)器集群朋沮,不同的機器處理不同的數(shù)據(jù),可以通過“數(shù)據(jù)ID%機器數(shù)”缀壤,計算得到一個機器編號樊拓,然后由對應(yīng)編號的機器處理這個數(shù)據(jù),對于軟件資源受限塘慕,可以使用資源池來復(fù)用如使用連接池將數(shù)據(jù)庫和Socket連接復(fù)用筋夏,或者在調(diào)用對方webservice接口獲取數(shù)據(jù)只建立一個連接。
Java并發(fā)機制的底層實現(xiàn)原理
Java代碼在編譯后會變成Java字節(jié)碼图呢,字節(jié)碼被類加載器加載到JVM里条篷,JVM執(zhí)行字節(jié)碼,最終需要轉(zhuǎn)化為匯編指令在CPU上執(zhí)行蛤织,Java所使用的并發(fā)機制依賴于JVM的實現(xiàn)和CPU的指令
volatile的應(yīng)用
volatile是輕量級的synchronized赴叹,在多處理器并發(fā)中保證了共享變量的可見性,可見性是指當(dāng)一個線程修改了一個共享變量指蚜,另一個線程能讀到修改的值乞巧,它不會引起線程上下文切換和調(diào)度
volatile在java代碼轉(zhuǎn)換為匯編代碼 會多了一個Lock前綴的指令,在多核處理器下發(fā)生兩件事情
- 將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存
- 將這個協(xié)會內(nèi)存的操作會使得其他CPU力緩存了該內(nèi)存的地址的數(shù)據(jù)無效
為了提高處理速度摊鸡,處理器不直接和內(nèi)存通信绽媒,而是將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進行操作柱宦,但操作完不知道何時回寫到內(nèi)存些椒,如果聲明了volatile的變量進行寫操作,JVM就會向處理器發(fā)送一條Lock前綴的指令掸刊,將這個變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存免糕,但是就是寫會內(nèi)存,如果其他處理器緩存的值還是舊的忧侧,再執(zhí)行計算操作就會有問題石窑,所以在多處理器下為了保證各個處理器的緩存是一致,就會執(zhí)行緩存一致性協(xié)議蚓炬,每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己的緩存值是否過期松逊,當(dāng)處理器發(fā)現(xiàn)自己緩存行所對應(yīng)的內(nèi)存地址被修改,就會將當(dāng)前處理器緩存行設(shè)置為無效肯夏,當(dāng)處理器對數(shù)據(jù)進行修改操作经宏,會重新從系統(tǒng)內(nèi)存讀到處理器緩存中
synchronized的和應(yīng)用
javase1.6 對synchronized進行各種優(yōu)化犀暑,過去被人稱為重量級鎖。
java每個對象都是鎖
- 對于普通同步方法烁兰,鎖就是實例對象
- 對于靜態(tài)同步方法耐亏,鎖就是當(dāng)前類的Class對象
- 對于同步代碼塊,鎖就是Synchonized括號里配置的對象
JVM基于進入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步
synchronized用的鎖是存在Java對象頭里的沪斟。Java對象頭里的Mark Word力默認儲存對象的HashCode广辰,分代年齡和鎖標記位。在運行期間主之,MarkWord儲存的數(shù)據(jù)會隨著鎖標記位的變化而變化
Javase1.6中 鎖一共有4種狀態(tài)择吊,級別從低到高為:無鎖狀態(tài)、偏向鎖狀態(tài)槽奕、輕量級鎖狀態(tài)和重量級鎖狀態(tài)几睛,這幾個狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級史翘,目的是為了提高獲得鎖和釋放鎖的效率
- 偏向鎖:大多數(shù)情況下枉长,鎖不僅不存在多線程競爭,而且總是同一個線程多次獲取琼讽,為了讓線程獲得鎖的代價更低引入偏向鎖。當(dāng)某一線程訪問同步塊時洪唐,會在對象頭和棧幀中的瑣記錄里存儲鎖偏向的線程ID钻蹬,以后該線程在進入該同步塊的時候,不需要再次使用CAS原子操作進行加鎖和解鎖凭需,只需要簡單的測試一下對象頭中的Mark Word是否存在指向當(dāng)前線程的偏向鎖问欠。如果測試成功,則表示獲得鎖粒蜈,否則檢測是否設(shè)置有偏向鎖顺献,如果沒有,則使用CAS競爭鎖枯怖,否則偏向鎖指向該線程注整。
- 輕量級鎖:線程執(zhí)行同步塊之前,會在線程私有的棧幀中開辟用于存儲鎖記錄的空間度硝,稱為Displaced Mark Word肿轨。然后線程嘗試將對象Mark Word的替換為指向Displaced Mark Word記錄的指針,如果成功蕊程,那么當(dāng)前線程獲得鎖椒袍,如果失敗,那么使用自旋獲得鎖藻茂。
原子操作的實現(xiàn)原理
- 處理器實現(xiàn)原子操作:使用總線鎖保證原子性驹暑,使用緩存鎖保證原子性(修改內(nèi)存地址玫恳,緩存一致性機制:阻止同時修改由2個以上的處理器緩存的內(nèi)存區(qū)域數(shù)據(jù))
- JAVA實現(xiàn)原子操作:循環(huán)使用CAS實現(xiàn)原子操作
public class Counter {
private AtomicInteger atomicI = new AtomicInteger(0);
private int i = 0;
public static void main(String[] args) {
final Counter cas = new Counter();
List<Thread> ts = new ArrayList<Thread>(600);
long start = System.currentTimeMillis();
for (int j = 0; j < 100; j++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
cas.count();
cas.safeCount();
}
}
});
ts.add(t);
}
for (Thread t : ts) {
t.start();
}
// 等待所有線程執(zhí)行完成
for (Thread t : ts) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(cas.i);
System.out.println(cas.atomicI.get());
System.out.println(System.currentTimeMillis() - start);
}
/**
* 使用CAS實現(xiàn)線程安全計數(shù)器
*/
private void safeCount() {
for (;;) {
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if (suc) {
break;
}
}
}
/**
* 非線程安全計數(shù)器
*/
private void count() {
i++;
}
}
CAS實現(xiàn)原子操作的三大問題
- ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新优俘,但是如果一個值原來是A京办,變成了B,又變成了A兼吓,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化臂港,但是實際上卻變化了。ABA問題的解決思路就是使用版本號视搏。在變量前面追加上版本號审孽,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A浑娜。
- 循環(huán)時間長開銷大佑力。自旋CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷筋遭。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升打颤,pause指令有兩個作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源漓滔,延遲的時間取決于具體實現(xiàn)的版本编饺,在一些處理器上延遲時間是零。第二它可以避免在退出循環(huán)的時候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush)响驴,從而提高CPU的執(zhí)行效率透且。
- 只能保證一個共享變量的原子操作。當(dāng)對一個共享變量執(zhí)行操作時豁鲤,我們可以使用循環(huán)CAS的方式來保證原子操作秽誊,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性琳骡,這個時候就可以用鎖锅论,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作楣号。比如有兩個共享變量i=2,j=a最易,合并一下ij=2a,然后用CAS來操作ij竖席。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性耘纱,你可以把多個變量放在一個對象里來進行CAS操作。
Java內(nèi)存模型
內(nèi)容較多涉及到內(nèi)存模型毕荐、重排序
http://blog.csdn.net/ccit0519/article/details/11241403(內(nèi)容較多也比較詳細介紹)
double_check的問題
public class DoubleCheckedlocking{
private static Instance instance;
public static Instance getInstance(){
if(instance==null){
synchronized(DoubleCheckedlocking.class){
if(instance==null)
instance=new Instance();
}
}
}
}
//根據(jù)重排序可能會出現(xiàn)的問題
instance=new Instance()常見一個對象可以分成三步
memory=allocate(),//1.分配對象的內(nèi)存空間
ctorInstance(memory)//2.初始化對象
instance=memory //3.設(shè)置Instance指向剛分配的內(nèi)存地址
如果2,3 重排序顛倒后 if語句就可以是引用是上存在但是對象還未被初始化束析,所以 可以給Instance加上一個volatile因為內(nèi)存屏障的緣故
Java中的鎖
鎖是用來控制多個線程訪問共享資源的方式,一般來說憎亚,一個鎖能夠防止多個線程同時訪問共享資源员寇,但是有些鎖可以運行多個線程并發(fā)的訪問共享資源弄慰,比如讀寫鎖。Lock接口和synchronized可以通過獲取鎖和釋放鎖蝶锋,但是前者比后者更具擴展性陆爽。
Lock是一個接口,定義了鎖獲取和釋放的基本操作
Lock和synchronized區(qū)別
- Lock接口可以嘗試非阻塞地獲取鎖 當(dāng)前線程嘗試獲取鎖扳缕。如果這一時刻鎖沒有被其他線程獲取到慌闭,則成功獲取并持有鎖。
- Lock接口能被中斷地獲取鎖 與synchronized不同躯舔,獲取到鎖的線程能夠響應(yīng)中斷驴剔,當(dāng)獲取到的鎖的線程被中斷時,中斷異常將會被拋出粥庄,同時鎖會被釋放丧失。
- Lock接口在指定的截止時間之前獲取鎖,如果截止時間到了依舊無法獲取鎖惜互,則返回布讹。
Lock接口的APi
- void lock() 獲取鎖,調(diào)用該方法當(dāng)前線程將會獲取鎖,當(dāng)鎖獲取后训堆,該方法將返回描验。
- void lockInterruptibly() throws InterruptedException 可中斷獲取鎖,與lock()方法不同之處在于該方法會響應(yīng)中斷坑鱼,即在鎖的獲取過程中可以中斷當(dāng)前線程
- boolean tryLock() 嘗試非阻塞的獲取鎖挠乳,調(diào)用該方法立即返回,true表示獲取到鎖
- boolean tryLock(long time,TimeUnit unit) throws InterruptedException 超時獲取鎖姑躲,以下情況會返回:時間內(nèi)獲取到了鎖,時間內(nèi)被中斷盟蚣,時間到了沒有獲取到鎖黍析。
- void unlock() 釋放鎖
隊列同步器(AQS)
隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架屎开,它使用了一個int成員變量表示同步狀態(tài)阐枣,通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作。同步器的主要使用方式是繼承奄抽,子類通過繼承同步器并實現(xiàn)它的抽象方法來管理同步狀態(tài)蔼两,在抽象方法的實現(xiàn)過程中免不了要對同步狀態(tài)進行更改,這時就需要使用同步器提供的3個方法(getState()逞度、setState(int newState)和compareAndSetState(int expect,int update))來進行操作额划,因為它們能夠保證狀態(tài)的改變是安全的。同步器既可以支持獨占式地獲取同步狀態(tài)档泽,也可以支持共享式地獲取同步狀態(tài)俊戳,這樣就可以方便實現(xiàn)不同類型的同步組件(ReentrantLock揖赴、ReentrantReadWriteLock和CountDownLatch等)。同步器是實現(xiàn)鎖(也可以是任意同步組件)的關(guān)鍵抑胎,在鎖的實現(xiàn)中聚合同步器燥滑,利用同步器實現(xiàn)鎖的語義“⑻樱可以這樣理解二者之間的關(guān)系:鎖是面向使用者的铭拧,它定義了使用者與鎖交互的接口(比如可以允許兩個線程并行訪問),隱藏了實現(xiàn)細節(jié)恃锉;同步器面向的是鎖的實現(xiàn)者搀菩,它簡化了鎖的實現(xiàn)方式,屏蔽了同步狀態(tài)管理淡喜、線程的排隊秕磷、等待與喚醒等底層操作。鎖和同步器很好地隔離了使用者和實現(xiàn)者所需關(guān)注的領(lǐng)域
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
以上是獨占式鎖是一個自定義的同步組件炼团,在同一個時刻指運行一個線程占有鎖澎嚣,用戶在使用Mutex并不會直接和內(nèi)部同步器打交道,而是調(diào)用Mutex提供的方法瘟芝,在Mutex的實現(xiàn)中易桃,獲取鎖Lock方法。
同步隊列
--
同步器依賴內(nèi)部的同步隊列(一個FIFO雙向隊列)來完成同步狀態(tài)的管理锌俱。同步隊列中的節(jié)點(Node)用來保存"獲取同步狀態(tài)失敗的線程"引用晤郑、等待狀態(tài)以及前驅(qū)和后繼節(jié)點。
當(dāng)前線程獲取同步狀態(tài)失敗時贸宏,同步器會將當(dāng)前線程造寝、等待狀態(tài)等信息構(gòu)造成為一個節(jié)點(Node)并將其加入同步隊列,同時會”“阻塞”當(dāng)前線程吭练。當(dāng)一個線程成功地獲取了同步狀態(tài)(或者鎖)诫龙,其他線程將無法獲取到同步狀態(tài),轉(zhuǎn)而被構(gòu)造成為節(jié)點并加入到同步隊列中鲫咽,而這個加入隊列的過程必須要保證線程安全签赃。同步器提供了一個基于CAS的設(shè)置尾節(jié)點的方法:compareAndSetTail(Nodeexpect,Nodeupdate),它需要傳遞當(dāng)前線程“認為”的尾節(jié)點和當(dāng)前節(jié)點分尸,只有設(shè)置成功后锦聊,當(dāng)前節(jié)點才正式與之前的尾節(jié)點建立關(guān)聯(lián)。
獨占式同步狀態(tài)獲取與釋放
主要邏輯:首先調(diào)用自定義同步器實現(xiàn)的tryAcquire(int arg)方法箩绍,該方法保證線程安全的獲取同步狀態(tài)孔庭,如果同步狀態(tài)獲取失敗,則構(gòu)造同步節(jié)點(獨占式Node.EXCLUSIVE伶选,同一時刻只能有一個線程成功獲取同步狀態(tài))并通過addWaiter(Node node)方法將該節(jié)點加入到同步隊列的尾部史飞,最后調(diào)用acquireQueued(Node node,int arg)方法尖昏,使得該節(jié)點以“死循環(huán)”的方式獲取同步狀態(tài)
//將節(jié)點加入到同步隊列的尾部
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//“生成節(jié)點”
// Try the fast path of enq; backup to full enq on failure
//快速嘗試在尾部添加
Node pred = tail;
if (pred != null) {
node.prev = pred;//先將當(dāng)前節(jié)點node的前驅(qū)指向當(dāng)前tail
if (compareAndSetTail(pred, node)) {//CAS嘗試將tail設(shè)置為node
//如果CAS嘗試成功,就說明"設(shè)置當(dāng)前節(jié)點node的前驅(qū)"與"CAS設(shè)置tail"之間沒有別的線程設(shè)置tail成功
//只需要將"之前的tail"的后繼節(jié)點指向node即可
pred.next = node;
return node;
}
}
enq(node);//否則构资,通過死循環(huán)來保證節(jié)點的正確添加
return node;
}
private Node enq(final Node node) {
for (;;) {//通過死循環(huán)來保證節(jié)點的正確添加
Node t = tail;
if (t == null) { // Must initialize 同步隊列為空的情況
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {//直到CAS成功為止
t.next = node;
return t;//結(jié)束循環(huán)
}
}
}
}
上述代碼通過使用compareAndSetTail(Node expect,Node update)來確保節(jié)點能夠被線程安全添加抽诉,如果使用普通的LinkedList來維護節(jié)點之間的關(guān)系,那么當(dāng)一個線程獲取到同步狀態(tài)吐绵,而其他多個線程由于調(diào)用tryAcquire(int arg)方法獲取同步狀態(tài)失敗而并發(fā)被添加到LinkedList迹淌,LinkedList將難以保證Node的正確添加
在enq(final Node node)方法中,同步器通過“死循環(huán)”來保證節(jié)點的正確添加己单,在“死循環(huán)”中只有通過CAS將節(jié)點設(shè)置成為尾節(jié)點之后唉窃,當(dāng)前線程才能從該方法返回,否則纹笼,當(dāng)前線程不斷地嘗試設(shè)置纹份。可以看出廷痘,enq(final Node node)方法將并發(fā)添加節(jié)點的請求通過CAS變得“串行化”了蔓涧。
節(jié)點自旋
--
節(jié)點進入同步隊列之后,就進入了一個自旋的過程笋额,每個節(jié)點(或者說是線程)都在自省地觀察元暴,當(dāng)條件滿足,獲取到了同步狀態(tài)兄猩,就可以從這個自旋過程中退出茉盏,否則依舊留在這個自旋過程中。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//無限循環(huán)
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//前驅(qū)節(jié)點是首節(jié)點且獲取到了同步狀態(tài)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;//從自旋中退出
}
if (shouldParkAfterFailedAcquire(p, node) &&//判斷獲取同步狀態(tài)失敗后是否需要阻塞
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
共享式同步狀態(tài)獲取與釋放
共享式獲取與獨占式獲取最主要的區(qū)別在于同一時刻能否有多個線程同時獲取到同步狀態(tài)枢冤。
以文件的讀寫為例鸠姨,如果一個程序在對文件進行讀操作,那么這一時刻對于該文件的寫操作均被阻塞淹真,而讀操作能夠同時進行享怀。寫操作要求對資源的獨占式訪問,而讀操作可以是共享式訪問
調(diào)用同步器的acquireShared(int arg)方法可以共享式地獲取同步狀態(tài)趟咆。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在acquireShared(int arg)方法中,同步器調(diào)用tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài)梅屉,tryAcquireShared(int arg)方法返回值為int類型值纱,當(dāng)返回值大于等于0時,表示能夠獲取到同步狀態(tài)坯汤。因此虐唠,在共享式獲取的自旋過程中,成功獲取到同步狀態(tài)并退出自旋的條件就是tryAcquireShared(int arg)方法返回值大于等于0惰聂。
在doAcquireShared(int arg)方法的自旋過程中疆偿,如果當(dāng)前節(jié)點的前驅(qū)為頭節(jié)點時咱筛,嘗試獲取同步狀態(tài),如果返回值大于等于0杆故,表示該次獲取同步狀態(tài)成功并從自旋過程中退出迅箩。
重入鎖
--
它表示該鎖能夠支持一個線程對資源的重復(fù)加鎖。除此之外处铛,該鎖的還支持獲取鎖時的公平和非公平性選擇
之前的例子饲趋,當(dāng)一個線程調(diào)用Mutex的lock()方法獲取鎖之后,如果再次調(diào)用lock()方法撤蟆,則該線程將會被自己所阻塞奕塑,原因是Mutex在實現(xiàn)tryAcquire(int acquires)方法時沒有考慮占有鎖的線程再次獲取鎖的場景,而在調(diào)用tryAcquire(int acquires)方法時返回了false家肯,導(dǎo)致該線程被阻塞龄砰。簡單地說,Mutex是一個不支持重進入的鎖讨衣。而synchronized關(guān)鍵字隱式的支持重進入换棚,比如一個synchronized修飾的遞歸方法,在方法執(zhí)行時值依,執(zhí)行線程在獲取了鎖之后仍能連續(xù)多次地獲得該鎖圃泡,而不像Mutex由于獲取了鎖,而在下一次獲取鎖時出現(xiàn)阻塞自己的情況愿险。
ReentrantLock雖然沒能像synchronized關(guān)鍵字一樣支持隱式的重進入颇蜡,但是在調(diào)用lock()方法時,已經(jīng)獲取到鎖的線程辆亏,能夠再次調(diào)用lock()方法獲取鎖而不被阻塞风秤。
公平鎖與非公平鎖的比較
--
公平性鎖每次都是從同步隊列中的第一個節(jié)點獲取到鎖,而非公平性鎖出現(xiàn)了一個線程連續(xù)獲取鎖的情況扮叨。
非公平性鎖可能使線程“饑餓”缤弦,當(dāng)一個線程請求鎖時,只要獲取了同步狀態(tài)即成功獲取鎖彻磁。在這個前提下碍沐,剛釋放鎖的線程再次獲取同步狀態(tài)的幾率會非常大,使得其他線程只能在同步隊列中等待衷蜓。
為什么它又被設(shè)定成默認的實現(xiàn)呢累提?非公平性鎖模式下線程上下文切換的次數(shù)少,因此其性能開銷更小磁浇。公平性鎖保證了鎖的獲取按照FIFO原則斋陪,而代價是進行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換无虚,保證了其更大的吞吐量缔赠。
讀寫鎖
讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時友题,所有的讀線程和其他寫線程均被阻塞嗤堰。
讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖咆爽,通過分離讀鎖和寫鎖梁棠,使得并發(fā)性相比一般的排他鎖有了很大提升。除了保證寫操作對讀操作的可見性以及并發(fā)性的提升之外斗埂,讀寫鎖能夠簡化讀寫交互場景的編程方式符糊。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量呛凶。Java并發(fā)包提供讀寫鎖的實現(xiàn)是ReentrantReadWriteLock男娄。
ConcurrentHashMap的實現(xiàn)原理與使用
ConcurrentHashMap是線程安全且高效的HashMap。
為什么使用ConcurrentHashMap的原因
- HashMap線程不安全漾稀,在多線程下使用HashMap進行put操作會引起死循環(huán)模闲,導(dǎo)致CPU利用率接近100%,原因在于多線程會導(dǎo)致HashMap的Entry鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu)崭捍,一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu)尸折,Entry的next節(jié)點永遠不為空,就回產(chǎn)生死循環(huán)獲取Entry
- HashTable效率低
- ConcurrentHashMap的鎖分段技術(shù)可以有效提升并發(fā)訪問率殷蛇,原因在于HashTable在競爭中都是競爭同一把鎖实夹,但是ConcurrentHashMap將數(shù)據(jù)分成一段一段地儲存,然后給每一段數(shù)據(jù)配一把鎖粒梦,當(dāng)一個線程占用鎖訪問其中一段數(shù)據(jù)時候亮航,其他段的數(shù)據(jù)也被其他線程訪問
具體的實現(xiàn)及原理http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
Fork/Join框架
Fork/Join框架是Java7提供了的一個用于并行執(zhí)行任務(wù)的框架, 是一個把大任務(wù)分割成若干個小任務(wù)匀们,最終匯總每個小任務(wù)結(jié)果后得到大任務(wù)結(jié)果的框架缴淋。
工作竊取算法
--
指某個線程從其他隊列里竊取任務(wù)來執(zhí)行。假如我們需要做一個比較大的任務(wù)泄朴,我們可以把這個任務(wù)分割為若干互不依賴的子任務(wù)重抖,為了減少線程間的競爭,于是把這些子任務(wù)分別放到不同的隊列里祖灰,并為每個隊列創(chuàng)建一個單獨的線程來執(zhí)行隊列里的任務(wù)仇哆,線程和隊列一一對應(yīng),比如A線程負責(zé)處理A隊列里的任務(wù)夫植。但是有的線程會先把自己隊列里的任務(wù)干完,而其他線程對應(yīng)的隊列里還有任務(wù)等待處理。干完活的線程與其等著详民,不如去幫其他線程干活延欠,于是它就去其他線程的隊列里竊取一個任務(wù)來執(zhí)行。而在這時它們會訪問同一個隊列沈跨,所以為了減少竊取任務(wù)線程和被竊取任務(wù)線程之間的競爭由捎,通常會使用雙端隊列,被竊取任務(wù)線程永遠從雙端隊列的頭部拿任務(wù)執(zhí)行饿凛,而竊取任務(wù)的線程永遠從雙端隊列的尾部拿任務(wù)執(zhí)行狞玛。優(yōu)點是充分利用線程進行并行計算,并減少了線程間的競爭涧窒。
參考文章
http://blog.csdn.net/ccfeng2008/article/details/49389463
http://blog.csdn.net/qq_16811963/article/details/52171764
http://blog.csdn.net/canot/article/details/52050633
http://www.2cto.com/kf/201608/540926.html
http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
http://www.infoq.com/cn/articles/fork-join-introduction/
java并發(fā)編程的藝術(shù)