Java死锁、活锁,悲观锁、乐观锁
1、死鎖與活鎖的區(qū)別,死鎖與饑餓的區(qū)別?
死鎖:是指兩個(gè)或兩個(gè)以上的進(jìn)程(或線程)在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。?
產(chǎn)生死鎖的必要條件:?
互斥條件:所謂互斥就是進(jìn)程在某一時(shí)間內(nèi)獨(dú)占資源。
請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。?
不剝奪條件:進(jìn)程已獲得資源,在末使用完之前,不能強(qiáng)行剝奪。??
循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
活鎖:任務(wù)或者執(zhí)行者沒(méi)有被阻塞,由于某些條件沒(méi)有滿足,導(dǎo)致一直重復(fù)嘗試,失敗,嘗試,失敗。
活鎖和死鎖的區(qū)別在于,處于活鎖的實(shí)體是在不斷的改變狀態(tài),所謂的“活”, 而處于死鎖的實(shí)體表現(xiàn)為等待;活鎖有可能自行解開,死鎖則不能。
饑餓:一個(gè)或者多個(gè)線程因?yàn)榉N種原因無(wú)法獲得所需要的資源,導(dǎo)致一直無(wú)法執(zhí)行的狀態(tài)。?
Java中導(dǎo)致饑餓的原因:?
高優(yōu)先級(jí)線程吞噬所有的低優(yōu)先級(jí)線程的CPU時(shí)間。?
線程被永久堵塞在一個(gè)等待進(jìn)入同步塊的狀態(tài),因?yàn)槠渌€程總是能在它之前持續(xù)地對(duì)該同步塊進(jìn)行訪問(wèn)。?
線程在等待一個(gè)本身也處于永久等待完成的對(duì)象(比如調(diào)用這個(gè)對(duì)象的wait方法),因?yàn)槠渌€程總是被持續(xù)地獲得喚醒。
2、樂(lè)觀鎖和悲觀鎖的理解及如何實(shí)現(xiàn),有哪些實(shí)現(xiàn)方式?
悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如Java里面的同步原語(yǔ)synchronized關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖。
樂(lè)觀鎖:顧名思義,就是很樂(lè)觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂(lè)觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂(lè)觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的。
樂(lè)觀鎖的實(shí)現(xiàn)方式:?
使用版本標(biāo)識(shí)來(lái)確定讀到的數(shù)據(jù)與提交時(shí)的數(shù)據(jù)是否一致。提交后修改版本標(biāo)識(shí),不一致時(shí)可以采取丟棄和再次嘗試的策略。?
java中的Compare and Swap即CAS ,當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。 CAS 操作中包含三個(gè)操作數(shù) —— 需要讀寫的內(nèi)存位置(V)、進(jìn)行比較的預(yù)期原值(A)和擬寫入的新值(B)。如果內(nèi)存位置V的值與預(yù)期原值A(chǔ)相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值B。否則處理器不做任何操作。
CAS缺點(diǎn):??
ABA問(wèn)題:?
比如說(shuō)一個(gè)線程one從內(nèi)存位置V中取出A,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出A,并且two進(jìn)行了一些操作變成了B,然后two又將V位置的數(shù)據(jù)變成A,這時(shí)候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但可能存在潛藏的問(wèn)題。從Java1.5開始JDK的atomic包里提供了一個(gè)類AtomicStampedReference來(lái)解決ABA問(wèn)題。?
循環(huán)時(shí)間長(zhǎng)開銷大:?
對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS自旋的概率會(huì)比較大,從而浪費(fèi)更多的CPU資源,效率低于synchronized。?
只能保證一個(gè)共享變量的原子操作:?
當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)CAS的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖。
3、SynchronizedMap和ConcurrentHashMap有什么區(qū)別?
SynchronizedMap一次鎖住整張表來(lái)保證線程安全,所以每次只能有一個(gè)線程來(lái)訪為map。
ConcurrentHashMap使用分段鎖來(lái)保證在多線程下的性能。ConcurrentHashMap中則是一次鎖住一個(gè)桶。ConcurrentHashMap默認(rèn)將hash表分為16個(gè)桶,諸如get,put,remove等常用操作只鎖當(dāng)前需要用到的桶。這樣,原來(lái)只能一個(gè)線程進(jìn)入,現(xiàn)在卻能同時(shí)有16個(gè)寫線程執(zhí)行,并發(fā)性能的提升是顯而易見的。?
另外ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中,當(dāng)iterator被創(chuàng)建后集合再發(fā)生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時(shí)new新的數(shù)據(jù)從而不影響原有的數(shù)據(jù) ,iterator完成后再將頭指針替換為新的數(shù)據(jù) ,這樣iterator線程可以使用原來(lái)老的數(shù)據(jù),而寫線程也可以并發(fā)的完成改變。
總結(jié)
以上是生活随笔為你收集整理的Java死锁、活锁,悲观锁、乐观锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: gc()两分钟了解JDK8默认垃圾收集器
- 下一篇: Java应用程序与小程序之间有那些差别?