1、ABA問題
CAS 會導致“ABA問題”。
CAS 算法實現一個重要前提需要取出內存中某時刻的數據,而在下時刻比較並替換,那么在這個時間差類會導致數據的變化。
比如說一個線程 one 從內存位置 V 中取出 A,這時候另一個線程 two 也從內存中取出 A,並且 two 進行了一些操作變成了 B,然后 two 又將 V 位置的數據變成 A,這時候線程 one 進行 CAS 操作發現內存中仍然是 A,然后 one 操作成功。盡管線程 one 的 CAS 操作成功,但是不代表這個過程就是沒有問題的。
部分樂觀鎖的實現是通過版本號(version)的方式來解決 ABA 問題,樂觀鎖每次在執行數據的修改操作時,都會帶上一個版本號,一旦版本號和數據的版本號一致就可以執行修改操作並對版本號執行 +1 操作,否則就執行失敗。因為每次操作的版本號都會隨之增加,所以不會出現 ABA 問題,因為版本號只會增加不會減少。
2、CAS樂觀鎖
CAS 是項樂觀鎖技術,當多個線程嘗試使用 CAS 同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。
CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。如果內存位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”這其實和樂觀鎖的沖突檢查 + 數據更新的原理是一樣的。
3、synchronize實現原理
1同步代碼塊是使用 monitorenter 和 monitorexit 指令實現的,同步方法(在這看不出來需要看 JVM 底層實現)依靠的是方法修飾符上的 ACC_SYNCHRONIZED 實現。
4、synchronize與lock的區別
synchronized 和 lock 的用法區別
osynchronized(隱式鎖):在需要同步的對象中加入此控制,synchronized 可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。
olock(顯示鎖):需要顯示指定起始位置和終止位置。一般使用 ReentrantLock 類做為鎖,多個線程中必須要使用一個 ReentrantLock 類做為對象才能保證鎖的生效。且在加鎖和解鎖處需要通過 lock() 和 unlock() 顯示指出。所以一般會在 finally塊中寫 unlock() 以防死鎖。
synchronized 和 lock 性能區別 synchronized 是托管給 JVM 執行的,而 lock是 Java 寫的控制鎖的代碼。在 JDK 1.5 中,synchronize 是性能低效的。因為這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用 Java 提供的 Lock 對象,性能更高一些。但是到了 JDK 1.6,發生了變化。synchronize 在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在 JDK 1.6 上 synchronize 的性能並不比 Lock 差。
synchronized 和 lock 機制區別
synchronized 原始采用的是 CPU 悲觀鎖機制,即線程獲得的是獨占鎖。獨占鎖意味着其 他線程只能依靠阻塞來等待線程釋放鎖。
Lock 用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖實現的機制就是 CAS 操作(Compare and Swap)。
5、volatile實現原理
在 JVM 底層 volatile 是采用“內存屏障”來實現的
緩存一致性協議(MESI協議)它確保每個緩存中使用的共享變量的副本是一致的。其核心思想如下:當某個 CPU 在寫數據時,如果發現操作的變量是共享變量,則會通知其他 CPU 告知該變量的緩存行是無效的,因此其他 CPU 在讀取該變量時,發現其無效會重新從主存中加載數據
6、樂觀鎖的業務場景及實現方式
樂觀鎖(Optimistic Lock):
每次獲取數據的時候,都不會擔心數據被修改,所以每次獲取數據的時候都不會進行加鎖,但是在更新數據的時候需要判斷該數據是否被別人修改過。如果數據被其他線程修改,則不進行數據更新,如果數據沒有被其他線程修改,則進行數據更新。由於數據沒有進行加鎖,期間該數據可以被其他線程進行讀寫操作。
比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,數據發生沖突的可能性就會增大,為了保證數據的一致性,應用層需要不斷的重新獲取數據,這樣會增加大量的查詢操作,降低了系統的吞吐量。
7、說說線程安全的問題
線程安全是多線程領域的問題,線程安全可以簡單理解為一個方法或者一個實例可以在多線程環境中使用而不會出現問題。
在 Java 多線程編程當中,提供了多種實現 Java 線程安全的方式:
最簡單的方式,使用 Synchronization 關鍵字
使用 java.util.concurrent.atomic 包中的原子類,例如 AtomicInteger
使用 java.util.concurrent.locks 包中的鎖
使用線程安全的集合 ConcurrentHashMap
使用 volatile關鍵字,保證變量可見性(直接從內存讀,而不是從線程 cache 讀)