悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以 在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态,悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加 排它锁 。如果获取锁失败, 则说明数据正在被其他线程修改,当前 线程则等待或者抛出异常常。 如果获取锁成功,则对记录进行操作 ,然后提交事务后释放排它锁;
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
select * from table where id=? for update;
乐观锁 相对悲观锁来说的,它认为数据在一般情况下不会造成冲 ,所以在访问记 录前不会加排它锁,而是在进行数据提交更新时,才会正式对数据冲突与否进行检测 。
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁
乐观锁一般会使用版本号机制或CAS(Compare-and-Swap,即比较并替换)算法实现。
版本号机制和CAS算法
乐观锁的缺点
ABA 问题
循环时间长开销大
只能保证一个共享变量的原子操作
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
- 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
- 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。