JUC(java.util.concurrent)的开始,可以说是从Unsafe类开始。

Unsafe 简介

Unsafe在sun.misc 下,顾名思义,这是一个不安全的类,因为Unsafe类所操作的并不属于Java标准,Java的一系列内存操作都是交给jvm的,而Unsafe类却能有像C语言的指针一样直接操作内存的能力,同时也会带来了指针的问题。过度使用Unsafe类的话,会使出错率变得更大,因此官方才命名为Unsafe,并且不建议使用,连注释的没有。

而为了安全使用Unsafe,Unsafe类只允许jdk自带的类使用,从下面的代码中可以看出

 public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}

如果当前Class是非系统加载的(也就是caller.getClassLoader()不为空),直接抛出SecurityException

在java9之后,又出现了一个jdk.internal.misc.Unsafe类,其功能与sun.misc.Unsafe类是一样的,唯一不一样的是在 getSafe() 的时候,jdk.internal.misc.Unsafe是没有做校验的,但是jdk包下的代码,应用开发时是不能直接调用的,而且在java9之后,两个Unsafe类都有充足的注释。

  • 获取Unsafe

Unsafe类里有这样的一个field。

private static final Unsafe theUnsafe = new Unsafe();

也就是说虽然不能直接拿到Unsafe对象,但是还是可以通过反射去获取的。

private static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
return (Unsafe) theUnsafeField.get(Unsafe.class);
}

而 JUC紧密使用了Unsafe的功能

功能简介

Unsafe类的功能主要分为内存操作、CAS、Class相关、对象操作、数组相关、内存屏障、系统相关、线程调度等功能。

内存操作

  • 堆外(native memory)内存操作
//分配内存,并返回内存地址
public native long allocateMemory(long bytes);
//扩充内存,address可以是allocateMemory方法返回的地址,bytes是扩充的大小
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块设置默认值
public native void setMemory(long address, long bytes, byte value);
//获取指定地址值的byte类型
public native byte getByte(long address);
//设置堆外指定值的byte类型的值
public native void putByte(long address, byte x);
  • 堆内内存操作
//在给定的内存块设置默认值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//获取指定地址的值,offset为o对象某个field的偏移量,类似有: getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//设置值,offset为o对象某个field的偏移量,类似的还有 putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);

通常,在java创建的对象都是在堆内存中的,堆内存是有JVM所托管,并且都遵循JVM内存管理机制。与之相对的是JAVA管核之外的堆外内存,是依赖Unsafe提供的操作堆外内存的native方法处理。

java带的NIO中的java.nio.DirectByteBuffer中就是用Unsafe的堆外内存函数来操作堆外内存。使用方法

ByteBuffer.allocateDirect(1024);

最终在 DirectByteBuffer 的构造函数中调用 UNSAFE 来分配并初始化内存。

long base = 0;
try {
base = UNSAFE.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
UNSAFE.setMemory(base, size, (byte) 0);
  • 使用案例

堆外内存

Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(Unsafe.class);
long address = unsafe.allocateMemory(1024);
unsafe.setMemory(address, 1024, (byte) 0);
unsafe.putInt(address, 1);
System.out.println(unsafe.getInt(address));
unsafe.freeMemory(address);

堆内内存

public class Demo {
private static String name = "jfound";
private int age = 10;
}
//-----------
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(Unsafe.class); Demo demo = new Demo();
Field ageField = Demo.class.getDeclaredField("age");
ageField.setAccessible(true);
long ageFieldAddress = unsafe.objectFieldOffset(ageField);
System.out.println(unsafe.getInt(demo, ageFieldAddress));
Field nameField = Demo.class.getDeclaredField("name");
nameField.setAccessible(true); unsafe.ensureClassInitialized(Demo.class); //初始化,否则name为null
long nameAddress = unsafe.staticFieldOffset(nameField);
System.out.println(unsafe.getObject(Demo.class, nameAddress)); //输出jfound

CAS

CAS,较并替换,CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

此操作是CPU指令cmpxchg,是属于指令级别的,具有原子性 ,典型的 atomic下的类,AQS系列的锁都是借用Unsafe下的CAS的api来实现的。

  • api
//cas改变值,o为改变的对象,可以是class,offset是指定的field偏移量,expected是期望的值,expected修改的值
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
  • 例子
public class Demo {
private static String name = "jfound";
}
//--------
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(Unsafe.class); Field nameField = Demo.class.getDeclaredField("name");
nameField.setAccessible(true);
long nameAddress = unsafe.staticFieldOffset(nameField);
unsafe.ensureClassInitialized(Demo.class); //初始化,否则name为null
unsafe.compareAndSwapObject(Demo.class, nameAddress, "jfound", "jfound-plus");
System.out.println(unsafe.getObject(Demo.class, nameAddress)); //输出 jfound-plus

Class相关

  • api
//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
public native long staticFieldOffset(Field f);
//获取一个静态类中给定字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。
public native boolean shouldBeInitialized(Class<?> c);
//检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。
public native void ensureClassInitialized(Class<?> c);
//定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
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);
  • 例子
