本文最后更新于512 天前,其中的信息可能已经过时,如有错误请发送邮件到mapleleaf2333@gmail.com
悲观锁
总是假设最坏情况,即共享资源每次被访问的时候就会出现问题。即共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
独占锁:synchronized、ReentrantLock
缺点:
- 高并发情况易造成线程阻塞
- 大量阻塞带来频繁的上下文切换,增加系统性能开销
- 易造成死锁
常用场景
用于写比较多的情况,可以避免频繁失败和重试影响性能,它的开销是固定的。
乐观锁
总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待。只是在提交修改的时候去验证对应的资源是否被其它线程修改了。
优点:
不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹
缺点
如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,很影响性能。
原子变量类:AtomicInteger、LongAdder
常见问题
- ABA问题:某段时间变量的值可能被改为其他值,然后又改回 A。单纯CAS会误认为其没修改。解决方法是添加版本号或时间戳
- 循环时间长开销大:CAS用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功,容易造成巨大开销。使用pause指令可以缓解:1.延迟流水线执行;2.避免退出循环时因内存顺序冲突导致CPU流水线被清空。
- 只能保证一个共享变量的原子操作:CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。解决方法:用AtomicReference(把多个变量放在一个对象里来进行 CAS 操作),相当于把多个共享变量合并成一个共享变量来操作。
常用场景
通常多于写比较少的情况下(多读场景,竞争较少),这样可以避免频繁加锁影响性能。
乐观锁实现
版本号机制
数据表中加上一个数据版本号 version
字段,表示数据被修改的次数,修改时version+1。当线程 A 要更新数据值时,在读取数据的同时也会读取 version
值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version
值相等时才更新,否则重试更新操作,直到更新成功。
CAS算法
用一个预期值和要更新的变量值进行比较,两值相等才会进行更新
CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。
CAS 涉及到三个操作数:
- V:要更新的变量值(Var)
- E:预期值(Expected)
- N:拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。