赞
踩
atomic包下原子操作类提供了一种用法简单、性能高效 、线程安全地更新一个变量的方式。
atomic包下一共有12个相关的类,分为4组,分别用于原子更新基本类型,原子更新数组,原子更新引用,原子更新字段。
原子更新基本类型
AtomicBoolean AtomicInteger AtomicLong
常用方法,以AtomicInteger为例
原子更新数组
AtomicIntegerArray AtomicLongArray AtomicReferenceArray
常用方法,同样以AtomicIntegerArray为例
原子更新字段
AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater
常用方法,同样以AtomicIntegerFieldUpdater为例
原子更新引用
AtomicReference AtomicMarkableReference AtomicStampedReference
常用方法,以AtomicReference为例
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
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
我们可以观察到,大多数Atomic类中都有lazySet()这个方法,这个方法有什么用呢?以AtomicInteger为例,通过观察源码可以发现,变量value是通过volatile修饰的,volatile在保证value可见性的同时也会比不加volatile修饰的更浪费cpu性能。我们知道volatile是通过设置内存屏障来实现的,对于某些确定不需要加内存屏障的情况下,volatile势必会浪费性能。所以Doug Lea大神提供也lazySet()这个方法提供了一个可优化的选项。当然,如果使用错误,会导致一些很严重的问题。
假如一个值原来是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操作数(目地操作数)则可用寄存器或任一种存储器寻址方式。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。