首页 > 编程语言 >【Java 并发】【七】【Unsafe】什么是Unsafe及其作用

【Java 并发】【七】【Unsafe】什么是Unsafe及其作用

时间:2023-04-03 16:58:56浏览次数:36  
标签:Java Unsafe unsafe long 并发 线程 内存 public native

1  前言

这节我们来看看JDK底层的unsafe,因为很多的操作都是依赖于unsafe提供的功能的。

2  unsafe是什么?

unsafeJDK提供的一个工具类,里面的方法大多是native方法,unsafe类是JDK给你提供的一个直接调用操作系统底层功能的一个工具类,unsafe提供了非常多操作系统级别的方法

(1)比如说通过unsafe可以让操作系统直接给你分配内存、释放内存。

(2)突破java语法本身的限制,直接从内存级别去操作堆里面的某个对象的数据;

(3)调用操作系统的CAS指令,实现CAS的功能

(4)操作系统层次将线程挂起和恢复

(5)提供操作系统级别的内存屏障(之前说过的Load屏障和Store屏障),读取数据强制走主存,修改数据直接刷新到主存

总之unsafe就相当于JDK给你提供的一个直接跟操作系统打交道的一个工具类,通过unsafe可做一些非常底层的指令和行为。

unsafe提供了很多操作系统级别的方法,在提供使用者便利的同时,也是隐藏着很多风险的。万一使用者分配大量的内存,没有及时回收,岂不是很容易造成内存溢出的风险?又或者分配了内存,但是忘记回收了,容易造成内存泄露。但是unsafe提供的这些操作系统级别的方法对于JDK底层的一些工具类、上层的一些框架来说在实现层方便了许多。比如著名的并发基础工具类AQS底层就是通过unsafe提供的CAS操作来进行加锁的,加锁失败的线程又是通过unsafe提供的park、unpark操作将线程挂起和唤醒的。还有一些非常著名的开源框架比如netty分配直接内存的方式底层也还是通过unsafe分配直接内存。

下面啊,我们分几类将一些unsafe提供的一些重要功能。

3  unsafe直接分配和释放内存

下面看一下unsafe提供的直接分配、释放内存,操作内存的一下方法:

// 分配bytes大小的堆外内存
public native long allocateMemory(long bytes);
// 还可以执行从address处开始分配,分配bytes大小的堆外内存
public native long reallocateMemory(long address, long bytes);
// 释放allocateMemory和reallocateMemory申请的内存块
public native void freeMemory(long address);
// 将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
// 相当于直接让你在内存级别直接给这个对象的变量赋值了
public native void setMemory(Object o, long offset, long bytes, byte value);
// 设置给定内存地址的long值
// 相当于直接在内存级别给address后面的8个字节赋值
public native void putLong(long address, long x);
// 获取指定内存地址的long值
// 相当于获取address后面的8个字节的值,然后转化为十进制的long值给你
public native long getLong(long address);
// 设置或获取指定内存的byte值
// 相当于获取address后面一个字节的数据,转化成十进制返回给你
public native byte  getByte(long address);
// 直接在内存级别给adderess地址后面的1个字节设置
public native void  putByte(long address, byte x);

这里提供一些操作系统级别的直接申请内存、释放内存的方式。同时不受java语法的限制,提供内存级别的直接获取数据,修改数据的方式;直接通过内存地址address找到这块内存然后直接操作这个内存块的数据了。unsafe是通过调用操作系统提供的能力直接去申请和释放内存的。

4  unsafe提供的CAS操作

JUC提供的很多Atomic原子类基于AQS实现的并发工具,底层都是通过CAS操作去实现的。下面我们就说说unsafe提供的cas操作:

假如目前有一个Test类是这样子的:

public class Test {
    private DemoClass demo;
    private int intValue;
    private long longValue;
}

有一个Test类的对象  Test o = new Test();

这个时候想要突破java语法的限制,直接修改对象o的private修饰的demo属性。可以通过CAS操作直接去修改对象o里面的demo属性,使用unsafe提供的下面方法:

(1)o就是你要操作的对象

(2)offset就是demo属性在对象o内部的位置,或者偏移量

(3)expected就是demo期待的值

(4)x就是你希望设置的新值,只有demo的值 == expect的时候,才能将demo的值设置成x

public final native boolean compareAndSwapObject( Object o,
                                 long offset,
                                    Object expected,
                                    Object x);

执行CAS操作大致是这样的,根据 对象o的地址,demo属性相对于o的偏移量offset,直接计算得到demo所在内存的位置,然后直接将demo的值从内存取出进行CAS(比较替换操作):

同理对于,执行CAS操作替换Test类对象o内部的int值和long值,unsafe提供了如下两个方法:

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);

底层执行CAS替换的原理跟上面画图讲的demo其实是一样的,这里就不再赘述了。

5  unsafe将线程挂起和恢复

unsafe类提供类将一个线程挂起、讲一个挂起的线程唤醒的方法,分别是park和unpark,我们看如下的代码:

park方法:

//线程调用该方法,线程将一直阻塞直到被唤醒,或者超时,
//或者中断条件出现。
public native void park(boolean isAbsolute, long time);

(1)isAbsolute是否是绝对时间,当isAbsolute == true ,后面time的时间单位是ms;当为false的时候,后面time参数的时间单位是ns。

(2)time > 0时候,表示大概要将线程挂起time的时间,过了时间后自动将线程唤醒。当time = 0的时候,表示一直将线程挂起,直到有人调用unpark方法将线程唤醒。

