JUC基石——Unsafe类

news/2024/5/20 10:19:47 标签: Unsafe, CAS, JUC

前言

我们经常在JUC包下的ConcurrentHashMap、Atomic开头的原子操作类、AQS以及LockSupport里面看到Unsafe类的身影,这个Unsafe类究竟是干什么的,本文可以带着读者一探究竟。

Java和C++、C语言的一个重要区别,就是Java中我们无法直接操作一块内存区域,而在C++、C中却可以自己申请内存和释放内存。Unsafe类的设计,为我们提供了手动管理内存的能力。

如同它的名字一样,它被认定为不安全的。直接操纵内存,意味着实例化出来的对象不会受到JVM的管理,不会被GC,需要手动进行回收,容易出现内存泄露的问题。因此,官方并不建议我们在自己的应用程序中使用该类。


构造方法

public final class Unsafe {
    private static final Unsafe theUnsafe;

    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
   //其他方法
}

可以看得出来,该类被final修饰,不允许被继承。构造方法是私有的,在外部不可被实例化。(关于final更多的作用,可以移步这篇文章关键词final的作用)

        但在内部提供了一个获取单例的getUnsafe()方法,不过该方法做了限制。如果是普通调用的话,它会抛出一个SecurityException异常。只有由系统类加载器(BootStrap classLoader)加载的类才可以调用这个类中的方法。

如果var0由系统类加载器加载的话,那么var0.getClassLoader()会返回null,VM.isSystemDomainLoader(null)则直接返回true,此时便不会抛出SecurityException异常。

当然,也不是无法获取到Unsafe类的实例,我们在文章最后会通过反射来获取。


获取偏移量

    public native long staticFieldOffset(Field var1);

    public native long objectFieldOffset(Field var1);
  • staticFieldOffset用于获取某一个静态属性在对象地址中的偏移量
  • objectFieldOffset用于获取某一个非静态属性在对象实例地址中的偏移量

偏移量这个名词在Unsafe类中十分重要,该类中80%的方法都需要依赖这个偏移量。


分配、释放内存等

    
    //分配内存
    public native long allocateMemory(long var1);

    //扩展或重新分配内存
    public native long reallocateMemory(long var1, long var3);

    //内存初始化
    public native void setMemory(Object var1, long var2, long var4, byte var6);

    //内存复制
    public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);

    //释放内存
    public native void freeMemory(long var1);

    //创建对象实例并返回该实例
    public native Object allocateInstance(Class<?> var1) throws InstantiationException;

其中allocateMemory()、reallocateMemory()、freeMemory()分别用于分配内存,扩展或重新分配内存和释放内存,与C语言中 malloc()、realloc()、free()对应。

allocateInstance()方法会通过Class对象创建一个类的实例,且不需要调用其构造函数、初始化代码、JVM安全检查等等。


普通读写

    public native int getInt(long var1);

    public native void putInt(long var1, int var3);
  • getInt()   在指定内存地址var1处读取一个int
  • putInt()   在指定内存地址var1处写入一个新int类型的数据var3

普通的读写,无法保证有序性与可见性。关于可见性,可以先移步到我的另外一篇文章多线程之内存可见性


volatile读写

    public native int getIntVolatile(Object var1, long var2);

    public native void putIntVolatile(Object var1, long var2, int var4);
  • getIntVolatile   在对象var1的指定偏移处var2读取一个int
  • putIntVolatie    在对象var1的指定偏移处var2写入一个int类型的数据var4

volatie能够保证有序性以及可见性,volatie保证有序性的一个实例,可以参考我的另外一篇文章浅说Synchronized中使用synchronized与volatie实现单例模式中双重检验锁的部分。


CAS操作

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

以compareAndSwapInt为例,在对象var1指定偏移量var2处读取一个int值real,如果var4=real,则用var5更新这个real,并返回true,否则返回false。

CAS用于实现乐观锁,每次更新时,都假设不会有其他线程并发修改,而只是在修改的时候判断该值是否被修改过。如果符合预期的值,则直接更新它,否则进入忙循环中,一直判断是否符合期望。在循环次数少便可以直接更新值的情况下,CAS机制比悲观锁拥有更好的性能。当然,如果循环次数过多,也是会白白浪费CPU资源。

synchronized就是一中悲观锁的实现,关于synchronized原理,可以移步我的另外一篇文章Synchronized的优化

关于CAS机制的详细介绍,我会另开篇幅。


线程调度

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

  • park()        用于阻塞当前线程,如果var1=true,则var2的单位为毫秒,否则为纳秒。
  • unpark()    用于恢复一个之前被park的线程var1

LockSupport里面的park()与unpark()方法内部正是通过以上两个本地方法来实现的。


