Atomic
簡介
atomic包下原子操作類提供了一種用法簡單、性能高效 、線程安全地更新一個變量的方式。
atomic包下一共有12個相關的類,分爲4組,分別用於原子更新基本類型,原子更新數組,原子更新引用,原子更新字段。
使用
-
原子更新基本類型
AtomicBoolean AtomicInteger AtomicLong
常用方法,以AtomicInteger爲例- int get() 獲取實際的值
- void set(int newValue) 設置值,多線程情況下會導致值不安全
- boolean compareAndSet(int expect, int update) 如果輸入的數值等於預期值,則以原子方式將該值設置爲輸入的值,並返回是否成功
- int getAndSet(int newValue) 以原子的方式將值設置爲新值
- int getAndIncrement() 以原子的方式將值加一,並返回
以上方法大致可以分爲3類:第一類不保證多線程操作下線程安全,如set();第二類返回boolean類型的值,需要傳入兩個參數,預期值和更新值,如compareAndSet(),這類方法不保證值一定能更新成功,多個線程同時操作,同樣的入參只會有一個線程成功。第三類:getAndIncrement(),這類方法會保證多線程下操作一定成功,它不關心預期的值,只關心更新後的值,假如多個線程同時操作,同一時刻只有一個線程在更新值,其他線程會循環等待之更新成功。
-
原子更新數組
AtomicIntegerArray AtomicLongArray AtomicReferenceArray
常用方法,同樣以AtomicIntegerArray爲例- int get(int i) 獲取第i個數組下標中的值
- void set(int i, int newValue) 設置第i個數組下標中的值
- boolean compareAndSet(int i, int expect, int update) 輸入的第i個下標等於預期值,則以原子方式將該值設置爲輸入的值,並返回是否成功
- int getAndSet(int i, int newValue) 以原子的方式將數字第i的值設置爲新值
- int getAndIncrement(int i) 以原子的方式將第i的值加一,並返回
-
原子更新字段
AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater
常用方法,同樣以AtomicIntegerFieldUpdater爲例- AtomicIntegerFieldUpdaterImpl(final Class tclass,final String fieldName,final Class<?> caller) 構造函數需要傳入class 和 字段名稱
- void set(T obj, int newValue); 類似,不再說明
- boolean compareAndSet(T obj, int expect, int update) 類似,不再說明
- int getAndSet(T obj, int newValue) 類似,不再說明
-
原子更新引用
AtomicReference AtomicMarkableReference AtomicStampedReference
常用方法,以AtomicReference爲例- void set(V newValue)
- boolean compareAndSet(V expect, V update)
- V getAndSet(V newValue)
代碼示例:
多線程下的併發問題
public class SatomicExample1 {
private static int sum;
private static AtomicInteger atomicSum = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch c1 = new CountDownLatch(5);
CountDownLatch c2 = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
for(int j = 0; j < 100000; j++) {
sum++;
}
c1.countDown();
});
}
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
for(int j = 0; j < 100000; j++) {
atomicSum.getAndIncrement();
}
c2.countDown();
});
}
c1.await();
c2.await();
System.out.println(sum);
System.out.println(atomicSum.get());
executorService.shutdown();
}
}
輸出結果
494796
500000
使用atomic實現簡單的自旋鎖
public class SatomicExample2 {
private static int sum;
public static void main(String[] args) throws InterruptedException {
TestLock lock = new TestLock();
ExecutorService executorService = Executors.newFixedThreadPool(5);
CountDownLatch c1 = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
for(int j = 0; j < 100000; j++) {
try {
lock.lock();
sum++;
} finally {
lock.unLock();
}
}
c1.countDown();
});
}
c1.await();
System.out.println(sum);
executorService.shutdown();
}
}
class TestLock {
private AtomicBoolean locked = new AtomicBoolean();
public void lock() {
while (!locked.compareAndSet(false, true)) {}
}
public void unLock() {
locked.set(false);
}
}
結果
500000
lazySet
我們可以觀察到,大多數Atomic類中都有lazySet()這個方法,這個方法有什麼用呢?以AtomicInteger爲例,通過觀察源碼可以發現,變量value是通過volatile修飾的,volatile在保證value可見性的同時也會比不加volatile修飾的更浪費cpu性能。我們知道volatile是通過設置內存屏障來實現的,對於某些確定不需要加內存屏障的情況下,volatile勢必會浪費性能。所以Doug Lea大神提供也lazySet()這個方法提供了一個可優化的選項。當然,如果使用錯誤,會導致一些很嚴重的問題。
ABA 問題
假如一個值原來是A,變成了B,又變成了A,那麼CAS檢查時會發現它的值沒有發生變化,但是實際上卻變化了。
通過引入 AtomicStampedReference AtomicMarkableReference來解決問題
AtomicStampedReference 提供了boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) ,在原有需要提供預期值,更新值之外,還需要提供預期版本號和更新版本號,假如預期值符合預期,允許更新,但是版本號不符合預期,同樣不會更新成功。
AtomicStampedReference和AtomicMarkableReference的區別是
AtomicStampedReference關注最新的版本號,而AtomicMarkableReference只關注最新是否更改過,換個說法假如一個更新的過程是這樣的A->B->A->B->A,AtomicStampedReference關注最後的A到底是哪個版本號的A,而AtomicMarkableReference只關注A是否產生了ABA問題
原理
我們以AtomicInteger爲例
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
可以看出都是調用了unsafe的方法
Unsafe源碼
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
/**
* Atomically adds the given value to the current value of a field
* or array element within the given object <code>o</code>
* at the given <code>offset</code>.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param delta the value to add
* @return the previous value
* @since 1.8
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
可以看出getAndAddInt()就是通過自旋compareAndSwapInt()來實現的,接着看compareAndSwapInt()
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
最終又是通過 Atomic::cmpxchg來實現的
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
最後我們終於看到了最原始的彙編指令cmpxchg
cpmxchg
cmpxchg是彙編指令
作用:比較並交換操作數.
如:CMPXCHG r/m,r 將累加器AL/AX/EAX/RAX中的值與首操作數(目的操作數)比較,如果相等,第2操作數(源操作數)的值裝載到首操作數,zf置1。如果不等, 首操作數的值裝載到AL/AX/EAX/RAX並將zf清0
該指令只能用於486及其後繼機型。第2操作數(源操作數)只能用8位、16位或32位寄存器。第1操作數(目地操作數)則可用寄存器或任一種存儲器尋址方式。