Java多线程(9)——Unsafe(1)——Unsafe类的源码分析

本文详细分析了Java中的Unsafe类,包括其在Class、Object、数组、并发、内存访问和系统相关方面的使用。重点讨论了Unsafe在并发中的CAS操作、线程调度、volatile读写以及内存屏障,并指出其作为Java底层操作的重要工具,虽然存在风险但能提升性能。
摘要由CSDN通过智能技术生成

目录

1.概述

2.源码分析

2.1 Class相关

2.2 Object相关

2.3 数组相关

2.4 并发相关

(1)CAS相关

(2)线程调度相关

(3)volatile相关读写

(4)内存屏障相关

2.5 直接内存访问(非堆内存)

2.6 系统相关

3.带英文原注释的源码


文章前半部分暂时大多数从此链接粘贴过来,仅学习使用,但在后续学习过程中再对每一部分再做详细补充,原链接:https://www.jb51.net/article/140726.htm

1.概述

  • Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Hadoop、Kafka等。
  • 使用Unsafe可用来直接访问系统内存资源并进行自主管理,Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
  • Unsafe可认为是Java中留下的后门,提供了一些低层次操作,如直接内存访问、线程调度等。
  • Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库。
  •  官方并不建议使用Unsafe。

2.源码分析

Unsafe的大部分API都是native的方法,主要包括以下几类:

  • 1)Class相关。主要提供Class和它的静态字段的操作方法。
  • 2)Object相关。主要提供Object和它的字段的操作方法。
  • 3)Arrray相关。主要提供数组及其中元素的操作方法。
  • 4)并发相关。主要提供低级别同步原语,如CAS、线程调度、volatile、内存屏障等。
  • 5)Memory相关。提供了直接内存访问方法(绕过Java堆直接操作本地内存),可做到像C一样自由利用系统内存资源。
  • 6)系统相关。主要返回某些低级别的内存信息,如地址大小、内存页大小。

2.1 Class相关

//判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class<?> c);
//确保类被初始化
public native void ensureClassInitialized(Class<?> c);
//定义一个类,可用于动态创建类
public native Class<?> defineClass(String name, byte[] b, int off, int len,
     ClassLoader loader,
     ProtectionDomain protectionDomain);
//定义一个匿名类,可用于动态创建类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

2.2 Object相关

Java中的基本类型(boolean、byte、char、short、int、long、float、double)及对象引用类型都有以下方法。

//获得实例对象的字段的偏移地址
public native long objectFieldOffset(Field f); 
//获得给定对象地址偏移量的int值,而offset参数即为上述的objectFiledOffset的返回值
public native int getInt(Object o, long offset);
//设置给定对象地址偏移量的int值
public native void putInt(Object o, long offset, int x);

objectFieldOffset获取到的偏移地址仅仅在该Unsafe函数中访问指定字段时使用

  • 如下代码使用unsafe获取AtomicLong中变量value在AtomicLong对象中的内存偏移,代码如下:
  •     static {
            try {
                valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
            } catch (Exception ex) {
                throw new Error(ex);
            }
    
        }
//获取静态字段的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//获取静态字段的定义它的类的class对象,这个方法得到的值跟staticFieldOffset方法得到的值可以共同确定字段的在堆中的位置 
public native Object staticFieldBase(Field f);

说明:

  • 以上方法访问的皆是在堆中
  • 我们知道在Java8使用元空间实现了方法区,在直接内存中,但是类的静态变量和常量池在堆中,所以staticFieldBase仍然返回的是堆中的地址,它和statciFieldOffset,一起同样可以通过上述的getInt(Object o, long offset)方法去获得静态字段的值,通过putInt(Object o, long offset, int x)修改静态字段的值
//创建对象,但并不会调用其构造方法。如果类未被初始化,将初始化类。
public native Object allocateInstance(Class<?> cls)
 throws InstantiationException;

2.3 数组相关

    //返回数组中第一个元素的偏移地址,它可以和下面的元素大小一起来让我们使用去访问数组中的元素
    public native int arrayBaseOffset(Class<?> arrayClass);
    
    //boolean、byte、short、char、int、long、float、double,及对象类型均有以下字段
    public static final int ARRAY_BOOLEAN_BASE_OFFSET
            = theUnsafe.arrayBaseOffset(boolean[].class);


    //返回数组中每一个元素占用的大小
    public native int arrayIndexScale(Class<?> arrayClass);

    //boolean、byte、short、char、int、long、float、double,及对象类型均有以下字段
    public static final int ARRAY_BOOLEAN_INDEX_SCALE
            = theUnsafe.arrayIndexScale(boolean[].class);

  • 通过arrayBaseOffset和arrayIndexScale可定位数组中每个元素在内存中的位置

2.4 并发相关

(1)CAS相关

CAS:CompareAndSwap,内存偏移地址offset,预期值expected,新值x。如果变量在当前时刻的值和预期值expected相等,尝试将变量的值更新为x。如果更新成功,返回true;否则,返回false。

