zoukankan      html  css  js  c++  java
  • 4.4.4 无锁的对象引用:AtomicReference和AtomicStampedReference

    AtomicReference 这个类和AtomicInteger非常类似,只是AtomicReference对应普通的对象引用,而AtomicInteger 它是对整数的封装,它的方法如下

      对weakCompareAndSet 说明:

    第一次看weakCompareAndSet doc文档的说明时,我是困惑的。我并不清楚你说的“fail spuriously”和“not provide ordering guarantees”的确切含义。于是我查询了些相关资料。

    首先,我从jdk 8 的官方文档的java.util.concurrent.atomic上找到这么二段话:

    The atomic classes also support method weakCompareAndSet, which has limited applicability. On some platforms, the weak version may be more efficient than compareAndSet in the normal case, but differs in that any given invocation of the weakCompareAndSet method may return false spuriously (that is, for no apparent reason). A false return means only that the operation may be retried if desired, relying on the guarantee that repeated invocation when the variable holds expectedValue and no other thread is also attempting to set the variable will eventually succeed. (Such spurious failures may for example be due to memory contention effects that are unrelated to whether the expected and current values are equal.) Additionally weakCompareAndSet does not provide ordering guarantees that are usually needed for synchronization control. However, the method may be useful for updating counters and statistics when such updates are unrelated to the other happens-before orderings of a program. When a thread sees an update to an atomic variable caused by a weakCompareAndSet, it does not necessarily see updates to any other variables that occurred before the weakCompareAndSet. This may be acceptable when, for example, updating performance statistics, but rarely otherwise.

    一个原子类也支持weakCompareAndSet方法,该方法有适用性的限制。在一些平台上,在正常情况下weak版本比compareAndSet更高效,但是不同的是任何给定的weakCompareAndSet方法的调用都可能会返回一个虚假的失败( 无任何明显的原因 )。一个失败的返回意味着,操作将会重新执行如果需要的话,重复操作依赖的保证是当变量持有expectedValue的值并且没有其他的线程也尝试设置这个值将最终操作成功。( 一个虚假的失败可能是由于内存冲突的影响,而和预期值(expectedValue)和当前的值是否相等无关 )。此外weakCompareAndSet并不会提供排序的保证,即通常需要用于同步控制的排序保证。然而,这个方法可能在修改计数器或者统计,这种修改无关于其他happens-before的程序中非常有用。当一个线程看到一个通过weakCompareAndSet修改的原子变量时,它不被要求看到其他变量的修改,即便该变量的修改在weakCompareAndSet操作之前。

    weakCompareAndSet atomically reads and conditionally writes a variable but does not create any happens-before orderings, so provides no guarantees with respect to previous or subsequent reads and writes of any variables other than the target of the weakCompareAndSet.

    weakCompareAndSet实现了一个变量原子的读操作和有条件的原子写操作,但是它不会创建任何happen-before排序,所以该方法不提供对weakCompareAndSet操作的目标变量以外的变量的在之前或在之后的读或写操作的保证。

    这二段话是什么意思了,也就是说weakCompareAndSet底层不会创建任何happen-before的保证,也就是不会对volatile字段操作的前后加入内存屏障。因为就无法保证多线程操作下对除了weakCompareAndSet操作的目标变量( 该目标变量一定是一个volatile变量 )之其他的变量读取和写入数据的正确性。

    AtomicReference 这个类有个问题 就是 如果有2个线程改变了值,最后改回它获取的值,那么程序也会继续执行,可能造成重复执行。例如:为账户低于多少钱用户赠送仅仅-张优惠卷,如果他在获得一张之后,充值了钱又花费了,就会造成多次操作。测试类:

    public class AtomicInteget {
    public static void main(String[] args) {
    AtomicReference<Integer> atomicReference=new AtomicReference<Integer>();
    Integer integer = atomicReference.get();
    boolean b = atomicReference.compareAndSet(integer, 5);
    System.out.println(b);
    boolean b1 = atomicReference.compareAndSet(integer, 6);
    System.out.println(b1);
    boolean b2 = atomicReference.compareAndSet(atomicReference.get(), null);
    System.out.println(b2);
    System.out.println(atomicReference.get());
    boolean b3 = atomicReference.compareAndSet(integer, 6);
    System.out.println(b3);
    System.out.println(integer);
    System.out.println(atomicReference.get());
    }
    }
    结果如下:

    true
    false
    true
    null
    true
    null
    6

    jdk针对于这种情况,有一个类专门处理这种问题:

    AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);

    public static void main(String args[]) {

    for (int i = 0; i < 100; i++) {
    final int timestap = money.getStamp();
    new Thread() {
    public void run() {
    while (true) {
    Integer m = money.getReference();
    if (m < 20) {
    if (money.compareAndSet(m, m + 20, timestap, timestap + 1)) {
    System.out.println("余额小于20元,充值成功,余额:" + money.getReference() + "元");
    break;
    }
    } else {
    System.out.println("余额大于20,无需充值");
    break;
    }
    }
    }
    }.start();
    }

    new Thread() {
    public void run() {
    for (int i = 0; i < 100; i++) {

    while (true) {
    int timestap = money.getStamp();
    Integer m = money.getReference();
    if (m > 10) {
    System.out.println("金额大于10元");
    if (money.compareAndSet(m, m - 10, timestap, timestap + 1)) {
    System.out.println("成功消费10元,余额:" + money.getReference() + "元");
    break;
    }
    } else {
    System.out.println("没有足够的金额");
    break;
    }
    }
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }.start();

    }
    }

  • 相关阅读:
    JZOJ 3845. 简单题(simple)
    JZOJ 3844. 统计损失(count)
    JZOJ 3843. 寻找羔羊(agnus)
    JZOJ 3833. 平坦的折线
    JZOJ 1956. 矩形
    JZOJ 3832. 在哪里建酿酒厂
    mysql 语法一 :case when详解
    阿里云推荐码
    redis配置文件详解(转)
    压力测试工具 webbench总结
  • 原文地址:https://www.cnblogs.com/anxbb/p/8662649.html
Copyright © 2011-2022 走看看