这篇文章用于记录我在尝试测试使用CAS
机制下的compareAndSwapObject
方法所遇到的问题:我的目的是想通过compareAndSwapObject
方法调用是否能够满足“若不相同,则不更新”的性质,但是发现其总是返回false,后来意识到是int值自动装箱所导致的问题。接下来就来看代码吧。
compareAndSwapObject
方法简介:
/**
* compareAndSwapObject(Object var1, long var2, Object var3, Object var4)
* var1 操作的对象
* var2 操作的对象属性
* var3 var2与var3比较,相等才更新
* var4 更新值
*/
compareAndSwapObject
方法遇到的自动装箱问题import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author Fisherman
* @date 2019/9/20
*/
public class TestCAS {
private volatile int a =10;
private static final long aOffset;
private static final sun.misc.Unsafe UNSAFE;
static {
try {
UNSAFE = getUnsafe();
Class<?> k = TestCAS.class;
aOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("a"));
} catch (Exception e) {
throw new Error(e);
}
}
public boolean changeA(int var1,int var2){
return UNSAFE.compareAndSwapObject(this,aOffset,var1,var2);
}
/**
* 获取Unsafe对象的方法
* @return
*/
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
TestCAS testCAS = new TestCAS();
boolean flag = testCAS.changeA(10, 20);//这里因为自动int属性的自动包装而导致了总是返回false
System.out.println(flag);
System.out.println(testCAS.a);
}
}
控制台输出:
false
10
在对象的构造器调用时将int a
值初始化为10,输入也为10,但是CAS更新未成功的原因是由于:我们使用compareAndSwapObject``的内部机理是比较对象的引用地址,但是int数据不是对象,JVM就将其自动装箱为Integer类对象,而构造器中的10,一个输出参数的10构造了两个Integer对象,指向不同的地址引用,所以CAS机制认为其更新了,所以总是返回false;比方说以下代码:
public class Test {
public static void main(String[] args) {
Integer integer1 = new Integer(10);
Integer integer2 = new Integer(10);
System.out.println(integer1==integer2);
}
}
这里代码块也总是在控制台输出:false,原理是相同的,就是俩基本数据类型int值自动装箱为两个Integer对象,地址不同,==
不满足,CAS
机制也认为其不同;
如果将上述代码改变,将int类型值转为String,那么就不会有上述问题:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author Fisherman
* @date 2019/9/20
*/
public class TestCAS2 {
private volatile String str ="hello";
private static final long aOffset;
private static final sun.misc.Unsafe UNSAFE;
static {
try {
UNSAFE = getUnsafe();
Class<?> k = TestCAS2.class;
aOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("str"));
} catch (Exception e) {
throw new Error(e);
}
}
public boolean changeStr(String var1,String var2){
return UNSAFE.compareAndSwapObject(this,aOffset,var1,var2);
}
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
TestCAS2 testCAS2 = new TestCAS2();
boolean flag = testCAS2.changeStr("hello", "Fisherman");
System.out.println(flag);
System.out.println(testCAS2.str);
}
}
控制台输出:
true
Fisherman
但是如果将上述代码改为:
boolean flag = testCAS2.changeStr(new String("hello"), "Fisherman");
不仅仅IDE会给出提示:不推荐以此方式创建字符串对象,而且flag的值被设置为flase,这再一次印证了一点:compareAndSwapObject
比较的是两个属性的引用地址是否一致。
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author Fisherman
* @date 2019/9/20
*/
public class TestCAS3 {
private volatile int a =10;
private static final long aOffset;
private static final sun.misc.Unsafe UNSAFE;
static {
try {
UNSAFE = getUnsafe();
Class<?> k = TestCAS.class;
aOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("a"));
} catch (Exception e) {
throw new Error(e);
}
}
public boolean changeA(int var1,int var2){
return UNSAFE.compareAndSwapInt(this,aOffset,var1,var2);
}
/**
* 获取Unsafe对象的方法
* @return
*/
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
TestCAS3 testCAS3 = new TestCAS3();
boolean flag = testCAS3.changeA(10, 20);//这里因为自动int属性的自动包装而导致了总是不对
System.out.println(flag);
System.out.println(testCAS3.a);
}
}
控制台输出:
true
20
我们只需将CAS方法由:compareAndSwapObject
方法转为compareAndSwapInt()
方法就能够防止自动装箱引起的问题,这里直接比较的是int值,所以可以使用CAS机制使int a
值得到更新;