乐观锁、悲观锁、CAS、synchronized、AQS、ReentrantLock、Semaphore

参考

https://mp.weixin.qq.com/s/WtAdXvaRuBZ-SXayIKu1mA

乐观锁

CAS(Compare And Swap 比较并且替换)

  • 是乐观锁的一种实现方式
  • 是一种轻量级锁
  • JUC 中很多工具类的实现就是基于 CAS 的。

线程在读取数据时不进行加锁,在准备写回数据时,先去查询原值,操作的时候比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。

CAS 整体是一个原子操作

乐观锁、悲观锁、CAS、synchronized、AQS、ReentrantLock、Semaphore

例子

AtomicInteger 自增函数 incrementAndGet() 就是这样实现的,其中就有大量循环判断的过程,直到符合条件才成功。
乐观锁、悲观锁、CAS、synchronized、AQS、ReentrantLock、Semaphore

实践

订单表,流水表

一直循环时 的性能问题

ABA 问题

乐观锁、悲观锁、CAS、synchronized、AQS、ReentrantLock、Semaphore

  1. 线程1读取了数据A
  2. 线程2读取了数据A
  3. 线程2通过CAS比较,发现值是A没错,可以把数据A改成数据B
  4. 线程3读取了数据B
  5. 线程3通过CAS比较,发现数据是B没错,可以把数据B改成了数据A
  6. 线程1通过CAS比较,发现数据还是A没变,就写成了自己要改的值
解决方法

增加自增的字段,操作一次就自增加一,或者增加时间戳,比较时间戳的值。

只能保证一个共享变量的原子操作

CAS操作单个共享变量的时候可以保证原子的操作,多个变量就不行了,JDK 5之后 AtomicReference可以用来保证对象之间的原子性,就可以把多个对象放入CAS中操作。

悲观锁

synchronized

synchronized (可以看成)是重量级锁

但是,随着 Java SE 1.6 对 synchronized 进行了各种优化之后,有些情况下它就并不那么重,Java SE 1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁轻量级锁

针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用 偏向锁 优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会 短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为 重量级锁

锁只能升级,不能降级。

AQS(Abstract Queued Synchronizer 队列同步器)

  • 实现 ReentrantLock(独占锁) 的基础
  • 实现 Semaphore(共享锁) 的基础
    乐观锁、悲观锁、CAS、synchronized、AQS、ReentrantLock、Semaphore
  1. AQS 有一个 state 标记位,值为1 时表示有线程占用,其他线程需要进入到同步队列等待,同步队列是一个双向链表。

  2. 获得锁 的线程需要等待某个条件时,会进入 condition 的等待队列,等待队列可以有多个。

  3. 当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。

ReentrantLock 内部有公平锁和非公平锁两种实现,差别就在于 新来的 线程是否比 已经在同步队列中的等待线程 更早获得锁。

Semaphore 也是基于 AQS 的,差别在于 ReentrantLock 是 独占锁,Semaphore 是 共享锁

  • 独占锁:一个锁在某一时刻只能被 一个 线程占有
  • 共享锁:一个锁可以同时被 多个 线程拥有。