JavaUnsafe类与CAS操作

JavaUnsafe类与CAS操作

前言

最近看java源码发现有多处地方都使用到了Unsafe类,于是在网上查阅资料教程.以下是个人总结

Unsafe简介

Unsafe两大功能:

  1. 直接通过内存地址 修改对象,获取对象引用
  2. 使用硬件指令 实现 原子操作 (CAS compare and swap)

Unsafe的使用:

  1. Unsafe是典型的单例模式,通过 public static Unsafe getUnsafe()获取实例

  2. 且 该方法被 @CallerSensitive所注解, 表明只能由系统类加载器加载的类所调用

  3. 为了在测试代码中使用Unsafe,可以通过反射获取该类的静态字段的实例

    Field f= Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe u = (Unsafe) f.get(null);
    

Unsafe API

获取偏移

  1. 获取成员变量在 对象中的偏移

    public native long objectFieldOffset(Field f);

  2. 获取静态成员所在 的类,返回Class对象

    public native Object staticFieldBase(Field f);

  3. 获取静态成员在 类中的偏移

    public native long staticFieldOffset(Field f);

  4. 获取数组首个元素 在数组对象中的偏移

    public native int arrayBaseOffset(Class arrayClass);

  5. 获取每个数组元素所占空间

    public native int arrayIndexScale(Class arrayClass);

根据 对象+偏移 获取或设置 对象中字段的引用或值

  1. 获取 对象var1内部中偏移为var2的 XXX类型字段的 值或引用

    public native byte getXxxx(Object var1, long var2);
    例如
       public native byte getByte(Object var1, long var2);
       public native int getInt(Object var1, long var2);
       public native double getDouble(long var1);
       public native boolean getBoolean(Object var1, long var2);
       public native Object getObject(Object var1, long var2);
    ......
    
  2. 设置对象var1内部中偏移为var2的 XXX类型字段的值 为var4

     public native void putBoolean(Object var1, long var2, boolean var4);
     public native void putByte(Object var1, long var2, byte var4);
     public native void putInt(Object var1, long var2, int var4);
     public native void putObject(Object var1, long var2, Object var4);
    ......
    
  3. volatile语义的get,put:表示多线程之间的变量可见,一个线程修改一个变量之后,另一个线程立刻能看到

    public native void putBooleanVolatile(Object var1, long var2, boolean var4);
    public native int getIntVolatile(Object var1, long var2);
    public native long getLongVolatile(Object var1, long var2);
    ......
    

本地内存操作

  1. 分配指定大小的一块本地内存 (同C语言中的 malloc)

    public native long allocateMemory(long bytes);

  2. 重新分配内存(同C语言中的 realloc)

    public native long reallocateMemory(long address, long bytes);

  3. 将给定的内存块 的所有字节 bytes 设置成固定的值 value (通过 object + offset 确定内存的基址)(同C语言中的 memset)

    public native void setMemory(Object o, long offset, long bytes, byte value);

  4. 复制内存块,内存块 srcBasc+srcOffset + bytes - > destBase+destOffset + bytes (同C语言中的 memcpy)

    public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset,long bytes);

  5. 释放通过Allocate分配的本地内存(同C语言中的 free)

    public native void freeMemory(long address);

  6. 获取和设置本地内存中的值,va1表示本地内存绝对地址,var3表示要设置的值

    public native short getShort(long var1);
    public native int getInt(long var1);
    public native void putShort(long var1, short var3);
    public native void putInt(long var1, int var3);
    

CAS操作

java.util.concurrent 包中无锁化的实现就是调用了CAS以下原子操作

  1. CAS语义

    1. 将 由var1+var2确定的地址的值 从var4 修改成 var5
    2. 如果旧值不为 var4,则直接退出
    3. 多个线程修改同一个变量时, 只会有一个线程修改成功,其他线程不会被挂起,而是告知失败
    4. 这是一种 乐观锁的语义, 每个线程都假设自己的操作能成功,与之相对应的synchronized的悲观锁语义,每次修改操作必须 只能有一个线程独占资源
  2. 设置 通过 var1+var2确定的内存基址的int类型变量,将值原子的从 var4 变成 var5,成功true,失败false

    替换int:public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    替换引用:public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    
  3. 基于上面操作的包装方法: 得到对象 中某个int字段的值 通过(var1+var2), 并给该值加上 var4,返回相加前的值

    典型实现
    public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
            return var5;
        }
    

Pack/Unpack

  1. 阻塞和释放任一线程对象

  2. 内部实现通过 信号量的方式,信号量值为1,pack 消耗值, unpack增加值

  3. LockSupport类包装使用

Example

