JUC并发工具九-Atomic类

目录

 

1 Atomic类作用

2 AtomicInteger使用

2.1 AtomicInteger用法

2.2 AtomicInteger原理

2.2.1 重要成员变量

2.2.2 addAndGet方法

2.3 AtomicInteger优缺点

3 AtomicReference使用

3.1 AtomicReference用法

3.2 AtomicReference原理

3.2.1 重要成员变量

3.2.3 updateAndGet方法

3.3.3 AtomicReference使用误区

4 ABA问题以及AtomicStampedReference的使用

4.1 ABA问题

4.2 AtomicStampedReference使用

4.3 AtomicStampedReference原理

4.3.1 重要成员变量

4.3.2 compareAndSet方法


1 Atomic类作用

前面文章我们知道volatile只能保证可见性,不能保证原子性,下面我们通过代码证明一下

// 定义一个volatile变量
private volatile int value = 0;

@Test
public void volatileTest() throws InterruptedException {
    // 循环10000次,每次自增1
    for (int i = 0; i < 10000; i ++) {
        new Thread(() -> value ++).start();
    }
    TimeUnit.SECONDS.sleep(1);
    System.out.println(value);
}

通过测试我们可以看到程序输出结果不确定的,大都达不到10000,可以说明transient不能保证原子性。
为了满足jvm内存模型原子性特性,juc并发包提供了Atomic类来保证原子性。

2 AtomicInteger使用

2.1 AtomicInteger用法

同样的功能我们用AtomicInteger来测试

private AtomicInteger value = new AtomicInteger();

@Test
public void atomicIntegerTest() throws InterruptedException {
    for (int i = 0; i < 10000; i ++) {
        new Thread(() -> value.addAndGet(1)).start();
    }
    TimeUnit.SECONDS.sleep(1);
    System.out.println(value);
}

每次输出的结果都是10000,可以说明AtomicInteger保证原子性

2.2 AtomicInteger原理

AtomicInteger原理也比较简单,就是前面文章讲过的CAS自旋volatile变量。

2.2.1 重要成员变量

// 定义一个volatile变量
private volatile int value;

// value属性的内存偏移量,在static块中初始化
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

2.2.2 addAndGet方法

public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

/**
* var1:当前对象
* var2:某个属性的内存地址偏移量
* var4: 增量
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // 通过当前对象和和某个属性内存偏移量读取属性值
        var5 = this.getIntVolatile(var1, var2);
        // 循环给对象赋值,直到成功
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

2.3 AtomicInteger优缺点

优点:无锁,效率高
缺点:cpu占用较高,只能保证一个共享变量原子操作,ABA问题
解决方案
    1 无解,高性能是建立在高CPU占用的基础上实现的
    2 AtomicReference
    3 AtomicStampedReference (多了一个stamp变量,可以理解为版本号)

3 AtomicReference使用

3.1 AtomicReference用法

创建一个员工类,有一个id和age属性,多线程对这个员工的id和age同时+1,循环10000次,观察是否会有并发问题

@Data
public class Employee implements Serializable {

    private static final long serialVersionUID = 7773021982257206440L;

    private int id;
    
    private int age;

测试代码

@Test
public void atomicReferenceTest() throws InterruptedException {
    for (int i = 0; i < 10000; i ++) {
        new Thread(() -> employeeAtomicReference.updateAndGet(employee -> {
            Employee newEmp = new Employee();
            newEmp.setId(employee.getId() + 1);
            newEmp.setAge(employee.getAge() + 1);
            return newEmp;
        })).start();
    }
    TimeUnit.SECONDS.sleep(1);
    System.out.println(employeeAtomicReference.get());
}

输出Employee{id=10000, age=10000}

3.2 AtomicReference原理

3.2.1 重要成员变量

// 定义一个泛型volatile变量
private volatile V value;

// value属性的内存偏移量,在static块中初始化
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

3.2.3 updateAndGet方法

public final V updateAndGet(UnaryOperator<V> updateFunction) {
    V prev, next;
    do {
        // 获取当前对象
        prev = get();
        // 获取执行后的对象
        next = updateFunction.apply(prev);
        // CAS更新对象为next
    } while (!compareAndSet(prev, next));
    return next;
}

/**
* expect 希望的当前值
* update 要修改的值
*/
public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

3.3.3 AtomicReference使用误区

在使用AtomicReference的时候做对象操作的时候一定要用一个新建的对象,否则不能保证原子性
错误用例:

@Test
public void atomicReferenceTest() throws InterruptedException {
    for (int i = 0; i < 10000; i ++) {
        new Thread(() -> employeeAtomicReference.updateAndGet(employee -> {
            // 这里直接用原来的对象进行修改
            employee.setId(employee.getId() + 1);
            employee.setAge(employee.getAge() + 1);
            return employee;
        })).start();
    }
    TimeUnit.SECONDS.sleep(1);
    System.out.println(employeeAtomicReference.get());
}

输出:Employee{id=9995, age=9996}
问题原因:
看CAS参数compareAndSet(prev, next)方法,prev参数和next参数实际上都是AtomicReference内部引用的employee对象
所以该方法始终返回true,达不到并发控制效果

4 ABA问题以及AtomicStampedReference的使用

4.1 ABA问题

整个CAS过程分为3步,查询当前值,比较希望的值(第一步取到的当前值)和当前值,修改。这里假设有3个线程操作数据

上个流程图可知线程一先获取数据,然后挂起。
线程二和线程三修改数据,但是值没有变。
这个时候线程一不能感知数据已经被修改过,进行CAS修改成功。这就是ABA问题

4.2 AtomicStampedReference使用

AtomicStampedReference解决ABA问题的方法也很简单,在原有的CAS方法在expectValue的基础上多了一个expectStamp条件的判断,
不仅expectedValue要和当前值相等,expectedStamp也要和当前值相等。
测试代码

private AtomicStampedReference<Integer> value = new AtomicStampedReference<>(0, 0);

@Test
public void atomicReferenceTest() throws InterruptedException {
    System.out.println(value.compareAndSet(0, 1, 1, 1));
    System.out.println(value.getReference() + " " + value.getStamp());
    System.out.println(value.compareAndSet(0, 1, 0, 1));
    System.out.println(value.getReference() + " " + value.getStamp());
}

输出
false
0 0
true
1 1

4.3 AtomicStampedReference原理

4.3.1 重要成员变量

// 定义一个静态内部类,维护reference和stamp两个属性
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);
    }
}

// 定义一个volatile的Pair对象
private volatile Pair<V> pair;

private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

4.3.2 compareAndSet方法

public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    // CAS的时候要保证expectedReference和当前reference相等,expectedStamp和当前stamp相等,才能进行CAS处理
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}

// CAS修改成员变量pair
private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值