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. Libra教程之:move语言的特点和例子

    文章目录 move语言的特点 资源优先 灵活性 安全性 可验证性 Move语句初探 点对点支付交易脚本 Currency Module move语言的特点 Libra的目标是打造一个全球话的金融和货币 ...

  2. 面向对象(OO)第二阶段学习总结

    0.前言 此阶段总共进行三次大作业,其中第一次作业中的第一题,水文数据校验及处理中,遇到较大的难题,第一次接触正则表达式,编码过程中显得难度特别大.第二次作业同样也是对于一元多项式求导中对单项的正则校 ...

  3. qemu-img 整理

    qemu-img命令语法: qemu-img command [command options] check命令: check [-f fmt < qcow2 | qed | vdi >] ...

  4. FastReport.Net中使用列表和数组作为报表数据源

    大多数现代报告工具允许您使用几乎任何数据库,然而,并不是所有报表工具都能以一个数据源的列表或数组来工作.本文中将展示如何使用FastReport .Net报表工具来实现. 请注意以下重要几点: 清单中 ...

  5. 数学--数论--HDU 6128 Inverse of sum (公式推导论)

    Description 给nn个小于pp的非负整数a1,-,na1,-,n,问有多少对(i,j)(1≤i<j≤n)(i,j)(1≤i<j≤n)模pp在意义下满足1ai+aj≡1ai+1aj ...

  6. zwx_helper

    通过重载c++operator,实现一种轻松的wxWidgets界面编程风格,如html编写页面一样直观容易. 举一例,一个界面页有四块区,如果是开发html的话,是从头到脚一气书写 <div ...

  7. 在linux上部署自己开发的web项目

    在linux上部署自己开发的web项目 前言:相信有很多做开发的小伙伴和我之前一样,只会在windows环境下,利用开发工具开发运行web项目,但是却不知道怎么把开发好的项目部署到linux服务器上去 ...

  8. P2816 宋荣子搭积木

    描述:https://www.luogu.com.cn/problem/P2816 saruka非常喜欢搭积木,他一共有n块积木.而且saruka的积木很特殊,只能一块块的竖着摞,可以摞很多列.说过s ...

  9. 最短路 西北大学2019年春季校赛 ( 重现赛 ) 房间迷宫 求一个数的所有的约数nlogn

    题目:https://www.cometoj.com/contest/33/problem/G?problem_id=1461(密码:jwjtxdy) 学习一下 求一个数的约数 复杂度n*logn # ...

  10. 2019国防科大校赛 B Escape LouvreⅡ

    https://ac.nowcoder.com/acm/contest/878/B 这个题目是一个网络流,但是建图却没有那么好建,首先我们都会把每一个人与源点相连,每一个洞口和汇点相连. 然后人和洞口 ...