AtomicInteger源码分析——基于CAS的乐观锁实现
原文出處:?bestStyle
1. 悲觀鎖與樂觀鎖
我們都知道,cpu是時分復(fù)用的,也就是把cpu的時間片,分配給不同的thread/process輪流執(zhí)行,時間片與時間片之間,需要進行cpu切換,也就是會發(fā)生進程的切換。切換涉及到清空寄存器,緩存數(shù)據(jù)。然后重新加載新的thread所需數(shù)據(jù)。當一個線程被掛起時,加入到阻塞隊列,在一定的時間或條件下,在通過notify(),notifyAll()喚醒回來。在某個資源不可用的時候,就將cpu讓出,把當前等待線程切換為阻塞狀態(tài)。等到資源(比如一個共享數(shù)據(jù))可用了,那么就將線程喚醒,讓他進入runnable狀態(tài)等待cpu調(diào)度。這就是典型的悲觀鎖的實現(xiàn)。獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,它假設(shè)最壞的情況,并且只有在確保其它線程不會造成干擾的情況下執(zhí)行,會導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。
但是,由于在進程掛起和恢復(fù)執(zhí)行過程中存在著很大的開銷。當一個線程正在等待鎖時,它不能做任何事,所以悲觀鎖有很大的缺點。舉個例子,如果一個線程需要某個資源,但是這個資源的占用時間很短,當線程第一次搶占這個資源時,可能這個資源被占用,如果此時掛起這個線程,可能立刻就發(fā)現(xiàn)資源可用,然后又需要花費很長的時間重新?lián)屨兼i,時間代價就會非常的高。
所以就有了樂觀鎖的概念,他的核心思路就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。在上面的例子中,某個線程可以不讓出cpu,而是一直while循環(huán),如果失敗就重試,直到成功為止。所以,當數(shù)據(jù)爭用不嚴重時,樂觀鎖效果更好。比如CAS就是一種樂觀鎖思想的應(yīng)用。
2. ? java中CAS的實現(xiàn)
CAS就是Compare and Swap的意思,比較并操作。很多的cpu直接支持CAS指令。CAS是項樂觀鎖技術(shù),當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。CAS有3個操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當且僅當預(yù)期值A(chǔ)和內(nèi)存值V相同時,將內(nèi)存值V修改為B,否則什么都不做。
JDK1.5中引入了底層的支持,在int、long和對象的引用等類型上都公開了CAS的操作,并且JVM把它們編譯為底層硬件提供的最有效的方法,在運行CAS的平臺上,運行時把它們編譯為相應(yīng)的機器指令。在Java.util.concurrent.atomic包下面的所有的原子變量類型中,比如AtomicInteger,都使用了這些底層的JVM支持為數(shù)字類型的引用類型提供一種高效的CAS操作。
在CAS操作中,會出現(xiàn)ABA問題。就是如果V的值先由A變成B,再由B變成A,那么仍然認為是發(fā)生了變化,并需要重新執(zhí)行算法中的步驟。有簡單的解決方案:不是更新某個引用的值,而是更新兩個值,包括一個引用和一個版本號,即使這個值由A變?yōu)锽,然后為變?yōu)锳,版本號也是不同的。AtomicStampedReference和AtomicMarkableReference支持在兩個變量上執(zhí)行原子的條件更新。AtomicStampedReference更新一個“對象-引用”二元組,通過在引用上加上“版本號”,從而避免ABA問題,AtomicMarkableReference將更新一個“對象引用-布爾值”的二元組。
3. ?AtomicInteger的實現(xiàn)。
AtomicInteger 是一個支持原子操作的 Integer 類,就是保證對AtomicInteger類型變量的增加和減少操作是原子性的,不會出現(xiàn)多個線程下的數(shù)據(jù)不一致問題。如果不使用 AtomicInteger,要實現(xiàn)一個按順序獲取的 ID,就必須在每次獲取時進行加鎖操作,以避免出現(xiàn)并發(fā)時獲取到同樣的 ID 的現(xiàn)象。
接下來通過源代碼來看AtomicInteger具體是如何實現(xiàn)的原子操作。
首先看incrementAndGet() 方法,下面是具體的代碼。
| 1 2 3 4 5 6 7 8 | public final int incrementAndGet() {? ????????for (;;) {? ????????????int current = get();? ????????????int next = current + 1;? ????????????if (compareAndSet(current, next))? ????????????????return next;? ????????}? ????} |
通過源碼,可以知道,這個方法的做法為先獲取到當前的 value 屬性值,然后將 value 加 1,賦值給一個局部的 next 變量,然而,這兩步都是非線程安全的,但是內(nèi)部有一個死循環(huán),不斷去做compareAndSet操作,直到成功為止,也就是修改的根本在compareAndSet方法里面,compareAndSet()方法的代碼如下:
| 1 2 3 | public final boolean compareAndSet(int expect, int update) {? ????????return unsafe.compareAndSwapInt(this, valueOffset, expect, update);? ????} |
compareAndSet()方法調(diào)用的compareAndSwapInt()方法的聲明如下,是一個native方法。
publicfinal native boolean compareAndSwapInt(Object var1, long var2, int var4, intvar5);
compareAndSet 傳入的為執(zhí)行方法時獲取到的 value 屬性值,next 為加 1 后的值, compareAndSet所做的為調(diào)用 Sun 的 UnSafe 的 compareAndSwapInt 方法來完成,此方法為 native 方法,compareAndSwapInt 基于的是CPU 的 CAS指令來實現(xiàn)的。所以基于 CAS 的操作可認為是無阻塞的,一個線程的失敗或掛起不會引起其它線程也失敗或掛起。并且由于 CAS 操作是 CPU 原語,所以性能比較好。
類似的,還有decrementAndGet()方法。它和incrementAndGet()的區(qū)別是將 value 減 1,賦值給next 變量。
AtomicInteger中還有g(shù)etAndIncrement() 和getAndDecrement() 方法,他們的實現(xiàn)原理和上面的兩個方法完全相同,區(qū)別是返回值不同,前兩個方法返回的是改變之后的值,即next。而這兩個方法返回的是改變之前的值,即current。還有很多的其他方法,就不列舉了。
from:?http://www.importnew.com/22078.html?
總結(jié)
以上是生活随笔為你收集整理的AtomicInteger源码分析——基于CAS的乐观锁实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原子操作类AtomicInteger详解
- 下一篇: Executors创建的4种线程池的使用