unpark方法:

public native void unpark(Object thread);

直接将正在被挂起的thread线程唤醒,让它继续干活

这里我们再提一个类LockSupport,LockSupport是对unsafe中park和unpark功能封装的一个工具类,提供了阻塞和唤醒功能。

我们可以直接使用LockSupport的方法达到挂起和恢复线程的效果,LockSupport方法的源码如下:

public class LockSupport {
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        // 这里直接调用unsafe的park方法将线程挂起
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    
    public static void unpark(Thread thread) {
        if (thread != null)
            // 直接调用unsafe的unpark方法将线程唤醒
            UNSAFE.unpark(thread);
    }
}

由于我们自己编写的java程序不能直接使用unsafe工具类,所以啊JDK还是有一些工具类对unsafe类的功能进行封装,然后我们就直接使用这些封装的工具类即可。

6  内存屏障

unsafe提供了几种内存屏障:

// 在该方法之前的所有读操作,一定在load屏障之前执行完成
public native void loadFence();
// 在该方法之前的所有写操作,一定在store屏障之前执行完成
public native void storeFence();
// 在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个的合体功能
public native void fullFence();

7  unsafe类的cas是怎么保证原子性的?

 

 

8  小结

这节我们看了unsafe提供的几类操作系统级别的功能,比较重要的还是:内存级别操作数据,cas操作,线程挂起park和唤醒unpark,还针对cas的原子性做了详细的解释,有理解不对的地方欢迎指正哈

标签:Java,Unsafe,unsafe,long,并发,线程,内存,public,native
From: https://www.cnblogs.com/kukuxjx/p/17283457.html

相关文章

  • PaddleOCR服务部署-并通过Java进行调用
    文章转载自: https://blog.csdn.net/f2315895270/article/details/128150679选择部署方式  官方推荐有以下几种:  Python推理  C++推理  Serving服务化部署(Python/C++)  Paddle-Lite端侧部署(ARM CPU/OpenCLARMGPU)  Paddle.js部署     由于我......
  • 【Java虚拟机探究】10.类装载器(下)
    上一篇我们总结了类加载器的基本原理和与应用程序相关的ClassLoader,并提到了双亲委派模式。本篇继续探讨类加载器的双亲委派模式,以及如何破坏双亲委派模式达到加载底层类的目的。1.双亲委派模式的问题我们回顾一下原来的应用程序的ClassLoader的加载模式:除了顶层的ClassLoader,每......
  • 【Java虚拟机探究】9.类装载器(上)
    在JVM类要通过类装载器(ClassLoader)进行装载后,才能进行执行。本篇总结了类装载器的一些知识。一、class装载验证流程在第一篇总结中介绍了JVM的内存结构:可以看到class文件首先要通过“类加载器子系统”,才能被加载到内存中处理。那么class文件是怎么通过类加载器加载至内存中的呢......
  • 【FastDFS分布式文件系统】6.FastDFS客户端启动与Java连接
    上一篇我们讲解了如何配置和启动FastDFS客户端,以及客户端上传下载的一些常用命令。那么,在许多需要进行分布式文件上传与下载的系统中,就不能像执行Linux命令一样去上传和下载文件,它们需要使用开发系统的语言去操作客户端使用其命令与服务端进行交互,此时FastDFS......
  • 【CSAPP】进程 | 上下文切换 | 用户视角下的并发进程
     ......
  • 性能工具之JMeter两个Java API Demo
    概述本文演示两个通过JavaAPI执行JMeter脚本的示例主要功能在线生成jmx脚本(demo1)加载本地已有jmx脚本(demo2)运行多个Sampler将生成的TestPlan存储为.jmx文件执行单机压测将测试执行结果存储为.jtlor.csv文件示例Maven配置为了开始使用JMeterAPI,我们首先需要将它添加到......
  • java稀疏数组实现实例
    没有原理讲解,仅记录一个实现代码,作为参考和笔记使用如题,稀疏数组仅在原始数组有效数据较少的情况下起压缩空间的作用实现过程:首先为了方便查看和确认,封装一个打印二维数组的方法publicstaticvoidprintArray(int[][]arrays){for(int[]array:arrays){......
  • 114.二叉树展开为链表 Java
    114.二叉树展开为链表给你二叉树的根结点root,请你将它展开为一个单链表:展开后的单链表应该同样使用TreeNode,其中right子指针指向链表中下一个结点,而左子指针始终为null。展开后的单链表应该与二叉树先序遍历顺序相同。示例1:输入:root=[1,2,5,3,4,null,6]输出......
  • 详细解析Java异步线程处理队列任务工具类以及实战
    场景待入快速理解小场景描述:【一群人】来到【一个大厅】办理业务,大厅中有【多个窗口】给我们办理业务。每个人都有自己要办事情,处理过程需要消耗时间。大厅根据人群多少,开始窗口梳理。如果把“一群人”理解成一群待处理的n个【任务】,把这群人排成一个长队就形成了一个【任......
  • 【】Java Error: Port 9095 was already in use
    问题描述JavaError:Port9095wasalreadyinuse问题原因端口被占用导致解决方案Windsow系统netstat-ano|findstr9090查询到占用9090端口的进程PID为9784。tasklist|findstr9784查询到PID为0=7984的进程打开【任务管理器】->【服务】,将对应应用关闭Lin......