这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战
前言
看过JUC并发包里面的源码,就一定明白Unsafe类是整个java并发包底层实现的核心。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是 native方法,通过使用JNI的方式来访问本地C++实现库。下面就继续深入的看一下Unsafe类。
主要方法
Unsafe有很多public native修饰的方法,还有几十个基于public native方法的其他方法。但是大体可分为以下几类:
(1)初始化操作
(2)操作对象属性
(3)操作数组元素
(4)线程挂起和回复
(5)CAS机制
下面就对Unsafe源码进一步分析。
操作属性方法
1 | vbnet复制代码//通过给定的Java变量获取引用值。这里实际上是获取一个Java对象o中,获取偏移地址为offset的属性的值,此方法可以突破修饰符的抑制,也就是无视private、protected和default修饰符。类似的方法有getInt、getDouble等等。同理还有putObject方法。 |
1 | java复制代码//强制从主存中获取属性值。类似的方法有getIntVolatile、getDoubleVolatile等等。同理还有putObjectVolatile。 |
1 | java复制代码//设置o对象中offset偏移地址offset对应的Object型field的值为指定值x。这是一个有序或者有延迟的putObjectVolatile方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被修改的时候使用才会生效。类似的方法有putOrderedInt和putOrderedLong。 |
1 | java复制代码//返回给定的静态属性在它的类的存储分配中的位置(偏移地址)。 |
操作数组
1 | java复制代码//返回数组类型的第一个元素的偏移地址(基础偏移地址) |
内存管理
1 | java复制代码//获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法。 |
内存屏障
1 | csharp复制代码//在该方法之前的所有读操作,一定在load屏障之前执行完成。 |
线程挂起和恢复
1 | java复制代码//释放被park创建的在一个线程上的阻塞。由于其不安全性,因此必须保证线程是存活的 |
CAS机制
Unsafe类中给出了大量比较并设置和比较并交换方法。这些方法都指向某个native的方法作为底层实现。
1 | java复制代码public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); |
执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作
Unsafe的使用
获取Unsafe实例
Unsafe 提供了getUnsafe方法,如下所示:
1 | typescript复制代码public class Main { |
直接这样用会报异常:
1 | php复制代码Exception in thread "main" java.lang.SecurityException: Unsafe |
因为Unsafe类主要是JDK内部使用,并不提供给普通用户调用。但是仍让可以通过反射获取到实例:
1 | csharp复制代码public static Unsafe testUnsafe() { |
改变私有字段
假设有如下类:
1 | arduino复制代码public class UnsafeDemo { |
下面通过Unsafe来改变私有属性juejin的值。
1 | ini复制代码UnsafeDemo ud = new UnsafeDemo(); |
通过unsafe.putInt直接改变了ud的私有属性的值。一旦通过反射获得了类的私有属性字段,就可以直接操作它的值。
总结
虽然Unsafe看起来不会被用到,但是能帮助我们更好的理解JUC的并发原理,因此还是很有必要学习的。
本文转载自: 掘金