LockSupport 线程控制 & Unsafe 核心类
一、概述
LockSupport类,JUC包
中的一个工具类,用来创建锁和其他同步类的基本线程阻塞原语。
Basic thread blocking primitives for creating locks and other synchronization classes
LockSupport类 的核心方法其实就两个:park()
和 unpark()
,其中 park()
方法用来阻塞当前调用线程,unpark()
方法用于唤醒指定线程。
这其实和 Object 类的 wait()
和 signal()
方法有些类似,但是 LockSupport类 的这两种方法从语意上讲比 Object类 的方法更清晰,而且可以针对指定线程进行阻塞和唤醒。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。
二、LockSupport 线程控制
1. 使用方法
LockSupport两个核心方法:
/* park */
public static void park() {
U.park(false, 0L);
}
/* unpark */
public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}
举例测试如何阻塞线程:
import java.util.concurrent.locks.LockSupport;
public class UseLockSupport {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 开始执行执行并进行park等待");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park();
System.out.println("线程 " + Thread.currentThread().getName() + " 解除park等待继续执行");
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t);
}
}
运行结果:
2. 底层实现
LockSupport
对线程的控制基于 Unsafe
类的 线程调度
功能:
/* park */
public static void park() {
U.park(false, 0L);
}
/* unpark */
public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}
三、Unsafe 核心类
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力:
- 不受 JVM 管理,也就意味着无法被 GC,需要我们手动 GC,稍有不慎就会出现内存泄漏。
- Unsafe 部分方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量需程序员计算,若出现问题会导致整个JVM实例崩溃,应用程序也会随之崩溃
- 直接操作内存,其速度更快,在高并发的条件下能很好地提高效率。
Unsafe类是JDK内部用的工具类。它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。
该类不应该在JDK核心类库(条件:当前类加载器为 BootStrapLoader
)之外使用。
1. 创建对象
Unsafe类是
final
的,不允许继承。
在JDK中,创建 sun.misc.Unsafe
对象 需要调用 getUnsafe()
方法,它采用了单例模式,保证全局仅有一个对象:
/* sun.misc.Unsafe类部分代码 */
//私有的构造方法
private Unsafe() {}
//静态变量 (final单例)
private static final Unsafe theUnsafe = new Unsafe();
//获取对象的单例工厂
@CallerSensitive
public static Unsafe getUnsafe() {
//获取当前类的ClassLoader加载器
Class<?> caller = Reflection.getCallerClass();
//caller 是否为 BootStrapLoader
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
//否:抛出异常
throw new SecurityException("Unsafe");
//是:返回对象
return theUnsafe;
}
获取 Unsafe
类对象的条件:当前类加载器为 BootStrapLoader
,否则会抛出异常:
为了获取到 sun.misc.Unsafe
对象,我们可以使用 反射
直接获取内部定义的单例:
/* 获取方法:(可能产生的异常:IllegalAccessException、NoSuchFieldException) */
//获取单例Field对象
Field f = Unsafe.class.getDeclaredField("theUnsafe");
//设置私有访问为true
f.setAccessible(true);
//获取对象
Unsafe unsafe = (Unsafe) f.get(null);
而 sun.misc.Unsafe
内部依赖于 jdk.internal.misc.Unsafe
,但我们不能直接获取jdk.internal.misc.Unsafe
对象(包括反射,因为编译不通过)。
2. 内置方法
我们对方法的展示来自于 jdk.internal.misc.Unsafe
类
① 普通读写
通过 Unsafe 内置方法可以对任何对象进行读写,无论它私有与否:
/*
注解 @HotSpotIntrinsicCandidate:
在 HotSpot 中有一套高效的实现,该高效实现基于CPU指令,运行时 HotSpot 维护的高效实现会替代JDK的源码实现,从而获得更高的效率。
*/
@HotSpotIntrinsicCandidate
public native int getInt(Object o, long offset); //读方法,o代表需要操作内部属性的对象,而offset代表偏移量
@HotSpotIntrinsicCandidate
public native void putInt(Object o, long offset, int x); //写方法,x代表想要写入的值
除此之外,还可以直接在地址上读写:
//从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
@ForceInline
public long getAddress(Object o, long offset) {
if (ADDRESS_SIZE == 4) {
return Integer.toUnsignedLong(getInt(o, offset));
} else {
return getLong(o, offset);
}
}
//存储一个本地指针到一个给定的内存地址,如果不是allocateMemory方法的,结果将不确定
@ForceInline
public void putAddress(Object o, long offset, long x) {
if (ADDRESS_SIZE == 4) {
putInt(o, offset, (int)x);
} else {
putLong(o, offset, x);
}
}
除了基本数据类型以外,还提供了引用类型的读写方法:
// The following deprecated methods are used by JSR 166.
@Deprecated(since="12", forRemoval=true)
public final Object getObject(Object o, long offset) {
return getReference(o, offset);
}
@Deprecated(since="12", forRemoval=true)
public final void putObject(Object o, long offset, Object x) {
putReference(o, offset, x);
}
遗憾的是,在JDK12 以后,这些引用类型读写方法已经 “过时” 了,并不推荐使用,取而代之的是这些方法:
@HotSpotIntrinsicCandidate
public native Object getReference(Object o, long offset);
@HotSpotIntrinsicCandidate
public native void putReference(Object o, long offset, Object x);
② volatile 读写
普通的读写无法保证可见性
和有序性
,而 volatile读写 方法则避免了该问题:
基本数据类型:
@HotSpotIntrinsicCandidate
public native void putIntVolatile(Object o, long offset, int x);
@HotSpotIntrinsicCandidate
public native int getIntVolatile(Object o, long offset);
引用类型:
@HotSpotIntrinsicCandidate
public native Object getReferenceVolatile(Object o, long offset);
@HotSpotIntrinsicCandidate
public native void putReferenceVolatile(Object o, long offset, Object x);
③ 有序写入
有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。
/* sun.misc.Unsafe */
@ForceInline
public void putOrderedInt(Object o, long offset, int x) {
theInternalUnsafe.putIntRelease(o, offset, x);
}
而本方法依赖于方法,完成有序写入:
/* jdk.internal.misc.Unsafe */
@HotSpotIntrinsicCandidate
public final void putIntRelease(Object o, long offset, int x) {
putIntVolatile(o, offset, x);
}
除此之外还有其他类型、引用类型对应的方法,在此不进行列举。
④ CAS 操作
JUC中大量运用了 CAS操作, CAS操作 是 JUC 的基础。Unsafe中提供了 多种类型 的CAS操作:
/* int CAS 操作 */
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
}
/* Object CAS 操作 */
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetReference(Object o, long offset,
Object expected,
Object x);
⑤ 直接内存操作
Unsafe 类为我们提供了直接操作内存的能力,并且这些方法依赖于一些 native
本地方法,为了区分,本地方法以 数字 0
结尾。
分配内存
/* 分配内存 */
public long allocateMemory(long bytes) {
bytes = alignToHeapWordSize(bytes);
//分配内存检查
allocateMemoryChecks(bytes);
if (bytes == 0) {
return 0;
}
//为其分配内存
long p = allocateMemory0(bytes);
//无法分配内存,抛出异常
if (p == 0) {
throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
}
//返回分配结果
return p;
}
重新分配内存
/* 重新分配内存 */
public long reallocateMemory(long address, long bytes) {
bytes = alignToHeapWordSize(bytes);
//重新分配内存检查
reallocateMemoryChecks(address, bytes);
if (bytes == 0) {
freeMemory(address);
return 0;
}
//检查:分配新内存 OR 重新分配内存
long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes);
//无法分配内存,抛出异常
if (p == 0) {
throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
}
//返回分配结果
return p;
}
内存初始化
/* 内存初始化 */
public void setMemory(Object o, long offset, long bytes, byte value) {
setMemoryChecks(o, offset, bytes, value); //内存初始化检查
if (bytes == 0) {
return;
}
setMemory0(o, offset, bytes, value); //内存初始化
}
内存复制
/* 内存复制 */
public void copyMemory(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes) {
copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes); //内存复制检查
if (bytes == 0) {
return;
}
copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes); //内存复制
}
清除内存
/* 清除内存 */
public void freeMemory(long address) {
freeMemoryChecks(address); //清除内存检查
if (address == 0) { //检查地址
return;
}
freeMemory0(address); //清除内存
}
⑥ 线程调度
@HotSpotIntrinsicCandidate
public native void park(boolean isAbsolute, long time);
@HotSpotIntrinsicCandidate
public native void unpark(Object thread);
⑦ 内存屏障
- loadFence:保证在这个屏障之前的所有
读操作
都已经完成。 - storeFence:保证在这个屏障之前的所有
写操作
都已经完成。 - fullFence:保证在这个屏障之前的所有
读写操作
都已经完成。
@HotSpotIntrinsicCandidate
public native void loadFence();
@HotSpotIntrinsicCandidate
public native void storeFence();
@HotSpotIntrinsicCandidate
public native void fullFence();
⑧ 属性偏移量
JVM的实现可以自由选择如何实现Java对象的“布局”,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量来访问某个Java对象的某个字段。
public long objectFieldOffset(Class<?> c, String name) {
if (c == null || name == null) { //检查参数是否为空
throw new NullPointerException();
}
//调用 对应native方法,返回名为“name”属性,相对于c的实例起始位置的偏移量
return objectFieldOffset1(c, name);
}
3. 特殊应用
常见的应用场景如下:
① 无视构造方法
public static void main(String[] args) throws Exception {
Unsafe unsafe = null;
try{
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
A o1 = new A(); // 构造方法
o1.a(); // prints 1
A o2 = A.class.getDeclaredConstructor().newInstance(); //反射
o2.a(); // prints 1
A o3 = (A) unsafe.allocateInstance(A.class); // Unsafe.allocateInstance()
o3.a(); // prints 0
}
static class A {
private long a; // 未经构造方法初始化, 默认为 0
public A() {
this.a = 1; // initialization
}
public long a() {
System.out.println(this.a);
return this.a;
}
}
运行结果:
② 巨型数组
//定义unsafe对象
private static Unsafe unsafe;
public static void main(String[] args) throws Exception {
/* 获取对象 */
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
// 设置数组大小为Integer.MAX_VALUE的2倍
long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
int sum = 0;
for (int i = 0; i < 100; i++) {
array.set((long) Integer.MAX_VALUE + i, (byte) 3);
sum += array.get((long) Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum); // 总数应为300
}
private static Unsafe getUnsafe() {
return unsafe;
}
static class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
public SuperArray(long size) {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
}
运行结果: