【并发】原子操作类

一. 原子更新基本类型

使用原子的方式更新基本类型,Atomic包提供了以下3个类:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

这三个类的用法和原理类似,这里以AtomicInteger为例详细分析下其内部原理。

AtomicInteger

核心属性
private static final Unsafe unsafe = Unsafe.getUnsafe();
//valueOffset即为字段value的内存偏移地址
private static final long valueOffset;

private volatile int value;

static {
    try {
        //通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
常用方法
1. getAndSet(int newValue)

getAndSet(int newValue)的作用是返回旧值,原子设置新值。

public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}

从源码可知,调用Unsafe类的getAndSetInt()方法,其源码如下:

public final int getAndSetInt(Object o, long offset, int newValue) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, newValue));
    return v;
}

getAndSetInt()原理也很简单,主要通过调用native方法compareAndSwapInt()来实现原子更新。

2. compareAndSet(int expect, int update)

compareAndSet(int expect, int update)方法为典型的CAS操作,直接调用Unsage类的native方法compareAndSwapInt()来进行原子更新。

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

getAndIncrement()方法的作用是对旧值加1,并返回旧值。

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

其主要调用了Unsafe的getAndAddInt()方法,代码如下:

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;
}

其他的几个方法的原理也是类似,这里不再赘述,代码还是比较简单易读的。

二. 原子更新数组

使用原子的方式更新数组里的某个元素,Atomic包提供了以下3个类:

  • AtomicIntegerArray: 原子更新整型数组里的元素
  • AtomicLongArray: 原子更新长整型数组里的元素。
  • AtomicReferenceArray: 原子更新引用类型数组里的元素。

这三个类的用法和原理类似,这里以AtomicIntegerArray为例详细分析下其内部原理。

AtomicIntegerArray

核心属性
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;

static {
    int scale = unsafe.arrayIndexScale(int[].class);
    if ((scale & (scale - 1)) != 0)
        throw new Error("data type scale not a power of two");
    shift = 31 - Integer.numberOfLeadingZeros(scale);
}
常用方法
1. addAndGet(int i, int delta)
  • addAndGet(int i, int delta)方法以原子方式将输入值delta与数组中索引i的元素相加,并返回新值。

  • getAndAdd(int i, int delta)方法以原子方式将输入值delta与数组中索引i的元素相加,并返回旧值。

public final int addAndGet(int i, int delta) {
    return getAndAdd(i, delta) + delta;
}

public final int getAndAdd(int i, int delta) {
    return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}

Unsafe类的getAndAddInt(Object o, long offset, int delta)方法通过CAS的方式原子更新对象o的属性值。

//通过CAS的方式将对象o的属性加delta,并返回旧值。
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;
}
2. compareAndSet(int i, int expect, int update)

compareAndSet方法会判断当前值如果等于预期值,则以原子方式将数组位置i的元素设置成update值。

public final boolean compareAndSet(int i, int expect, int update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

private boolean compareAndSetRaw(long offset, int expect, int update) {
    return unsafe.compareAndSwapInt(array, offset, expect, update);
}

三. 原子更新引用类型

通过原子的方式更新引用类型,Atomic包提供了3个类:

  • AtomicReference: 原子更新引用类型
  • AtomicReferenceFieldUpdater: 原子更新引用类型里的字段
  • AtomicMarkableReference: 原子更新带有标记位的引用类型,可以原子更新一个布尔类型的标记位和引用类型。

AtomicReference

核心属性
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        //通过字段valueOffset的值可以定位到对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。
        valueOffset = unsafe.objectFieldOffset
            (AtomicReference.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile V value;
常用方法
1. compareAndSet(V expect, V update)
public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

简单示例:

public static void main(String[] args) {
    AtomicReference<User> userRef = new AtomicReference<>();
    boolean res1 = userRef.compareAndSet(null, new User(1, "1"));
    System.out.println(res1);
    System.out.println(userRef.get());
    //需要同一个对象才能更新成功
    boolean res2 = userRef.compareAndSet(new User(1, "1"), new User(2, "2"));
    System.out.println(res2);
    System.out.println(userRef.get());
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class User {
    private Integer id;
    private String name;
}

输出为:

true
App.User(id=1, name=1)
false
App.User(id=1, name=1)

AtomicReferenceFieldUpdater

AtomicReferenceFieldUpdater的主要作用是可对指定类的指定volatile字段进行原子更新。

示例如下:

public class Node {
    private volatile Node left, right;

    private static final AtomicReferenceFieldUpdater<Node, Node> leftUpdater =
      AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
    
    private static AtomicReferenceFieldUpdater<Node, Node> rightUpdater =
      AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");

    public Node getLeft() { return left; }

    public boolean compareAndSetLeft(Node expect, Node update) {
      return leftUpdater.compareAndSet(this, expect, update);
    }
}

AtomicMarkableReference

AtomicMarkableReference类封装了一个对象的引用reference和一个布尔值mark,可以原子性地对这两个值进行更新。

核心属性
private static class Pair<T> {
    final T reference;
    final boolean mark;
    private Pair(T reference, boolean mark) {
        this.reference = reference;
        this.mark = mark;
    }
    static <T> Pair<T> of(T reference, boolean mark) {
        return new Pair<T>(reference, mark);
    }
}

private volatile Pair<V> pair;
常用方法
1. 构造函数
public AtomicMarkableReference(V initialRef, boolean initialMark) {
    pair = Pair.of(initialRef, initialMark);
}
2. get(boolean[] markHolder)
//markHolder[0]返回标记,返回值返回引用对象
public V get(boolean[] markHolder) {
    Pair<V> pair = this.pair;
    markHolder[0] = pair.mark;
    return pair.reference;
}
3. set(V newReference, boolean newMark)
//如果引用对象或者标记不同,则重新赋值
public void set(V newReference, boolean newMark) {
    Pair<V> current = pair;
    if (newReference != current.reference || newMark != current.mark)
        this.pair = Pair.of(newReference, newMark);
}
4. compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark)
//主要通过UnSafe类的CAS方法更新Pair对象
public boolean compareAndSet(V       expectedReference,
                             V       newReference,
                             boolean expectedMark,
                             boolean newMark) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedMark == current.mark &&
        ((newReference == current.reference &&
          newMark == current.mark) ||
         casPair(current, Pair.of(newReference, newMark)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

四. 原子更新字段

原子更新字段类可以原子地更新某个类里的某个字段,Atomic包提供了以下3个类进行原子字段更新。

  • AtomicIntegerFieldUpdater: 原子更新整型字段的更新器。
  • AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
  • AtomicStampedReference: 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

原子更新字段的流程有两步:

  1. 原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要指定想要更新的类和属性。
  2. 更新类的字段(属性)必须使用public volatile修饰符。

AtomicIntegerFieldUpdater

下面的示例代码需要注意的是,age的类型必须为public volatile int,否则会报错,其他的方法使用和原理与AtomicInteger类似。

public static void main(String[] args) {
    AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
    User user = new User(1, 1);
    //加1并返回更新后的age
    System.out.println(updater.addAndGet(user, 1));
    //输出age
    System.out.println(updater.get(user));
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class User {
    private Integer id;
    public volatile int age;
}

AtomicStampedReference

核心属性

AtomicStampedReference类除了维护一个引用对象之外,还附带一个stamp属性,相当于版本号,可以避免CAS可能出现的ABA问题。

private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

private volatile Pair<V> pair;
常用方法
1. 构造函数
public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}
2. set(V newReference, int newStamp)
//设置时,reference或者stamp两者有一个不同就会更新pair
public void set(V newReference, int newStamp) {
    Pair<V> current = pair;
    if (newReference != current.reference || newStamp != current.stamp)
        this.pair = Pair.of(newReference, newStamp);
}
3. get(int[] stampHolder)
//stampHolder[0]返回stamp,返回值返回reference
public V get(int[] stampHolder) {
    Pair<V> pair = this.pair;
    stampHolder[0] = pair.stamp;
    return pair.reference;
}
4. compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
//与AtomicMarkableReference很类型,只不过AtomicMarkableReference的mark为boolean类型
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

五. 参考资料

  • 《Java并发编程的艺术》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值