4.1节关于compareAndSwapInt()方法的形参解释是不是有问题?

来源:3-1 线程安全性-原子性-atomic-1

shinysirius

2018-04-01

4.1节7分54秒左右,讲解CAS方法时

unsafe.compareAndSwapInt(var1, var2, var5, var5 + var4)

解释`var2`是原来的值,var5是底层的值,最后一个形参是更换的值。实现原理是var2与var5进行比较,如果相同则硬件把值置为var5+var4。

但我查看调用:

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

发现`var2`是valueOffset,即AtomicInteger中值的地址偏移量,这个值是不会变的。百度查阅资料后发现,正确的流程解释应该是:

`var2`是AtomicInteger的value的相对地址值,`var5`是期望值,`var5 + var4`是置换值,与compareAndSet类似,每次循环调本地方法时,传最新的预期值,和符合修改值。由本地方法中硬件层具体实现,如果预期值和最新值相同,将AtomicInteger对象的value值改为符合修改值

写回答

5回答

wwpbjing

2018-11-03

尝试班门弄斧地解答:
我最初对于Java语言的理解是:Java无法像C/C++那样使用指针操作内存,
其实Java语言偷偷地埋了个后门:sun.misc.Unsafe(Java9开始计划废弃),
这个类可以完成:
1、分配/重新分配/释放内存,(allocateMemory/reallocateMemory/freeMemory)
2、获得对象中某个属性/静态属性的在该对象中的偏移地址(objectFieldOffset/staticFieldOffset),
3、使用CAS等CPU的操作原语(compareAndSwapObject/compareAndSwapInt/compareAndSwapLong)
4、存取普通变量/volatile变量(putObject/getObject/putObjectVolatile/getObjectVolatile...)
5、线程挂起/恢复(park/unpark...)
6、内存屏障(loadFence/storeFence)
7、synchronized关键字中monitor对象锁定解锁(monitorEnter/monitorExit)
...
这些方法是Java很多底层功能的实现基石
1、NIO直接内存(Direct Memory,参见java.nio.DirectByteBuffer类)
2、大部分Unsafe类中的方法都需要一个类型为long的offset参数:
 eg:

  public native Object getObject(Object obj, long offset);
  public final native boolean compareAndSwapInt(obj, offset, expect, update);

3、可用于实现基于CAS的乐观锁等操作
4、存取变量/volatile变量
5、java.util.concurrent.locks.LockSupport类中park/unpark系列方法实现的基础,LockSupport又是J.U.C实现的基础,用以实现线程挂起/恢复
6&7、还需要结合JVM才能彻底明白,底层的东西,自己也半吊子,就省略了
再多插一句:
sun.misc.Unsafe类的静态工厂方法比较有意思:

public static Unsafe getUnsafe() {
 Class var0 = Reflection.getCallerClass();
 if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
  throw new SecurityException("Unsafe");
 } else {
  return theUnsafe;
 }
}

getUnsafe方法会验证类加载器,只有启动类加载器加载的类才可以调用Unsafe.getUnsafe方法,
自己写的类由于是应用(系统)类加载器加载的就会抛异常:SecurityException
此举显然是JDK开发者不希望应用开发者调用Unsafe类中的方法,毕竟这个类功能太过强大也太过底层,
使用不当是有很大风险的,当然我们可以使用Java黑魔法————反射拿到Unsafe实例
我提供两种反射思路拿到Unsafe实例:

// 获取Unsafe类中属性"theUnsafe"获取实例
try {
 Field field = Unsafe.class.getDeclaredField("theUnsafe");
 field.setAccessible(true);
 Unsafe unsafe = (Unsafe) field.get(null);
 System.out.println(unsafe);
} catch (NoSuchFieldException | IllegalAccessException e) {
 e.printStackTrace();
}
// 通过私有构造器创建实例
try {
 Constructor<Unsafe> c = Unsafe.class.getDeclaredConstructor();
 c.setAccessible(true);
 Unsafe unsafe = c.newInstance();
 System.out.println(unsafe);
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
 e.printStackTrace();
}

有了Unsafe实例,我们就可以玩了:

public class CASDemo {
    private static Unsafe unsafe;
    private int count = 0;
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception {
        CASDemo instance = new CASDemo();
        // 使用反射API获取count变量的Field
        Field countVariableField = CASDemo.class.getDeclaredField("count");
        //  使用Unsafe类中objectFieldOffset方法获取CASDemo类中count属性的偏移地址:offset
        long countVariableAddressOffset = unsafe.objectFieldOffset(countVariableField);
        System.out.println("CASDemo类中count属性内存偏移地址:" + countVariableAddressOffset);
        // 我们来玩compareAndSwapInt了
        boolean success = unsafe.compareAndSwapInt(instance, countVariableAddressOffset, 0, 1);
        System.out.println("CAS 成功:" + success);
        System.out.println("经过CAS,count值变为:" + instance.count);
    }
}

另外对于x86 CPU,CAS实际是一条名为“cmpxchg”的指令,了解即可。

最后希望这个回答你能彻底明白

0
0

birdskyws

2018-07-12

感谢 shinysirius

学习的时候,也卡在这块了,老师没说清楚。看同学的解释大概是什么意思了。把var5交给底层判断,如果var5没变,那么改为var5+var4,即修改为目的结果。

0
1
一颗小沙砾
我也这么理解的,是这样的么?
2018-10-12
共1条回复

Jimin

2018-04-02

我看了一下视频,把传入的变量当时看错了,因此把var2的值说错了。

借这个问题,再总结一下:

compareAndSwapInt(var1, var2, var5, var5 + var4)换成 compareAndSwapInt(obj, offset, expect, update)能清楚一些,如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步CAS没有成功,那就采用自旋的方式继续进行CAS操作。这块是一个CPU指令完成的,依旧是原子操作。

0
4
风洛洛
回复
birdskyws
其实对于老师后来给的compareAndSwapInt(obj, offset, expect, update)能更好理解。 传入对象,然后会首先通过getIntVolatile 查询对象引用地址offset偏移量的值,得到expect,然后在调用compareAndSwapInt,这里的操作内部应该是,原子操作,对对象偏移量offset地址的值比对expect,如果发现相等就直接赋值为update,如果发现不等则继续循环直到相等为止。 这么做主要原因是getIntVolatile查询之后,在赋值的时候不能保证没有其他线程修改了这个偏移量地址位置的值,所以还需要比对,但是因为compareAndSwapInt是原子的,这里如果比对相等赋值是原子操作,所以是可以信任的。
2018-11-13
共4条回复

Jimin

2018-04-01

你好,你说的这个没问题,我现在不方便看视频,等我确认一下

0
0

shinysirius

提问者

2018-04-01

这个地方今天看的时候困惑了半天

0
0

Java高并发编程,构建并发知识体系,提升面试成功率

构建完整并发与高并发知识体系,倍增高薪面试成功率!

3923 学习 · 832 问题

查看课程