【Java 线程 · 并发】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;
    }
}

运行结果:
结果

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值