如何实现乐观锁
如何实现乐观锁
- 实现乐观锁一般有两种方式:
- 版本号机制
- CAS(Compare-And-Swap)算法
- Java实现CAS主要依赖于
Unsafe类提供的底层原子操作方法
1. 版本号机制
- 一般是在数据表中加上一个数据版本号
version字段表示数据被修改的次数。当数据被修改时,version会加1。线程在读取数据时会同时读取version,在提交更新时只有读取到的version和当前数据库中的version一致时才允许更新,否则重试更新操作,直到更新成功
2. CAS(Compare-And-Swap)算法
- CAS全称是
Compare And Swap(比较与交换)。CAS的思想就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新 - CAS是一个原子操作,底层依赖于一条CPU的原子指令。
- CAS涉及到三个操作数:
V:要更新的变量值(Var)E:预期值(Expected)N:拟写入的新值(New)
- 当且仅当
V的值与E相等时,才会将V的值更新为N - 示例:假设线程A要修改变量i的值为6,i原来的值为1(即
V=1, E=1, N=6,假设不存在ABA问题)- i与1进行比较,如果相等,则说明没被其他线程修改,可以修改为6
- i与1进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更改,CAS操作失败,线程A可以选择重试
3. Java中CAS是如何实现的
- 在Java中,实现CAS操作的一个关键类是
Unsafe Unsafe类位于sun.misc包中,是一个提供低级别、不安全操作的类。Unsafe类提供了compareAndSwapInt、compareAndSwapLong、compareAndSwapObject等方法来实现对int、long、Object类型的CAS操作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* 以原子方式更新对象字段的值。
*
* @param o 要操作的对象
* @param offset 对象字段的内存偏移量
* @param expected 期望的旧值
* @param x 要设置的新值
* @return 如果值被成功更新,则返回 true;否则返回 false
*/
boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
/**
* 以原子方式更新 int 类型的对象字段的值。
*/
boolean compareAndSwapInt(Object o, long offset, int expected, int x);
/**
* 以原子方式更新 long 类型的对象字段的值。
*/
boolean compareAndSwapLong(Object o, long offset, long expected, long x);Unsafe类中的CAS方法是native方法。native关键字表明这些方法是使用本地代码(通常是C或C++)实现的,这些方法直接调用底层的硬件指令来实现原子操作。也就是说,Java语言并没有直接用Java实现CAS,而是通过C++内联汇编的形式实现的(通过JNI调用)。java.util.concurrent.atomic包提供了一些用于原子操作的类,这些类利用底层的原子指令确保多线程环境下的线程安全。- 例如
AtomicInteger是Java原子类之一,主要用于对int类型的变量进行原子操作,核心源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35// 获取 Unsafe 实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 获取“value”字段在AtomicInteger类中的内存偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 确保“value”字段的可见性
private volatile int value;
// 如果当前值等于预期值,则原子地将值设置为newValue
// 使用 Unsafe#compareAndSwapInt 方法进行CAS操作
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 原子地将当前值加 delta 并返回旧值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// 原子地将当前值加 1 并返回加之前的值(旧值)
// 使用 Unsafe#getAndAddInt 方法进行CAS操作。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 原子地将当前值减 1 并返回减之前的值(旧值)
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
} Unsafe#getAndAddInt源码:1
2
3
4
5
6
7
8
9
10
11// 原子地获取并增加整数值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// 以volatile方式获取对象o在内存偏移量offset处的整数值
v = getIntVolatile(o, offset);
// 尝试将当前值加上delta并更新
} while (!compareAndSwapInt(o, offset, v, v + delta));
// 返回旧值
return v;
}- 通过代码可以看出,
getAndAddInt方法会通过compareAndSwapInt方法来尝试更新value的值,如果更新失败,它会重新获取当前值并再次尝试更新,直到操作成功 - 由于CAS操作可能会因为并发冲突而失败,因此通常与
while循环搭配使用,这就是自旋锁机制。
