前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发-当compareAndSwapObject遇到自动装箱问题详解

Java并发-当compareAndSwapObject遇到自动装箱问题详解

作者头像
Fisherman渔夫
发布2020-02-18 16:36:41
8420
发布2020-02-18 16:36:41
举报
文章被收录于专栏:渔夫渔夫

一、引子

 这篇文章用于记录我在尝试测试使用CAS机制下的compareAndSwapObject方法所遇到的问题:我的目的是想通过compareAndSwapObject方法调用是否能够满足“若不相同,则不更新”的性质,但是发现其总是返回false,后来意识到是int值自动装箱所导致的问题。接下来就来看代码吧。 compareAndSwapObject方法简介:

            /**
             * compareAndSwapObject(Object var1, long var2, Object var3, Object var4)
             * var1 操作的对象
             * var2 操作的对象属性
             * var3 var2与var3比较,相等才更新
             * var4 更新值
             */

二、int属性值使用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比较的是两个属性的引用地址是否一致。

三、防止自动装箱的CAS方法

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值得到更新;

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引子
  • 二、int属性值使用compareAndSwapObject方法遇到的自动装箱问题
  • 三、防止自动装箱的CAS方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档