//测试对象
public class UnsafeEntity {
    private  int a;
    private  int c;
    private  int d;
    private  static  int b = 1;
    getter......
    setter......
}
//测试代码
package com.weisanju;
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeTest {
    public static void main(String[] args) throws Exception {
        Field f= Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe u = (Unsafe) f.get(null);
        //获取成员变量 的偏移
        long a = u.objectFieldOffset(UnsafeEntity.class.getDeclaredField("a"));
        long c = u.objectFieldOffset(UnsafeEntity.class.getDeclaredField("c"));
        long d = u.objectFieldOffset(UnsafeEntity.class.getDeclaredField("d"));
        System.out.println("成员字段a:"+a);
        System.out.println("成员字段c:"+c);
        System.out.println("成员字段d:"+d);

        //设置对象字段的值
        UnsafeEntity testa = new UnsafeEntity();
        testa.setA(666);
        System.out.println("设置前:"+u.getInt(testa, a));
        u.putInt(testa,a,777);
        System.out.println("设置后:"+u.getInt(testa, a));

        //获取静态字段所在的类的对象
        System.out.println(u.staticFieldBase(UnsafeEntity.class.getDeclaredField("b")));
        //获取静态字段的偏移
        long b = u.staticFieldOffset(UnsafeEntity.class.getDeclaredField("b"));
        System.out.println("静态字段b:"+b);

        //静态字段的设置, 注意由于静态字段,存储于方法区,所以起始对象为类的字节码
        System.out.println("设置前:"+u.getInt(UnsafeEntity.class, b));
        u.putInt(UnsafeEntity.class,b,11);
        System.out.println("设置后:"+u.getInt(UnsafeEntity.class, b));


        //普通 数组的使用
        int arr[] = {1,2,3,4,5,6,7,8};
        //head为头地址偏移
        long head = u.arrayBaseOffset(int[].class);
        //step为数组元素所占空间
        long step = u.arrayIndexScale(int[].class);
        // 获取 与设置 arr[7] 的值
        int index = 7;
        System.out.println(u.getInt(arr, head + step * index));
        u.putInt(arr,head+step*index,666);
        System.out.println(arr[index]);

        //对象数组的使用
        UnsafeEntity arrObj[] = new UnsafeEntity[10];
        //head为头地址偏移
        head = u.arrayBaseOffset(UnsafeEntity[].class);
        //step为数组元素所占空间
        step = u.arrayIndexScale(UnsafeEntity[].class);
        // 获取 与设置 arr[7] 的值
        index = 7;
        arrObj[index] = new UnsafeEntity();
        System.out.println(u.getObject(arrObj, head + step * index));
        u.putObject(arrObj,head+step*index,new UnsafeEntity());
        System.out.println(arrObj[index]);
    }
}
输出结果
成员字段a:12
成员字段c:16
成员字段d:20
设置前:666
设置后:777
class com.weisanju.UnsafeEntity
静态字段b:104
设置前:1
设置后:11
8
666
com.weisanju.UnsafeEntity@1540e19d
com.weisanju.UnsafeEntity@677327b6

总结

  1. Unsafe为从cpu底层指令 层面 为多线程提供了无锁化设计,以及直接操作内存地址的能力,Java中 Atomic原子类,netty,concurrent包等底层都封装了 该对象
  2. 当然 极大的效率,也必然意外着 极大的不安全, 如果错误给一块内存区赋值,程序不会有任何反应,这就给程序带来极大的安全隐患
  3. 当然了解Unsafe类 能够便于我们更好的阅读 Java底层源码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unsafe类是Java中一个非常特殊的类,它提供了对底层操作的支持,包括内存操作、线程调度等。使用Unsafe类可以实现一些Java中无法实现的操作,并且可以提高代码的性能。 CAS(Compare and Swap)操作是Unsafe类中非常常见的操作之一。它的作用是比较当前值和期望值是否相等,如果相等,则将新值更新到当前值中,否则不做任何操作CAS操作通常被用于多线程编程中的数据同步,以保证多个线程对共享变量的操作是正确的。 下面是使用Unsafe类进行CAS操作的一个示例: ``` import sun.misc.Unsafe; import java.lang.reflect.Field; public class CASDemo { private static Unsafe unsafe; static { try { // 使用反射获取Unsafe类 Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { // 假设初始值为10 int expectedValue = 10; // 将初始值存储在内存地址100的位置 long offset = unsafe.objectFieldOffset(CASDemo.class.getDeclaredFields()[0]); // 使用CAS操作将值从10更新为20 boolean casResult = unsafe.compareAndSwapInt(new CASDemo(), offset, expectedValue, 20); System.out.println(casResult); } private int value = 10; } ``` 在上面的代码中,首先通过反射获取Unsafe类的实例,然后将初始值存储在类的某个字段中,接着使用`unsafe.compareAndSwapInt()`方法进行CAS操作,将值从10更新为20。这个方法的第一个参数是对象实例,第二个参数是字段的内存地址偏移量,第三个参数是期望值,第四个参数是更新值。如果更新成功,该方法返回true,否则返回false。 需要注意的是,Unsafe类使用需要非常小心,因为它可以直接操作内存,容易引发安全问题。在使用Unsafe类的时候,应该先了解清楚相关的API文档,并且在实际应用中要进行严格的测试和验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值