Java 按照锁的实现分为乐观锁和悲观锁,
目录
1.为什么要设置锁?
不加锁的情况:
假设余票只有1张;但是事务A和事务B都看到仅剩1张的余票,于是两人同时选择出票,出票后系统将余票更新为0。
这样就产生了错误:明明只有1个票,却卖给了两个人。
加锁类似于:
如果你想洗澡,而且已经有人在使用浴室,你就必须等待。
如果你正在洗澡,则希望可以独享浴室。所以,你通常会进入浴室后从里面锁上门。任何试图使用浴室的人都会被锁挡住。当你洗完后,你会打开门,让其他人进入。
加锁以后:
就不存在卖票的并发问题了。
在上述案例中,加锁以后,A和B竞争最后一张票。如果A胜出,那么A购买时会上锁,不让B购买。
这样A会优先B完成购买,B购买时,余票1是过时的数据,更新后变为0,因此无法购买。
所以需要乐观锁或者悲观锁,来记录一个信息:当前已经读取的数据,是不是已经过时了!
2.乐观锁vs悲观锁
含义 | |
悲观锁 | 就是比较悲观的锁,总是假设最坏的情况,每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程) |
乐观锁 | 假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。 |
两种锁各有优缺点,不可认为一种好于另一种。
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。循环时间长开销大,只对单个共享变量有效,当操作涉及跨多个共享变量时无效。
乐观锁适用场景:
-
当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,
-
因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,
-
而且加锁和释放锁都需要消耗额外的资源。
但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
悲观锁适用场景:
-
悲观并发控制主要用于数据竞争激烈的环境,
-
以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
-
因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。