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. js 运动函数篇(二) (加速度运动、弹性运动、重力场运动(多方向+碰撞检测+重力加速度+能量损失运动)拖拽运动)层层深入

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS写加速度运动.弹性运动.重力场运 ...

  2. css套路学习(一)

    css3信息获取方法 文档搜索:Google css spec; Google: 关键词 MDN; css tricks成熟css代码块sinppets; Google: center css tri ...

  3. 在okhttp的callback回调中加Toast出现Cant create handler inside hread that has not called Looper.prepare()...

    2019独角兽企业重金招聘Python工程师标准>>> 分析:callback中回调的response方法中还是在子线程中运行的,所以要调取Toast必须回到主线程中更新ui 解决方 ...

  4. 全网最简单明了的MySQL连接Eclipse方法(JDBC详细安装方式及简单操作)2020新版

    Step 1 你得有Eclipse 没有出门右拐,我教不了你. Step 2 你得有Mysql MySQL的详细安装过程,我在另一篇博客中给出.戳我 Step 3 安装JDBC 可以去官网下,如果用的 ...

  5. Scrapy爬虫快速入门

    安装Scrapy Scrapy是一个高级的Python爬虫框架,它不仅包含了爬虫的特性,还可以方便的将爬虫数据保存到csv.json等文件中. 首先我们安装Scrapy. pip install sc ...

  6. VUE简单整理

    在用 Vue.js 构建大型应用时推荐使用 NPM 安装: # 最新稳定版 $ cnpm install vue 命令行工具 Vue.js 提供一个官方命令行工具,可用于快速搭建大型单页应用. # 全 ...

  7. centos安装libconfig

    安装很简单,生成的.so文件会被安装到/usr/local/lib目录,记得修改/etc/profile. 安装过程会出现两个错误: What is makeinfo, and how do I ge ...

  8. Circle of Monsters(贪心)

    n个怪物围成一圈,每个怪物有自己的血量和爆炸伤害. 怪物在死后会对下一个怪物造成爆炸伤害,又死了又可以爆炸...... 你每发子弹可以对怪物造成1点伤害,求杀死所有怪物的最小子弹数. 传送门 \(\c ...

  9. 记一次jackson序列化Boolean的坑

    @Data public class CouponTemplateDto { /** * 优惠券类型id */ private Long couponTypeId; /** * 优惠券模板id */ ...

  10. 记一次sqoop安装后测试的问题

    运行命令: sqoop import --connect "jdbc:mysql://x.x.x.x:3306/intelligent_qa_bms?useUnicode=true& ...