从 CAS 到无锁编程
从 CAS 到无锁编程
先尝试实现一个 CAS 自旋锁,我们直接看 ChatGPT 给出的结果
public class SpinLock {
// 使用一个原子变量来表示锁的状态
private volatile boolean isLocked = false;
// 获取锁的方法
public void lock() {
while (true) {
// 如果当前没有被锁住,则尝试将锁设置为已锁定
if (!isLocked) {
// 通过 CAS 操作设置锁状态
if (!compareAndSet(false, true)) {
continue;
}
break;
}
}
}
// 释放锁的方法
public void unlock() {
isLocked = false; // 释放锁
}
// CAS 比较并设置方法,模拟原子操作
private boolean compareAndSet(boolean expectedValue, boolean newValue) {
// 这个地方依然是有问题的
// 从 isLocked == expectedValue 这一步到 isLocked = newValue 一步是没有加锁的,也就是说,
// 执行完 isLocked == expectedValue 之后,执行 isLocked = newValue 之前,别的线程可能会修改 isLocked 的值
if (isLocked == expectedValue) {
isLocked = newValue;
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException {
SpinLock spinLock = new SpinLock();
// 模拟多个线程竞争锁
Runnable task = () -> {
spinLock.lock(); // 获取锁
try {
System.out.println(Thread.currentThread().getName() + " is working");
Thread.sleep(1000); // 模拟一些工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
spinLock.unlock(); // 释放锁
System.out.println(Thread.currentThread().getName() + " finished work");
}
};
// 创建多个线程
Thread thread1 = new Thread(task, "Thread-1");
Thread thread2 = new Thread(task, "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
执行结果
Thread-1 is working
Thread-2 is working
Thread-1 finished work
Thread-2 finished work
从结果看,确实实现了锁的效果,但是实际上这个代码是有问题的,问题在于 compareAndSet 方法,这个方法中从 isLocked == expectedValue
这一步到 isLocked = newValue
一步是没有加锁的,即并不是原子性的,也就是说,执行完 isLocked == expectedValue
之后,执行 isLocked = newValue
之前,别的线程可能会修改 isLocked 的值。
上面这个例子告诉我们,不能盲目相信 AI,它给出的代码可能是错的。
我们简单验证以下,依托于上面的代码,写一个支持原子累加的 Integer 类:
public class MyAtomInt {
private volatile int innerVal = 0;
public MyAtomInt(int initVal) {
this.innerVal = initVal;
}
private boolean compareAndSwap(int expected, int newVal) {
// 这一段无法保证原子操作,会有线程问题
if (innerVal == expected) {
innerVal = newVal;
return true;
}
return false;
}
public void add(int add) {
boolean success = false;
while (!success) {
int nowValue = getIntVal();
success = compareAndSwap(nowValue, nowValue + add);
}
}
// 获取当前值的办法
public int getIntVal() {
return innerVal;
}
public static void main(String[] args) {
MyAtomInt atomInt = new MyAtomInt(10);
Random random = new Random();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
Thread.sleep(random.nextInt(10) * 100);
} catch (InterruptedException e) {
// throw new RuntimeException(e);
}
atomInt.add(1);
System.out.println(atomInt.getIntVal());
}).start();
}
}
}
执行结果是
11
12
14
13
15
16
17
17
18
19
17 出现了两次,也就是说出现了上面的代码线程不安全。
那我们如何保证 isLocked == expectedValue
到 isLocked = newValue
的原子性呢?加锁可以实现,但是枷锁的话就变成了互斥锁了,这不是我们想要的,我们想要的是无锁的设计。
这个时候就只能使用 Unsafe 类了,通过其提供的 compareAndSwapXXX 方法,可直接去内存中对比、修改值,当然这也是危险操作,因此其名字就叫 Unsafe。
public class MyAtomIntFinal {
// setup to use Unsafe.compareAndSwapInt for updates
// 直接调用 Unsafe.getUnsafe() 会报错,只能在静态代码块中通过反射获取
private static Unsafe unsafe = null;
private static final long valueOffset;
static {
try {
//
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true); // 使其可以访问
unsafe = (Unsafe) field.get(null);
valueOffset = unsafe.objectFieldOffset
(MyAtomIntFinal.class.getDeclaredField("innerVal"));
} catch (Exception ex) {
throw new Error(ex);
}
}
private volatile int innerVal = 0;
public MyAtomIntFinal(int initVal) {
this.innerVal = initVal;
}
private boolean compareAndSwap(int expected, int newVal) {
return unsafe.compareAndSwapInt(this, valueOffset, expected, newVal);
}
// 原子增加方法
public void add(int add) {
boolean success = false;
while (!success) {
int nowValue = getIntVal();
success = compareAndSwap(nowValue, nowValue + add);
}
}
// 获取当前值的办法
public int getIntVal() {
return innerVal;
}
public static void main(String[] args) {
MyAtomIntFinal atomInt = new MyAtomIntFinal(10);
Random random = new Random();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(random.nextInt(10) * 100);
} catch (InterruptedException e) {
// throw new RuntimeException(e);
}
new Thread(() -> {
atomInt.add(1);
System.out.println(atomInt.getIntVal());
}).start();
}
}
}
执行结果
11
12
13
14
15
16
17
18
19
20
此时我们再去看 AtomicInteger,会发现跟我们上面的写法以及差不多了
Unsafe
获取 Unsafe
有一点需要注意 Unsafe 类因为其直接操作内存的危险操作,因此在使用上故意设置了门槛,以防止滥用,因此,我们是无法直接初始化 Unsafe 类的,我们需要通过反射的方式才能获取 Unsafe 实例。
从 Java 9 开始,Unsafe
类的使用被认为是危险的,而且不推荐直接使用。Java 9 引入了 VarHandle
作为 Unsafe
的替代,它提供了更安全、更灵活的方式来进行低级的内存操作和原子操作。如果你使用的是 Java 9 或更高版本,应该考虑使用 VarHandle
来替代 Unsafe
。
import java.lang.invoke.*;
public class VarHandleExample {
public static void main(String[] args) throws Exception {
VarHandle handle = MethodHandles.lookup().findVarHandle(VarHandleExample.class, "value", int.class);
VarHandleExample example = new VarHandleExample();
// 设置字段值
handle.set(example, 42);
System.out.println("Value: " + example.value);
}
private volatile int value = 0;
}
了解 Unsafe
参考美团技术团队写的文章,写的非常的好:Java魔法类:Unsafe应用解析
如果链接无效,请看 转载:Java魔法类:Unsafe应用解析