一、Unsafe 类

  1. 普通类不能直接调用 Unsafe 的 getUnsafe 方法获取 Unsafe 对象,会抛出异常
Unsafe unsafe = Unsafe.getUnsafe();
# 输出结果

Exception in thread "main" java.lang.SecurityException: Unsafe
  1. Unsafe 类的源码有类加载器检查
@CallerSensitive
public static Unsafe getUnsafe() {
    Class<?> caller = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
        throw new SecurityException("Unsafe");
    return theUnsafe;
}
  1. 可以通过反射获取 Unsafe 对象
public static Unsafe getUnsafe() throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    return (Unsafe) field.get(null);
}
try {
    Unsafe unsafe = getUnsafe();
    System.out.println(unsafe);
} catch (Exception e) {
    e.printStackTrace();
}
# 输出结果

sun.misc.Unsafe@682a0b20
  1. Unsafe 类允许开发者像 C 语言一样操作内存,但是极其危险,一旦写错,JVM 就会直接奔溃
public static Unsafe getUnsafe() throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    return (Unsafe) field.get(null);
}
try {
    Unsafe unsafe = getUnsafe();
    System.out.println(unsafe);
    unsafe.putInt(0x12345678, 123);
} catch (Exception e) {
    e.printStackTrace();
}
# 输出结果

sun.misc.Unsafe@682a0b20
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffa6b2eab64, pid=13396, tid=10064
#
# JRE version: Java(TM) SE Runtime Environment (17.0.7+8) (build 17.0.7+8-LTS-224)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (17.0.7+8-LTS-224, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# Problematic frame:
# V  [jvm.dll+0x7aab64]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# D:\javaCode\MyTest\hs_err_pid13396.log
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
#

二、源码角度看 CAS

AtomicInteger atomicInteger  = new AtomicInteger(10);

atomicInteger.incrementAndGet();
public final int incrementAndGet() {
    return U.getAndAddInt(this, VALUE, 1) + 1;
}
  1. this:当前 AtomicInteger 对象

  2. VALUE:value 字段在对象内存中的地址偏移量(静态初始化时算好的)

  3. 1:要增加的值

  4. + 1:因为 getAndAddInt 方法返回旧值,加1才得到新值

@IntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}
@IntrinsicCandidate
public native int getIntVolatile(Object o, long offset);
@IntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset,
                                            int expected,
                                            int x) {
    return compareAndSetInt(o, offset, expected, x);
}
@IntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                                int expected,
                                                int x);
  1. @IntrinsicCandidate:这个注解就像给方法贴了个标签,告知 JVM,这方法性能要求高,如果能用更高效的方式实现(例如,CPU 指令),就别用 Java 代码了(建议性的,而非强制性的)

  2. v = getIntVolatile(o, offset);:获取当前值,带 volatile 语义,强制从主存读

  3. weakCompareAndSetInt(o, offset, v, v + delta):尝试 CAS 更新,如果还是 v,说明没人改过,改成 v + delta,如果失败了就重试(自选)


三、ABA 问题

1、基本介绍
线程 1:读取到 v 为 100,准备做 CAS

线程 2:把 100 改成 101,做点操作,又改回 100

线程 1:做 CAS(100 -> 101)成功

以为没人动过,其实中间变了
2、ABA 问题影响场景
(1)无影响的场景
  • 只关心最终数值,中间过程不影响业务,ABA 相当于白折腾,结果对就行
  1. 计数器
线程 1:想从 5 加到 6

线程 2:5 -> 6 -> 5(加 1 又减 1)

线程 1:CAS(5 -> 6)成功

最终结果 6,业务正确(只关心最终值)
  1. 库存扣减
要扣 1 件:10 -> 9

别人折腾:10 -> 9 -> 10(下单又退货)

你 CAS(10 -> 9)成功

库存 9,正确(过程不重要,只关心最终数量)
(2)有影响的场景
  1. 栈 / 链表结构
public class Stack {
    private AtomicReference<Node> top = new AtomicReference<>();
    
    public Node pop() {
        Node oldTop = top.get();
        Node newTop = oldTop.next;
        if (top.compareAndSet(oldTop, newTop)) {
            return oldTop;
        }
        return null;
    }
}
初始:A -> B -> C(当前:top = A)

线程 1:想弹出 A(期望:top = A,新 top = B)

线程 2:弹出 A(当前:top = B)

线程 2:弹出 B(当前:top = C)

线程 2:推入 A(当前:top = A,A.next = C)

线程 1:CAS 发现 top 还是 A,成功

结果:弹出 A,但实际 top 变成 C,期望的 top 是 B,B 已经不在栈中了
  1. 账户扣款
初始 state:balance = 1000, locked = false

线程 1:开始扣款 500(当前 state:balance = 1000, locked = false)

线程 2:由于某些原因冻结账户(当前 state:balance = 1000, locked = true)

线程 2:发现误冻结,立即解冻(当前 state:balance = 1000, locked = false)

线程 1:恢复执行,发现 state 未改变,扣款成功,但是中间的状态标志没有记录
  1. 更简单的理解,借书流程
线程 1:准备借书(状态 = 可借)

线程 2:管理员把书拿去消毒(状态 = 消毒中)

线程 2:消毒完放回(状态 = 可借)

线程 1:直接借走

书被消毒过这个信息丢了,下个读者不知道这本书刚消过毒

四、ABA 问题处理策略

1、基本介绍
  1. AtomicStampedReference:用 int 版本号,可多次变化(0 -> 1 -> 2…)

  2. AtomicMarkableReference:用 boolean 标记,只能表示是否被修改过(false -> true -> false…)

2、演示
  1. 使用 AtomicStampedReference
// 初始化
AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("test", 0);

// get 方法用于同时获取值和版本
int[] stampHolder = new int[1];
String value = atomicStampedReference.get(stampHolder);
System.out.println("值: " + value + ", 版本: " + stampHolder[0]);

// 尝试更新
boolean result = atomicStampedReference.compareAndSet("test", "content", 0, 1);
System.out.println("更新结果: " + result);

// getReference 方法用于获取值,getStamp 方法用于获取版本
System.out.println("新值: " + atomicStampedReference.getReference() + ", 新版本: " + atomicStampedReference.getStamp());
# 输出结果

值: test, 版本: 0
更新结果: true
新值: content, 新版本: 1
  1. 使用 AtomicMarkableReference
// 初始化
AtomicMarkableReference<String> atomicMarkableReference = new AtomicMarkableReference<>("test", false);

boolean[] markHolder = new boolean[1];
String value = atomicMarkableReference.get(markHolder);
System.out.println("值: " + value + ", 标记: " + markHolder[0]);

// 尝试更新
boolean result = atomicMarkableReference.compareAndSet("test", "content", false, true);
System.out.println("更新结果: " + result);
System.out.println("新值: " + atomicMarkableReference.getReference() + ", 新标记: " + atomicMarkableReference.isMarked());
# 输出结果

值: test, 标记: false
更新结果: true
新值: content, 新标记: true
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