【Java并发编程】什么是Unsafe?

Unsafe

Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。

Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。 Java中的Unsafe类为我们提供了类似C++手动管理内存的能力,同时也有了指针的问题。

首先,Unsafe类是"final"的,不允许继承。且构造函数是private的,因此我们无法在外部对Unsafe进行实例化。

java 复制代码
public final class Unsafe {
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;

    private static native void registerNatives();

    private Unsafe() {
    }
    ...

}

获取Unsafe

Unsafe无法实例化,那么怎么获取Unsafe呢?答案就是通过反射来获取Unsafe:

java 复制代码
public Unsafe getUnsafe() throws IllegalAccessException {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);
    return unsafe;
}

Unsafe功能

Unsafe的功能如下图:

CAS相关

JUC中大量运用了CAS操作,可以说CAS操作是JUC的基础,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:

java 复制代码
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

偏移量相关

java 复制代码
public native long staticFieldOffset(Field var1);

public native long objectFieldOffset(Field var1);
  • staticFieldOffset方法用于获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量。
  • objectFieldOffset方法用于获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量

类加载

java 复制代码
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

public native Object allocateInstance(Class<?> var1) throws InstantiationException;

public native boolean shouldBeInitialized(Class<?> var1);

public native void ensureClassInitialized(Class<?> var1);
  • defineClass方法定义一个类,用于动态地创建类。
  • defineAnonymousClass用于动态的创建一个匿名内部类。
  • allocateInstance方法用于创建一个类的实例,但是不会调用这个实例的构造方法,如果这个类还未被初始化,则初始化这个类。
  • shouldBeInitialized方法用于判断是否需要初始化一个类。
  • ensureClassInitialized方法用于保证已经初始化过一个类。

举例

java 复制代码
public class UnsafeFooTest {
    private static Unsafe geUnsafe() {

        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;

    }

    static class Simple {
        private long l = 0;

        public Simple() {
            this.l = 1;
            System.out.println("我被初始化了");
        }

        public long getL() {
            return l;
        }
    }

    public static void main(String[] args) throws Exception {

        Unsafe unsafe = geUnsafe();

        Simple s = (Simple) unsafe.allocateInstance(Simple.class);
        System.out.println(s.getL());
    }
}

结果:

0

  • 可以发现,利用Unsafe获取实例,不会调用构造方法

普通读写

通过Unsafe可以读写一个类的属性,即使这个属性是私有的,也可以对这个属性进行读写。

读写一个Object属性的相关方法

java 复制代码
public native int getInt(Object var1, long var2);

public native void putInt(Object var1, long var2, int var4);
  • getInt用于从对象的指定偏移地址处读取一个int。
  • putInt用于在对象指定偏移地址处写入一个int。其他的primitive type也有对应的方法。

举例

java 复制代码
public class UnsafeFooTest {
    private static Unsafe geUnsafe() {

        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;

    }

    static class Guard{
        private int ACCESS_ALLOWED = 1;
        private boolean allow(){
            return 50 == ACCESS_ALLOWED;
        }

        public void work(){
            if (allow()){
                System.out.println("我被允许工作....");
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Unsafe unsafe = geUnsafe();
        Guard guard = new Guard();

        Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");

        unsafe.putInt(guard,unsafe.objectFieldOffset(f),50);
        System.out.println("强行赋值...");
        guard.work();

    }
}

结果

强行赋值...

我被允许工作...

类加载

java 复制代码
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

public native Object allocateInstance(Class<?> var1) throws InstantiationException;

public native boolean shouldBeInitialized(Class<?> var1);

public native void ensureClassInitialized(Class<?> var1);
  • defineClass方法定义一个类,用于动态地创建类。
  • defineAnonymousClass用于动态的创建一个匿名内部类。
  • allocateInstance方法用于创建一个类的实例,但是不会调用这个实例的构造方法,如果这个类还未被初始化,则初始化这个类。
  • shouldBeInitialized方法用于判断是否需要初始化一个类。
  • ensureClassInitialized方法用于保证已经初始化过一个类。

内存屏障

java 复制代码
public native void loadFence();

public native void storeFence();

public native void fullFence();
  • loadFence:保证在这个屏障之前的所有读操作都已经完成。
  • storeFence:保证在这个屏障之前的所有写操作都已经完成。
  • fullFence:保证在这个屏障之前的所有读写操作都已经完成。

线程调度

java 复制代码
public native void unpark(Object var1);

public native void park(boolean var1, long var2);

public native void monitorEnter(Object var1);

public native void monitorExit(Object var1);

public native boolean tryMonitorEnter(Object var1);
  • park方法和unpark方法相信看过LockSupport类的都不会陌生,这两个方法主要用来挂起和唤醒线程。
  • LockSupport中的park和unpark方法正是通过Unsafe来实现的:
java 复制代码
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

monitorEnter方法和monitorExit方法用于加锁,Java中的synchronized锁就是通过这两个指令来实现的。

参考

相关推荐
Themberfue12 分钟前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
深山夕照深秋雨mo21 分钟前
在Java中操作Redis
java·开发语言·redis
努力的布布26 分钟前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
xujinwei_gingko27 分钟前
Spring MVC 常用注解
java·spring·mvc
PacosonSWJTU31 分钟前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
PacosonSWJTU33 分钟前
spring揭秘26-springmvc06-springmvc注解驱动的web应用
java·spring·springmvc
记得开心一点嘛40 分钟前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala
原野心存1 小时前
java基础进阶——继承、多态、异常捕获(2)
java·java基础知识·java代码审计
进阶的架构师1 小时前
互联网Java工程师面试题及答案整理(2024年最新版)
java·开发语言
黄俊懿1 小时前
【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法
java·后端·算法·spring cloud·微服务·架构