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操作数(目地操作数)则可用寄存器或任一种存储器寻址方式。