了解过java的锁机制的朋友一定知道java的锁可以分为乐观锁和悲观锁,那么它们分别是什么意思呢?
悲观锁:顾名思义,整体是很丧的。它认为在它对资源进行操作的时候,其他线程也会来对资源进行操作,因此一定要对线程进行一套管理机制,只有持有锁的线程才能对资源进行操作。
乐观锁:整体积极向上。它认为它对资源进行操作的时候,其他线程不会对资源进行操作,因此不需要引入线程管理机制,不用对资源上锁。
CAS 即 compare and swap,比较替换,就是典型的乐观锁机制
CAS中主要用的有三个操作数:内存地址v,旧的预期值A,修改的新值B。
当我们更新一个变量的时候,只有当变量地址v的值是预期值A的时候,才会将内存地址v的对应值修改为B。
举一个通俗的例子,假设我们存在A,B两个舔狗同时想追求我们的一个女神,女神有一个牌子。我们约定当女神的牌子是0时,代表女神现在有空,可以约会,为1时代表女神没空不能约会。所以当A,B看到女神有空时,都会去找女神约会,假设A抢先一步,找到了女神,并且将女神的牌子变成了1,此时B看到了女神的牌子变成了1,知道女神现在没空了,但是他也不会就此罢休,于是他不断等待,等待女神有空的时候,即自旋等待。
但其实这也存在一个问题,就是A,B都看到了女神是0,空闲状态。然后几乎同时到达,认为自己有资格和女神约会,将牌子翻为1。这和我们的设想不符,因此CAS还会引入volatile来进行协助。
volatile可以保证当主存更新后,所有线程中的私有内存全部失效,需要重新从主存中读取数据,然后进行CAS操作。
但是我们使用CAS时会遇见ABA的问题
即当我们想要修改的值在之前已经被改为了B,然后重新改为A,此时我们使用CAS是无法进行判断的。
public static void main(String[] args) {
final AtomicInteger a = new AtomicInteger(1);
Thread main = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("操作的线程:" + Thread.currentThread().getName() + " ,初始值: " + a.get());
try {
int exepectNum = a.get();
int newNum = exepectNum + 1;
Thread.sleep(1000);
boolean b = a.compareAndSet(exepectNum, newNum);
System.out.println("操作的线程:" + Thread.currentThread().getName() + "操作是否成功:" + b);
System.out.println(a.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "主线程");
Thread other = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(20);
System.out.println("操作的线程:" + Thread.currentThread().getName() + "操作后的a值:" + a.incrementAndGet());
System.out.println("-------------------------------------------------");
System.out.println("操作的线程:" + Thread.currentThread().getName() + "操作后的a值:" + a.decrementAndGet());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
main.start();
other.start();
}
如上述代码所示,我们的执行结果是这样的
即我们在对a进行cas操作之前,已经对a进行了替换,但是却依然替换成功,证明我们使用cas操作存在ABA问题。
那么对于这个问题如何解决呢?
- 我们可以使用sychornized关键字进行修饰
- 我们可以使用compareAndSet 带版本号的方式进行解决,即AtomicStampedReference的方法进行解决
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}