public static class Demo {
private static String name = "jfound";
}
// -----------
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(Unsafe.class); System.out.println(unsafe.shouldBeInitialized(Demo.class)); //true,为初始化
unsafe.ensureClassInitialized(Demo.class); //初始化
System.out.println(unsafe.shouldBeInitialized(Demo.class)); //fale,已经初始化 Field nameField = Demo.class.getDeclaredField("name");
nameField.setAccessible(true);
long nameAddress = unsafe.staticFieldOffset(nameField);
System.out.println(unsafe.getObject(unsafe.staticFieldBase(nameField), nameAddress));

对象操作

  • api
//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
public native long objectFieldOffset(Field f);
//获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
public native Object getObjectVolatile(Object o, long offset);
//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
public native void putOrderedObject(Object o, long offset, Object x);
//绕过构造方法、初始化代码来创建对象,这个厉害吧
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
  • demo
public static class Demo {
private String name = "jfound";
public Demo() {
System.out.println("构造函数");
}
}
//--------
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(Unsafe.class); Field nameField = Demo.class.getDeclaredField("name"); Demo demo = (Demo) unsafe.allocateInstance(Demo.class); //构造函数没调用
long offset = unsafe.objectFieldOffset(nameField);
System.out.println(unsafe.getObject(demo, offset)); //打印null,allocateInstance出来的对象,属性也没有初始化
System.out.println(unsafe.getObject(new Demo(), offset)); //jfound

有意思的是,allocateInstance 创建出来的对象是没有调用构造函数的,而且对象的属性也没有初始化。

数组相关

  • api
//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);
  • demo
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(Unsafe.class); System.out.println(unsafe.arrayBaseOffset(int[].class));
System.out.println(unsafe.arrayIndexScale(int[].class));

内存屏障

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

系统相关

//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();

线程调度

这部分包括了线程的挂起、回复、锁等方法,

  • api
//阻塞线程
public native void park(boolean isAbsolute, long time);
//取消阻塞线程
public native void unpark(Object thread);

park

阻塞当前线程直到一个unpark方法出现(被调用)、一个用于unpark方法已经出现过(在此park方法调用之前已经调用过)、线程被中断或者time时间到期(也就是阻塞超时)。在time非零的情况下,如果isAbsolute为true,time是相对于新纪元之后的毫秒,否则time表示纳秒

unpark

释放被park创建的在一个线程上的阻塞。这个方法也可以被使用来终止一个先前调用park导致的阻塞。这个操作是不安全的,因此必须保证线程是存活的(thread has not been destroyed)。

微信搜索 JFound

回复 unsafe 获取 unsafe 的思维导图

回复 juc 获取 juc 的思维导图

