AtomicReference的compareAndSet更新问题

最近在学习AtomicInteger可能导致的ABA问题以及解决办法。

当学习到原子引用时,遇到了一个问题。不多说,开始:

一、问题代码

DEMO-1:

public class ABADemo {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(atomicReference.compareAndSet(100, 127));
            System.out.println(atomicReference.compareAndSet(127, 100));
            System.out.println(atomicReference.get());
        },"T1").start();
    }
}

打印结果:

b6727d921413096cb0c2f52348a27a5f8c2.jpg

可见,两次比较并更新均成功了。

DEMO-2:

public class ABADemo {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(atomicReference.compareAndSet(100, 128));
            System.out.println(atomicReference.compareAndSet(128, 100));
            System.out.println(atomicReference.get());
        },"T1").start();
    }
}

打印结果:

0e74248953908c065c955724aca45a065d5.jpg

这次出现了问题:第一次更新成功,第二次更新失败。

两段代码唯一不一样的地方就是第一次更新的值,前者为127,后者为128。

二、原因

  1. AtomicReference.compareAndSet通过“==”比较,即比较的内容是否为同一地址。

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

   2. 对于java的字面量,Java会对其自动装箱为其包装类对象。

          int通过Integer.value()方法包装成Integer对象,

          short通过Short.value()方法包装成Short对象,

          long通过Long.value()方法包装成Long对象。

    以int类型为例:Integer.valueOf源码:

   /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    由源码可知,int再包装为Integer时,如果int值在-128到127之间,会直接从缓存中获取;否则会直接new一个新的Integer对象。(缓存最大值一般就是127,有可能会通过虚拟机配置改为其他值)

    所以在我们从100更新为128时,对于128是new了一个新对象;128再更新成100时,对于128会再new一个新对象。相当于两次比较并替换操作里的128,是两个不同的对象,用==比较,自然是返回false。

    所以就出现了我上面的情况。

三、short和long

1. short

    /**
     * Returns a {@code Short} instance representing the specified
     * {@code short} value.
     * If a new {@code Short} instance is not required, this method
     * should generally be used in preference to the constructor
     * {@link #Short(short)}, as this method is likely to yield
     * significantly better space and time performance by caching
     * frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  s a short value.
     * @return a {@code Short} instance representing {@code s}.
     * @since  1.5
     */
    public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }

2. long 

    /**
     * Returns a {@code Long} instance representing the specified
     * {@code long} value.
     * If a new {@code Long} instance is not required, this method
     * should generally be used in preference to the constructor
     * {@link #Long(long)}, as this method is likely to yield
     * significantly better space and time performance by caching
     * frequently requested values.
     *
     * Note that unlike the {@linkplain Integer#valueOf(int)
     * corresponding method} in the {@code Integer} class, this method
     * is <em>not</em> required to cache values within a particular
     * range.
     *
     * @param  l a long value.
     * @return a {@code Long} instance representing {@code l}.
     * @since  1.5
     */
    public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }

所以对于short和long类型的ABA比较并替换操作,也会出现大于等于128时,第二次无法更新成功的问题。

转载于:https://my.oschina.net/alexjava/blog/3089074

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AtomicReference.compareAndSetJavaAtomicReference类的一个方法,用于比较当前引用和预期引用是否相等,并在相等的情况下将引用设置为新的值。该方法是原子性操作,可以用于实现多线程环境下的线程安全。 下面是一个示例代码,演示了AtomicReference.compareAndSet的用法: ```java import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceExample { public static void main(String[] args) { AtomicReference<String> atomicReference = new AtomicReference<>("Hello"); // 预期引用为"Hello",当前引用为"Hello",比较相等,设置新的引用为"World" boolean result1 = atomicReference.compareAndSet("Hello", "World"); System.out.println("Result 1: " + result1); // 输出:Result 1: true System.out.println("Current value: " + atomicReference.get()); // 输出:Current value: World // 预期引用为"Hello",当前引用为"World",比较不相等,不修改引用 boolean result2 = atomicReference.compareAndSet("Hello", "Java"); System.out.println("Result 2: " + result2); // 输出:Result 2: false System.out.println("Current value: " + atomicReference.get()); // 输出:Current value: World } } ``` 在这个示例中,我们创建了一个AtomicReference对象,并初始设置为"Hello"。然后我们分别调用compareAndSet方法来尝试修改引用的值。 第一个调用compareAndSet方法时,预期引用为"Hello",当前引用也为"Hello",因此比较相等,设置新的引用为"World"。返回值为true,表示修改成功。最后输出当前引用的值为"World"。 第二个调用compareAndSet方法时,预期引用为"Hello",当前引用已经被修改为"World",不再与预期引用相等,因此不会修改引用的值。返回值为false,表示修改失败。最后输出当前引用的值仍为"World"。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值