AtomicInteger 的值保存在 value 中,通过 volatile 保证操作的可见性,通过一个静态代码块来保证,类被加载时 valueOffset 已经有值了
Unsafe 是一个不安全的类,提供了一些对底层的操作,我们是不能使用这个类的,valueOffset 是 AtomicInteger 对象 value 成员变量在内存中的偏移量
1 2 3
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
1 2 3 4 5 6 7 8 9 10 11 12 13
//第一个参数为当前这个对象,如count.getAndIncrement(),则这个参数则为count这个对象 //第二个参数为AtomicInteger对象value成员变量在内存中的偏移量 //第三个参数为要增加的值 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //调用底层方法得到value值 var5 = this.getIntVolatile(var1, var2); //通过var1和var2得到底层值,var5为当前值,如果底层值=当前值,则将值设为var5+var4,并返回true,否则返回false } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5; }
这个方法是由其他语言实现的,就不再分析
1
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
并发比较低的时候用 CAS 比较合适,并发比较高用 synchronized 比较合适
CAS的缺点
只能保证对一个变量的原子性操作 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
长时间自旋会给 CPU 带来压力 我们可以看到 getAndAddInt 方法执行时,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。
ABA 问题 如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为 A,那我们就能说它的值没有被其他线程改变过了吗?
如果在这段期间它的值曾经被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。这个漏洞称为 CAS 操作的“ABA”问题。Java 并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证 CAS 的正确性。因此,在使用 CAS 前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。