1. 概念介绍
sun.misc.Unsafe
是 Java 中的一个特殊类,它提供了一组低级别的、不安全的操作,这些操作通常是 JVM 内部使用的。由于这些操作非常强大且危险,因此 Unsafe
类被设计为只能在受信任的代码中使用。
2. 主要功能和用途
-
内存操作:
- 直接内存访问:
Unsafe
允许直接操作内存地址,绕过 Java 的内存管理机制。例如,allocateMemory
和freeMemory
方法可以分配和释放内存。 - 数组操作:
arrayBaseOffset
和arrayIndexScale
方法可以获取数组的基地址和元素大小,从而实现对数组的直接操作。
- 直接内存访问:
-
对象操作:
- 对象创建:
allocateInstance
方法可以直接创建对象实例,而不调用构造函数。 - 字段访问:
objectFieldOffset
方法可以获取对象字段的偏移量,从而实现对字段的直接读写。
- 对象创建:
-
线程操作:
- 线程挂起和恢复:
park
和unpark
方法可以挂起和恢复线程。
- 线程挂起和恢复:
-
CAS(Compare-And-Swap)操作:
- 原子操作:
compareAndSwapInt
、compareAndSwapLong
等方法提供了原子性的比较和交换操作,这是实现无锁数据结构的基础。
- 原子操作:
-
内存屏障:
- 内存屏障:
loadFence
、storeFence
和fullFence
方法可以插入内存屏障,控制内存操作的顺序。
- 内存屏障:
示例代码
以下是一些 Unsafe
类的常见用法示例:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeExample {
public static void main(String[] args) throws Exception {
// 获取 Unsafe 实例
Unsafe unsafe = getUnsafe();
// 直接分配内存
long address = unsafe.allocateMemory(1024);
System.out.println("Allocated memory at address: " + address);
// 写入和读取内存
unsafe.putByte(address, (byte) 42);
byte value = unsafe.getByte(address);
System.out.println("Read byte from memory: " + value);
// 释放内存
unsafe.freeMemory(address);
// 创建对象实例而不调用构造函数
ExampleClass obj = (ExampleClass) unsafe.allocateInstance(ExampleClass.class);
System.out.println("Created instance without calling constructor: " + obj);
// CAS 操作
long offset = unsafe.objectFieldOffset(ExampleClass.class.getDeclaredField("value"));
boolean success = unsafe.compareAndSwapInt(obj, offset, 0, 42);
System.out.println("CAS operation result: " + success);
System.out.println("Updated value: " + obj.getValue());
}
private static Unsafe getUnsafe() throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
}
static class ExampleClass {
private int value;
public ExampleClass() {
this.value = 10;
}
public int getValue() {
return value;
}
}
}
其中运行结果如下:
private static Unsafe getUnsafe() throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
}
其中theUnsafe
是unsafe中的属性,目的是为了获取unsage对象来操作。
源码如下
package sun.misc;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
public final class Unsafe {
private static final Unsafe theUnsafe;
public static final int INVALID_FIELD_OFFSET = -1;
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
```
3. 注意事项
- 安全性:
Unsafe
类提供了非常底层的操作,使用不当可能会导致 JVM 崩溃、内存泄漏或其他未定义行为。因此,它通常只应在受信任的代码中使用。 - 兼容性:
Unsafe
类是sun.misc
包的一部分,这意味着它不是 Java 标准库的一部分,可能在未来的 Java 版本中被移除或更改。 - 性能:虽然
Unsafe
类提供了高性能的操作,但它们通常也更复杂且难以调试。
总结
sun.misc.Unsafe
类提供了许多强大的底层操作,适用于需要高性能或特殊操作的场景。然而,由于其潜在的危险性和非标准性,使用时应格外小心。
4. 举例说明: AtomicBoolean
4.1 源码如下
public class AtomicBoolean implements java.io.Serializable {
private static final long serialVersionUID = 4654671469794556979L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicBoolean.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
...
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public boolean weakCompareAndSet(boolean expect, boolean update {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
/**
* Unconditionally sets to the given value.
*
* @param newValue the new value
*/
public final void set(boolean newValue) {
value = newValue ? 1 : 0;
}
/**
* Eventually sets to the given value.
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(boolean newValue) {
int v = newValue ? 1 : 0;
unsafe.putOrderedInt(this, valueOffset, v);
}
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();
} while (!compareAndSet(prev, newValue));
return prev;
}
方法中getAndSet
是原子操作,来设置值。
说实话weakCompareAndSet
和compareAndSet
两个方法没看出来有什么区别。
为了保证原子性,value还是使用volatile 修饰。
volatile 是 Java 中的一个关键字,用于修饰变量。它的主要作用是确保变量的可见性和禁止指令重排序。以下是 volatile 的详细作用和使用场景:
- 可见性(Visibility)
当一个变量被声明为 volatile 时,所有线程都能看到该变量的最新值。具体来说:
- 写操作:当一个线程对 volatile 变量进行写操作时,该操作会立即刷新到主内存中。
- 读操作:当一个线程对 volatile 变量进行读操作时,该操作会从主内存中读取最新的值,而不是从线程的本地缓存(如 CPU 缓存)中读取。
- 禁止指令重排序(Preventing Instruction Reordering)
volatile 变量的读写操作会被编译器和处理器特殊对待,以确保它们不会被重排序。具体来说:
- 写操作:在写 volatile 变量之前,所有之前的写操作都会被刷新到主内存中。
- 读操作:在读 volatile 变量之后,所有之后的读操作都会从主内存中读取最新的值。
4.2 使用场景
volatile 通常用于以下场景:
- 状态标志:
当一个变量用于表示某种状态标志(如 boolean 类型的标志)时,可以使用 volatile 来确保所有线程都能看到最新的状态。
例如,一个线程可以设置一个 volatile 的 boolean 变量来通知其他线程某个操作已经完成。 - 单例模式的双重检查锁定(Double-Checked Locking):
在单例模式的实现中,可以使用 volatile 来确保实例的初始化操作不会被重排序,从而避免潜在的线程安全问题。
4.3 注意事项
- 原子性
- volatile 只能保证变量的可见性和禁止指令重排序,但不能保证复合操作的原子性。例如,volatile 不能替代 synchronized 或 AtomicInteger 等原子类来实现原子操作。
- 性能:
- volatile 的性能开销比普通变量要高,因为它涉及到主内存的读写操作。因此,只有在确实需要确保可见性和禁止重排序时才使用 volatile。
5. compareAndSwapInt 底层原理
Unsafe 类的 compareAndSwapInt 方法是一个本地方法(native method),它的底层实现依赖于操作系统和硬件平台。具体来说,它通常利用了处理器提供的原子性指令(如 x86 架构上的 CMPXCHG 指令)来实现比较和交换操作。
5.1 底层实现原理
-
处理器指令:
compareAndSwapInt
方法的核心是利用处理器的原子性指令来确保操作的原子性。在 x86 架构上,这个指令通常是CMPXCHG
。CMPXCHG
指令会比较目标内存位置的值与一个期望值,如果相等,则将新值写入目标内存位置;否则,不进行任何操作。
-
内存屏障:
- 为了确保操作的原子性和顺序性,
compareAndSwapInt
方法通常会插入内存屏障(memory barrier)。内存屏障可以防止指令重排序,确保在屏障之前的所有内存操作都完成之后,屏障之后的内存操作才能开始。
- 为了确保操作的原子性和顺序性,
-
操作系统支持:
- 不同的操作系统和硬件平台可能有不同的实现方式。例如,在 Linux 上,JVM 可能会调用内核提供的原子操作函数(如
__sync_bool_compare_and_swap
)。
- 不同的操作系统和硬件平台可能有不同的实现方式。例如,在 Linux 上,JVM 可能会调用内核提供的原子操作函数(如
5.2 示例代码
以下是一个简化的伪代码,展示了 compareAndSwapInt
方法的底层实现思路:
// 伪代码,实际实现可能更复杂
bool compareAndSwapInt(Object* obj, long offset, int expected, int newValue) {
// 获取对象的内存地址
int* address = (int*)((char*)obj + offset);
// 使用处理器指令进行比较和交换
bool success = false;
__asm__ volatile (
"lock cmpxchg %4, (%3)\n"
"sete %0\n"
: "=q" (success)
: "a" (expected), "q" (newValue), "r" (address)
: "memory"
);
return success;
}
5.3 解释
__asm__ volatile
:- 这是 GCC 内联汇编的语法,用于在 C 代码中嵌入汇编指令。
lock cmpxchg %4, (%3)
:这是 x86 架构上的CMPXCHG
指令,它会锁定总线,确保操作的原子性。sete %0
:如果CMPXCHG
操作成功(即目标内存位置的值等于期望值),则将success
设置为true
。
- 内存屏障:
__asm__ volatile
中的"memory"
约束表示这是一个内存屏障,确保在执行这段汇编代码时,之前的所有内存操作都已完成。
5.4 总结
compareAndSwapInt
方法的底层实现依赖于处理器提供的原子性指令(如 CMPXCHG
)和内存屏障,以确保操作的原子性和顺序性。不同的操作系统和硬件平台可能有不同的实现方式,但核心思想是利用处理器的原子性指令来实现比较和交换操作。