乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
乐观锁: 乐观锁其实是一种思想。相对悲观锁而言,乐观锁假设为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发生了冲突,则让返回给用户错误信息,让用户决定如何去做。
CAS: CAS是乐观锁技术,当多个线程尝试使用CAS同时更新一个变量时,只有其中一个线程能够更新变量,而其他线程都失败,失败的线程不会被挂起,而是被告知失败,并可以再次尝试。
CAS操作中包含三个数据 需要读写的位置(V) , 进行比较的预期原值(A) 和拟写入的新值(B)。如果内存的位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为值B,否则处理器不会做任何操作。无论哪种情况,他都会在CAS指令之前返回该位置的值。(在CAS的一些特殊情况下将返回CAS是否成功,而不是取当前值)CAS有效地说明了“我”认为位置V应该包含值A;如果包含该值,则将B放到这个位置,否则不要更新该位置,告诉“我”这个位置现在的值即可;这其实是乐观锁的冲突检查+数据更新的原理是一样的。
乐观锁是一种思想,CAS是这种思想的实现方式。
Java对CAS的支持
在JDK1.5 中新增java.util.concurrent(J.U.C)就是建立在CAS之上的。相对于对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。
我们以java.util.concurrent中的AtomicInteger为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解getAndIncrement方法,该方法的作用相当于 ++i 操作。
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
在没有锁的机制下需要字段value要借助volatile原语,保证线程间的数据是可见的。这样在获取变量的值的时候才能直接读取。然后来看看++i是怎么做到的。
getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。