//更新变量值为x,如果当前值为expected
//o:对象    offset:偏移量     expected:期望值     x:新值
public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
  
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
  
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

从Java 8开始,Unsafe中提供了以下方法:(注意:以下方法不是native的哦)

    //取到并增加值
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

    public final long getAndAddLong(Object o, long offset, long delta) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!compareAndSwapLong(o, offset, v, v + delta));
        return v;
    }

    //取到并设置为新值
    public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, newValue));
        return v;
    }

    public final long getAndSetLong(Object o, long offset, long newValue) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!compareAndSwapLong(o, offset, v, newValue));
        return v;
    }

    public final Object getAndSetObject(Object o, long offset, Object newValue) {
        Object v;
        do {
            v = getObjectVolatile(o, offset);
        } while (!compareAndSwapObject(o, offset, v, newValue));
        return v;
    }

(2)线程调度相关

    /**
     * 阻塞当前线程,
     * 其中参数 isAbsolute 等于 false 时候,time 等于 0 表示一直阻塞,
     *      time 大于 0 表示等待指定的 time 后阻塞线程会被唤醒,
     *      这个 time 是个相对值,是个增量值,
     *      也就是相对当前时间累加 time 后当前线程就会被唤醒。
     * 如果 isAbsolute 等于 true,并且 time 大于 0 表示阻塞后到指定的时间点后会被唤醒,
     *      这里 time 是个绝对的时间,是某一个时间点换算为 ms 后的值。
     * 另外当其它线程调用了当前阻塞线程的 interrupt 方法中断了当前线程时候,
     *      当前线程也会返回,
     *      当其它线程调用了 unpark 方法并且把当前线程作为参数时候当前线程也会返回。
     */
    public native void park(boolean isAbsolute, long time);


    /**
     * 唤醒调用 park 后阻塞的线程,参数为需要唤醒的线程。
     */

    public native void unpark(Object thread);
  • 关于park和unpark方法更详细的源码分析见另一篇博客:待补充链接

    //获得对象锁
    public native void monitorEnter(Object o);
    //释放对象锁
    public native void monitorExit(Object o);
    //尝试获取对象锁,返回true或false表示是否获取成功
    public native boolean tryMonitorEnter(Object o);

(3)volatile相关读写

Java中的基本类型(boolean、byte、char、short、int、long、float、double)及对象引用类型都有以下方法。

    //从对象的指定偏移量处获取变量的引用,使用volatile的加载语义(保证可见性和禁止指令重排序)
    //相当于getObject(Object, long)的volatile版本,
    public native Object getObjectVolatile(Object o, long offset);

    //存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义(保证可见性和禁止指令重排序)
    //相当于putObject(Object, long, Object)的volatile版本
    public native void putObjectVolatile(Object o, long offset, Object x);

    /**
     * 设置 obj 对象中 offset 偏移地址对应的 long 型 field 的值为 value。
     * 
     * 这是有延迟的 putLongVolatile 方法,并不保证值修改对其它线程立刻可见。
     * 变量只有使用 volatile 修饰并且期望被意外修改的时候使用才有用。 
     *
     */
    public native void putOrderedObject(Object o, long offset, Object x);


    public native void putOrderedInt(Object o, long offset, int x);


    public native void putOrderedLong(Object o, long offset, long x);

(4)内存屏障相关

//内存屏障,禁止load操作重排序,即屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序,即屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();

2.5 直接内存访问(非堆内存)

//(boolean、byte、char、short、int、long、float、double)都有以下get、put两个方法。 
//获得给定地址上的int值
public native int getInt(long address);
//设置给定地址上的int值
public native void putInt(long address, int x);

//获得本地指针
public native long getAddress(long address);
//存储本地指针到给定的内存地址
public native void putAddress(long address, long x);
//分配内存
public native long allocateMemory(long bytes);
//重新分配内存
public native long reallocateMemory(long address, long bytes);
//初始化内存内容
public native void setMemory(Object o, long offset, long bytes, byte value);
//初始化内存内容
public void setMemory(long address, long bytes, byte value) {
 setMemory(null, address, bytes, value);
}
//内存内容拷贝
public native void copyMemory(Object srcBase, long srcOffset,
    Object destBase, long destOffset,
    long bytes);
//内存内容拷贝
public void copyMemory(long srcAddress, long destAddress, long bytes) {
 copyMemory(null, srcAddress, null, destAddress, bytes);
}
//释放内存
public native void freeMemory(long address);
  • 注意:allocateMemory所分配的内存需要手动free(不被GC回收)

2.6 系统相关

//返回指针的大小。返回值为4或8。
public native int addressSize();
  
/** The value of {@code addressSize()} */
public static final int ADDRESS_SIZE = theUnsafe.addressSize();
  
//内存页的大小。
public native int pageSize();

2.7 getUnsafe特别说明

  •  

 

3.带英文原注释的源码

/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值