1、淺談Java多線程CAS原理java
2、多線程鎖的升級原理是什麼? 安全
3、synchronized 和 volatile 的區別是什麼?網絡
1、淺談Java多線程CAS原理多線程
(一)、鎖併發
實現線程同步最直觀的策略是加鎖,如使用synchronizeed關鍵字進行加鎖。app
悲觀鎖:悲觀鎖在一個線程進行加鎖操做後使得該對象變爲該線程的獨有對象,其它的線程都會被悲觀鎖阻攔在外,沒法操做。性能
悲觀鎖的缺陷:優化
一、一個線程得到悲觀鎖後其它線程必須阻塞。this
二、線程切換時要不停的釋放鎖和獲取鎖,開銷巨大。atom
三、當一個低優先級的線程得到悲觀鎖後,高優先級的線程必須等待,致使線程優先級倒置問題,synchronized加鎖是一種典型的悲觀鎖。
樂觀鎖:
樂觀鎖與悲觀鎖不一樣,他樂觀的認爲對一個對象的操做不會引起衝突,因此每次操做都不進行加鎖,只是在最後提交更改時驗證是否發生衝突,若是衝突則再試一遍知道成功爲止,這個嘗試的過程被稱爲自旋。樂觀鎖其實並無加鎖,但樂觀鎖也引入了諸如ABA、自旋次數過多等問題。
(二)、CAS操做
一、在JDK1.5以前,Java中全部鎖都是重量級的悲觀鎖,1.5中引入了java.util.concurrent包,這個包中提供了樂觀鎖的使用,而整個JUC包實現的基石就是CAS操做。
二、CAS(compare and swap)即比較和替換,在juc包中進程會看多諸如此類的代碼:
unsafe.compareAndSwapInt(this, valueOffset, expect, update);這即是使用CAS操做。
三、CAS操做的過程爲判斷某個內存地址的值是否爲給定的原值,若是是則修改成心智並返回成功,不然返回該地址的值。CAS操做有三個參數:內存地址、原值、新值。當內存地址中存放的對象不等於提供的原值時則將其替換爲新值。
四、以AtomicInteger的修改成例查看使用CAS時如何無鎖並安全的修改某個值的:
public final int getAndUpdate(IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return prev; }
先得到要修改的原值prev和要改爲的新值next,當使用CAS替換新值不成功時,自旋,從新得到原值和新值再試一次直到成功爲止。
五、這段代碼中能夠發現兩個問題:
① compareAndSet操做必須是原子性的,即操做中間沒法被打斷。
② 獲取原值時要保證這個原值對本線程可見
這兩個問題是CAS操做的前提條件。
compareAndSet實際上是調用了JNI,使用本地方法來保證原子性。
JNI(Java native interface),經過使用Java本地接口書寫程序,能夠確保代碼在不一樣的平臺上方便移植。
(三)、CAS帶來的問題
一、ABA問題
CAS操做的流程爲:
①讀取原值。
②經過原子操做比較和替換。
雖然比較和替換是原子性的,可是讀取原值和比較替換這兩步不是原子性的,期間原值可能被其它線程修改。
ABA問題有些時候對系統不會產生問題,可是有些時候卻也是致命的。
如:你在自動售貨機上買了瓶飲料花了5塊錢,你的帳戶原有10塊錢,此時售貨機發出扣款CAS請求:若是是10元變爲5元。可是因爲網絡問題,這個請求發了兩次,若是第一次執行正常將帳戶變爲5元了,那麼第二次請求時沒法執行的,可是在第一次扣款操做執行完以後有人給你轉帳5元了,此時你的帳戶再次變爲10元,那麼第二遍的扣款請求是能夠執行成功的,顯然在這種狀況下你多花了5塊錢。
ABA問題的解決方法是對該變量增長一個版本號,每次修改都會更新其版本號。JUC包中提供了一個類AtomicStampedReference,這個類中維護了一個版本號,每次對值的修改都會改動版本號。
二、自旋次數過多
CAS操做在不成功時會從新讀取內存值並自旋嘗試,當系統的併發量很是高時即每次讀取新值以後該值又被改動,致使CAS操做失敗並不斷的自旋重試,此時使用CAS並不能提升效率,反而會由於自旋次數過多還不如直接加鎖進行操做的效率高。
三、只能保證一個變量的原子性
當對一個變量操做時,CAS能夠保證原子性,但同時操做對個變量CAS就無能爲力了。
Java中提供了atomicReference類來保證引用對象之間的原子性,就能夠把多個變量放到一個對象裏進行CAS操做,或者直接使用鎖來操做多個對象。
2、多線程鎖的升級原理是什麼?
鎖的級別:
無鎖->偏向鎖->輕量級鎖->重量級鎖
鎖分級別緣由:
沒有優化前,sychroniezed是重量級鎖(悲觀鎖),使用wait、notify、notifyAll來切換線程狀態很是消耗系統資源,線程的掛起和喚醒間隔很短暫,這樣很浪費資源,影響性能。因此JVM對sychronized關鍵字進行了優化,把鎖分爲無鎖、偏向鎖、輕量級鎖、重量級鎖。
一、無鎖
沒有對資源進行鎖定,全部的線程都能訪問並修改同一個資源,但同時只有一個線程能修改爲功,其它修改失敗的線程會不斷重試直到修改爲功。
二、偏向鎖
對象的代碼一直被同一線程執行,不存在多個線程競爭,該線程在後續執行中自動獲取鎖,下降獲取鎖帶來的性能開銷。偏向鎖,指的是偏向第一個加鎖線程,該線程是不會主動釋放偏向鎖的,只有當其餘線程嘗試競爭偏向鎖纔會被釋放。
偏向鎖的撤銷,須要在某個時間點上沒有字節碼正在執行時,先暫停偏向鎖的線程,而後判斷鎖對象是否處於被鎖定狀態,若是線程不處於活動狀態,則將對象頭設置成無鎖狀態,並撤銷偏向鎖。
若是線程處於活動狀態,升級爲輕量級鎖的狀態。
三、輕量級鎖
輕量級鎖是指當鎖是偏向鎖的時候,被第二個線程B訪問,此時偏向鎖就會升級爲輕量級鎖,線程B會經過自旋的形式嘗試獲取鎖,線程不會阻塞,從er提高性能。
當前只有一個等待線程,則該線程將經過自旋進行等待。可是當自旋超過必定次數時,輕量級鎖邊會升級爲重量級鎖,當一個線程已持有鎖,另外一個線程在自旋,而此時第三個線程來訪時,輕量級鎖也會升級爲重量級鎖。
注:自旋是什麼?
自旋(spinlock)是指當一個線程獲取鎖的時候,若是鎖已經被其它線程獲取,那麼該線程將循環等待,而後不斷的判斷鎖是否可以被成功獲取,直到獲取到鎖纔會退出循環。
四、重量級鎖
指當有一個線程獲取鎖以後,其他全部等待獲取該鎖的線程都會處於阻塞狀態。
重量級鎖經過對象內部的監聽器(monitor)實現,而其中monitor的本質是依賴於底層操做系統的Mutex Lock實現,操做系統實現線程之間的切換須要從用戶態切換到內核態,切換成本很是高。
五、鎖狀態對比:
偏向鎖 | 輕量級鎖 | 重量級鎖 | |
使用場景 | 只有一個線程進入同步塊 | 雖然不少線程,但沒有衝突,線程進入時間錯開於是並未爭搶鎖 | 發生了鎖爭搶的狀況,多條線程進入同步塊爭用鎖 |
本質 | 取消同步操做 | CAS操做代替互斥同步 | 互斥同步 |
優勢 | 不阻塞,執行效率高(只有第一次獲取偏向鎖時須要CAS操做,後面只是比對ThreadId) | 不會阻塞 | 不會空耗CPU |
缺點 | 適用場景太侷限。若競爭產生,會有額外的偏向鎖撤銷的消耗 |
長時間獲取不到鎖空耗CPU | 阻塞,上下文切換,重量級操做,消耗操做系統資源 |
六、CAS是什麼呢?
CAS即即比較和替換,當使用CAS替換新值不成功時,自旋,從新得到原值和新值再試一次直到成功爲止。
CAS經過無鎖操做提升了系統的吞吐率,高效的解決了原子操做問題。
3、synchronized 和 volatile 的區別是什麼?
一、notify() 和 notifyAll() 有什麼區別?
先解釋兩個概念:
等待池:假設一個線程調用了wait方法,線程就會釋放該對象的鎖,進入到該對象的等待池
鎖池:只有得到了對象的鎖,線程纔會執行對象的synchronizeed代碼,對象的鎖每次只有一個線程能夠得到,其它線程只能在鎖池中等待
notify()方法隨機喚醒對象等待池中的一個線程,進入鎖池。
notifyAll()喚醒對象的等待池中的全部線程,進入鎖池。
二、execute()和submit()有什麼區別?
線程任務分兩類,一類是實現了runnable接口,一類是實現了callable接口。
execute(Runnable x)沒有返回值,能夠執行任務,但沒法判斷任務是否成功完成,實現runnable接口
submit(Runnable x)返回一個future。能夠用這個future來判斷任務是否成功完成,實現Callable接口
三、sleep() 和 wait() 有什麼區別?
①sleep()是thread類的靜態本地方法
wait()是Obejct類的成員本地方法
②sleep()方法能夠在任何地方使用
wait()方法只能在同步方法或同步代碼塊中使用
③sleep()會休眠當前線程指定時間,釋放CPU資源,不釋放對象鎖,休眠時間到自動甦醒繼續執行
wait()方法放棄持有的對象鎖,進入等待隊列,當該對象被調動notify()或notifyAll()方法後纔有機會競爭獲取對象鎖,進行運行狀態
④均需捕獲interruptedException異常
四、synchronized 和 volatile 的區別是什麼?
做用:
synchronized表示只有一個線程能夠獲取做用對象的鎖,執行代碼,阻塞其它線程。
volatile表示變量在CPU寄存器中是不肯定的,必須從主存中讀取,保證多線程環境下變量的可見性和有序性。
區別:
synchronized能夠做用於方法、變量;volatile只能做用於變量。
synchronized能夠保證線程間的有序性、原子性和可見性;volatile紙包裝了可見性和有序性,沒法保證原子性。
synchronized線程阻塞,volatile線程不阻塞。