最近在学习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();
}
}
打印结果:
可见,两次比较并更新均成功了。
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();
}
}
打印结果:
这次出现了问题:第一次更新成功,第二次更新失败。
两段代码唯一不一样的地方就是第一次更新的值,前者为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时,第二次无法更新成功的问题。