JUC整理笔记一之细说Unsafe的更多相关文章

  1. JUC整理笔记二之聊聊volatile

    要想学好JUC,还得先了解 volatile 这个关键字.了解 volatile ,我们从一个例子开始吧. 本文不会很详细去说java内存模型,只是很简单地学习一下volatile 一个例子 pack ...

  2. JUC整理笔记三之测试工具jcstress

    并发测试工具Jcstress使用教程 Jcstress 全称 Java Concurrency Stress,是一种并发压力测试工具,可以帮助研究JVM.java类库和硬件中并发的正确性. Wiki地 ...

  3. JUC学习笔记——共享模型之管程

    JUC学习笔记--共享模型之管程 在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的管程部分 我们会分为以下几部分进行介绍: 共享问题 共享问题解决方案 线程安全分析 Monitor ...

  4. canvas学习之API整理笔记(二)

    前面我整理过一篇文章canvas学习之API整理笔记(一),从这篇文章我们已经可以基本了解到常用绘图的API.简单的变换和动画.而本篇文章的主要内容包括高级动画.像素操作.性能优化等知识点,讲解每个知 ...

  5. xmpp整理笔记:发送图片信息和声音信息

    图片和音频文件发送的基本思路就是: 先将图片转化成二进制文件,然后将二进制文件进行base64编码,编码后成字符串.在即将发送的message内添加一个子节点,节点的stringValue(节点的值) ...

  6. xmpp整理笔记:聊天信息的发送与显示

    任何一个信息的发送都需要关注两个部分,信息的发出,和信息在界面中的显示 往期回顾: xmpp整理笔记:环境的快速配置(附安装包)  http://www.cnblogs.com/dsxniubilit ...

  7. xmpp整理笔记:用户网络连接及好友的管理

    xmpp中的用户连接模块包括用户的上线与下线信息展现,用户登录,用户的注册: 好友模块包括好友的添加,好友的删除,好友列表的展示. 在xmpp中 负责数据传输的类是xmppStream,开发的过程中, ...

  8. xmpp整理笔记:xmppFramework框架的导入和介绍

    一个将要开发xmpp的项目,建议在项目刚创建就导入框架,这样可以避免一些自己操作失误造成不必要的损失. xmpp中最常用的框架就是 xmppFrameWork 往期回顾: xmpp整理笔记:环境的快速 ...

  9. jQuery整理笔记文件夹

    jQuery整理笔记文件夹 jQuery整理笔记一----jQuery開始 jQuery整理笔记二----jQuery选择器整理 jQuery整理笔记三----jQuery过滤函数 jQuery整理笔 ...

随机推荐

  1. hdu_2391 Filthy Rich DP

    Filthy Rich Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tota ...

  2. opencv-10-图像滤波-噪声添加与均值滤波-含opencv C++ 代码实现

    开始之前 再说上一篇文章中, 我们想按照噪声产生, 然后将降噪的, 但是限于篇幅, 我就放在这一篇里面了, 说起图像的噪声问题就又回到了我们上一章的内容, 把噪声当作信号处理, 实际上数字图像处理实际 ...

  3. sed 和 awk

    sed [选项] 动作 文件 -n #取消默认输出 ,有n必须要有p,有p加了n才不会有默认输出 -i #真正的替换,修改 -r #支持扩展正则 (* [A-z] '|') 内部命令: p #打印 - ...

  4. MATLAB学习1 之画图函数

    ezplot适用条件 "ezplot"命令可以用于显函数.隐函数和参数方程作图. 不同函数的使用格式 显函数y=f(x),ezplot函数的调用格式为ezplot(f, [xmin ...

  5. div/dom元素拖拽缩放插件,纯js实现拖拽缩放,不依赖jQuery~

    产品需求,需要用到对div(dom)进行拖拽缩放操作,看到有好多插件,要么依赖jQuery,要么文件太大. 封装了一个插件,不压缩状态下5KB. html <!DOCTYPE html> ...

  6. CF1316E Team Building

    CF1316E [Team Building] 状压dp,感觉比D简单 \(f[i][s]\),表示考虑前\(i\)个人,状态为\(s\)(\(s\)的第\(j-1\)个二进制位表示队员的第\(j\) ...

  7. LeetCode 25. K 个一组翻转链表 | Python

    25. K 个一组翻转链表 题目来源:https://leetcode-cn.com/problems/reverse-nodes-in-k-group 题目 给你一个链表,每 k 个节点一组进行翻转 ...

  8. spring bootweb综合开发的整理

    1.json接口开发 当前开发中微服务的概念日渐深入人心,所以json数据交互可以带来的便利也不言而喻.在springboot中json数据的返回方式比较简单,只需要用@RestController注 ...

  9. 洛谷p1149

    一道很有意思的题目嘞. 这道题目看起来,用搜索似乎无疑了. 我想了这样一个办法(看了很多博客似乎都没用这种方法),可能是觉得太麻烦了吧: 1.我们先把0到9的数字排列,找出排列消耗火柴等于0的序列.这 ...

  10. Pycharm修改HTML模板