内存屏障

    public native void loadFence();

    public native void storeFence();

    public native void fullFence();
  • loadFence      保证在这个屏障之前,所有的读操作全部完成。这里的load对应于从局部变量表中读取某个位置上的元素到栈顶
  • storeFence     保证在这个屏障之前,所有的写操作全部完成。这里的store对应于从栈顶出栈某个元素保存到局部变量表的某个位置上
  • fullFence        保证在这个屏障之前,所有的读写操作全部完成,相当于load+store

反射获取Unsafe实例

package com.yang.testUnsafe;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Main {

    static class Student {
        private String name;
        private int age;

        public Student() {
            System.out.println("通过构造方法");
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        //1.获取Unsafe类的实例
        //但无法通过Unsafe.getUnsafe(),由于Main类不是系统类加载器加载,因此会抛出SecurityException异常
        //Unsafe unsafe = Unsafe.getUnsafe();
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        //2.为Student对象分配内存
        //该方法不需要调用构造方法
        Student student = (Student) unsafe.allocateInstance(Student.class);

        //3.获取student实例中age属性的偏移量
        Field age = student.getClass().getDeclaredField("age");
        long ageOffset = unsafe.objectFieldOffset(age);

        //4.利用CAS操作,当age=0时,将age变为1
        boolean casResult = unsafe.compareAndSwapInt(student, ageOffset, 0, 1);
        System.out.println(casResult);//输出true
        System.out.println(student.getAge());//输出1
    }
}

总结

使用Unsafe类直接操纵内存,意味着速度更快,效率更高,但也更加危险。之前盛传Unsafe类将在Java9中移除,一时间风波四起,具体的文章可以参考这篇Java 9中将移除 Sun.misc.Unsafe(译)。

但其实Java9出现之后,只是对其进行了改进和优化,不过依然是不推荐开发者使用Unsafe类。


http://www.niftyadmin.cn/n/1705237.html

相关文章

Beyond Compare 4 破解

重置评估时间的方法 1、打开注册表 WINR&#xff1a;regedit 2、删除项目CacheId HKEY_CURRENT_USER\Software\Scooter Software\Beyond Compare 4 右键->删除 3、重新打开软件 4、软件下载 https://www.beyondcomparepro.com/download

设备管理器里不见网卡

开机后无法上网,打开网络连接,发现本地连接不见了,在设备管理器里也找不到网卡.后来经查询:是在BIOS(Onboard lan )里屏蔽了板载网卡的缘故.转载于:https://blog.51cto.com/sdzian/79733

深入分析AQS实现原理

简单解释一下J.U.C&#xff0c;是JDK中提供的并发工具包,java.util.concurrent。里面提供了很多并发编程中很常用的实用工具类&#xff0c;比如atomic原子操作、比如lock同步锁、fork/join等。 从Lock作为切入点 我想以lock作为切入点来讲解AQS&#xff0c;毕竟同步锁是解决线…

VB实现半透明或者部分透明窗体

Windows2000已经出了n年多了,就先介绍一下Windows2000特有的API吧!! AnimateWindow是一个窗口打开和关闭时产生动画效果的新函数&#xff0c;因为是一个新的函数, 所以在 API Viewer中是找不到的,必需自己定义: Public Declare Function SetLayeredWindowAttributes Lib "…

【JVM】类的奇幻漂流——类加载机制探秘

我们写的类&#xff0c;在编译完成后&#xff0c;究竟是怎么加载进虚拟机的&#xff1f;虚拟机又做了什么神奇操作&#xff1f;本文可以带着读者初探类加载机制。上来先放类加载各个阶段的主要任务&#xff0c;用于给读者一个大概的印象体验&#xff0c;现在记不住也没有什么关…

win11修改mac地址的方法

查看物理地址 【WinR】输入【cmd】&#xff0c;进入命令窗口在命令窗口输入ipconfig/all按回车&#xff0c;在显示结果中可以看到以太网物理地址&#xff0c;也即是你的网卡的mac地址。 然后进入设置&#xff0c;选择【网络和internet】&#xff0c;选择【更多网络适配器选项】…

再学 GDI+[17]: FillRectangle、ColorRefToARGB、TGPSolidBrush 和颜色透明度

本例效果图:代码文件:unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, ComCtrls, StdCtrls, ExtCtrls;typeTForm1 class(TForm)ColorListBox1: TColorListBox;TrackBar1: TTrackBar;procedure FormCreate(Sende…

【JVM】虚拟机栈的五脏六腑

虚拟机栈&#xff0c;本身就是一个普通的栈&#xff0c;栈中的元素叫做栈帧。 虚拟机栈是线程私有的&#xff0c;每有一个线程&#xff0c;虚拟机就会创建一个虚拟机栈&#xff0c;线程与虚拟机栈一一对应。线程每调用一个方法&#xff0c;虚拟机就会创建一个栈帧&#xff0c;…