Java Concurrency — 原子变量类

《Java并发编程实践》第15章 原子变量与非阻塞同步机制 读书笔记

Java中的对共享变量访问的同步机制

  1. synchronized内部锁
  2. 显示锁
  3. volatile
  4. 原子变量

volatile变量 VS 锁

  1. volatile变量更轻量级,因为它们不会引起上下文的切换和线程的调度。对于锁,当频繁地发生锁的竞争时,上下文切换和调度开销可能远大于工作开销。
  2. 都提供了可见性保证。
  3. volatile变量不能用于构建原子化的复合操作,例如i++。
  4. 加锁的缺点。当一个线程正在等待锁时,它不能做任何事情。如果另一个线程在持有锁的情况下发生延迟(原因包括页错误、调度延迟等),那么其他所有需要该锁的线程都不能前进了。

原子变量类(Atomic Variable Classes)

于是,就需要类似于volatile变量的机制,并且还要支持原子化更新的技术。原子变量类就满足了这样的需求。
原子变量类共12个,分4组

  1. 计数器
    AtomicInteger AtomicLong AtomicBoolean AtomicReference
  2. 域更新器(field updater)
    AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater
  3. 数组
    AtomicIntegerArray AtomicLongArray AtomicReferenceArray
  4. 复合变量
    AtomicMarkableReference AtomicStampedReference

Atomic类的实现依赖于冲突监测,从而能判定更新过程中是否存在来自于其他成员的干涉,在冲突发生的情况下,操作失败,并会重试(也可能不重试)。
现代的处理器提供了原子化的读-改-写指令,如比较并交换(compare-and-swap)CAS有3个操作数,内存位置V,旧的预期值A,新值B。当且仅当V等于旧的预期值A时,CAS用新值B原子化地更新V的值,否则什么都不会做。在任何一种情况下,都会返回V的真实值。
若处理器不支持这样的指令,JVM会使用自旋锁

利用AtomicInteger实现i++复合操作。

AtomicInteger i = new AtomicInteger(0); i.incrementAndGet();

相关Java源码

package java.util.concurrent.atomic;
import sun.misc.Unsafe;

public class AtomicInteger extends Number implements java.io.Serializable {

    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

incrementAndGet方法体内是一个for循环,表示如果冲突发生,就不断重试,直到compareAndSet方法返回true。
compareAndSet方法调用的是Unsafe类的compareAndSwapInt方法,该方法是一个native方法。

public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

该native方法的最终实现源码路径为openjdk\hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp

// Adding a lock prefix to an instruction on MP machine // VC++ doesn't like the lock prefix to be on a single line // so we can't insert a label after the lock prefix. // By emitting a lock prefix, we can define a label after it. #define LOCK_IF_MP(mp) __asm cmp mp, 0 \ __asm je L0 \ __asm _emit 0xF0 \ __asm L0: 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
  }
}

如源代码所示,如果是多处理器机器,LOCK_IF_MP(mp)就会为cmpxchg指令加上lock前缀。原因是有可能多个处理器同时从各自的缓存中读取共享变量,分别进行操作,然后分别写入系统内存当中。这时就需要加锁,其中一种方式就是总线锁。

ABA问题

CAS只检测”V的值是否仍为A“,然而可能V的变化过程是A -> B -> A,这就是ABA问题。要知道“V的值在我上次观察后是否发生变化”,一种解决方案是,更新一对值,包括引用和版本号。A改为B,又改回A,版本号不同。AtomicStampedReference或AtomicMarkableReference提供了一对变量原子化的条件更新。
AtomicStampedReference更新对象引用的整数对
AtomicMarkableReference更新对象引用的布尔对

你可能感兴趣的:(java,并发,atomic)