锁机制
在数据库操作过程中,为了避免两个或多个用户同时对一条数据操作,通常采用锁的机制来来解决数据冲突问题。
同样,在程序流程中为了避免对多线程共享的资源的修改冲突,也采用锁的机制来避免修改冲突
锁的分类
乐观锁(Optimistic Lock)
所谓乐观锁,就是相信大部分场景下,不会产生数据修改冲突,所以在读取数据进行修改的时候,不对数据进行加锁,而是在最终提交修改的时候,通过version或CAS机制,检查对数据的修改是否发生了冲突。
乐观锁的适用于读多写少的场景,能提供系统的处理能力,如果在冲突比较概率高的场景使用乐观锁,反而会降低系统的处理能力。
悲观锁(Pessimistic Lock)
所谓悲观锁,就是认为对数据修改发生冲突的概率比较大,所以在读取数据进行修改的时候,先用“排他写锁”锁住数据,Block其他人的操作,等修改完成后,再释放锁。
此模式比较适用于数据修改冲突发生概率高的场景,但会一定程度降低系统的处理能力。
数据库场景
悲观锁
数据的行锁、表锁、读锁、写锁都属于悲观锁,典型的就是select * from xxx where id=n for update
命令。
乐观锁
CAS机制(compare and swap)
假设有一条订单(order)数据,ID为1,订单状态(status)是已付款,这个时候,商家打开订单列表,准备进行发货;在商家打开订单后,这时候用户在APP端取消了订单,但是商家不知道;商家执行发货的时候;这时候商家操作发货,如果只根据ID进行更新:
update order set status='已发货' where id=1
则会导致取消的订单被发货,此时,使用CAS机制,在更新数据的时候检查订单状态是否正确:
update order set status='已发货' where id=1 and status='已付款'
并通过检查update语句发返回值,可以确认时数据更新是否成功。
Version机制
Version机制,是在order表中增加一个数字型的version字段,每次查下数据的时候,都带上version字段。更新数据是,把version字段加1。以上述订单为例,比如:
- 订单创建后version为1;
- 付款后version为2;
- 此时商家准备发货,读到的version为2;
- 用户取消订单后,version为3;
- 商家发货是,更新订单状态是,发现version不是读取时的2,说明订单已经被更新,系统驳回商家的修改,并提升商家。
JAVA锁场景
Java中, java.util.concurrent.atomic
包下的原子变量属于使用CAS计算的乐观锁。
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);
}
}
getAndIncrement 采用了CAS机制,每次从内存中读取数据,然后将此数据和 +1 后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
compareAndSet 利用JNI来完成CPU指令的操作:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
unsafe.compareAndSwapInt(this, valueOffset, expect, update)逻辑类似如下:
if (this == expect) {
this = update
return true;
} else {
return false;
}
而synchronized
关键字属于悲观锁。
CAS的问题
ABA问题
如线程1读取了一个变量的值为A;这时候线程2修改变量的值B;线程3有把变量值改回为A;此时,线程1再去更新此变量,会认为此变量未被其他人更新过,但其实变量已经被更新了多次。
所以CAS是适用于对象子包含单个共享变量的原子操作,对于对象中包含多个共享变量的情况无法保证原子性。
锁开销
对于资源竞争比较激烈的情况,CAS自旋的概率较大,会导致CPU开销增大,效率会低于synchronized
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。