博客做网站,域名注册查询,2015年做网站行不行,国外做展台搭建的设计网站【Java多线程学习5】什么是悲观锁#xff0c;什么是乐观锁#xff1f;如何实现乐观锁、乐观锁存在哪些问题
一、什么是悲观锁
概述
悲观锁总是假设最坏的情况#xff0c;认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改)#xff0c;所以每次在获取资源操作…【Java多线程学习5】什么是悲观锁什么是乐观锁如何实现乐观锁、乐观锁存在哪些问题
一、什么是悲观锁
概述
悲观锁总是假设最坏的情况认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改)所以每次在获取资源操作的时候都会上锁这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说共享资源每次只给一个线程使用其它线程阻塞用完后再把资源转让给其它线程。
像Java中synchronized和ReentrantLock等独占锁就是悲观锁的思想。
synchronized示例如下
private Lock lock new ReentrantLock();public void deal() {//上锁lock.lock();try {//需要同步的操作} finally {//释放锁lock.unlock();}}ReentrantLock示例如下
private Lock lock new ReentrantLock();public void deal() {//上锁lock.lock();try {//需要同步的操作} finally {//释放锁lock.unlock();}}高并发的场景下激烈的锁竞争会造成线程阻塞大量阻塞线程会导致系统的上下文切换增加系统的性能开销。并且若未及时释放锁悲观锁还可能会存在死锁问题影响代码的正常运行。
二、什么是乐观锁
概述
乐观锁总是假设最好的情况认为共享资源每次被访问的时候不会出现问题线程可以不停地执行无需加锁也无需等待只是在提交修改的时候去验证对应的资源也就是数据是否被其它线程修改了
乐观锁的实现方式版本号机制 或 CAS算法
总体来说
悲观锁通常多用于写比较多的情况下多写场景竞争激烈这样可以避免频繁失败和重试影响性能悲观锁的开销是固定的。不过如果乐观锁解决了频繁失败和重试这个问题的话比如LongAdder也是可以考虑使用乐观锁的要视实际情况而定。乐观锁通常多于写比较少的情况下多读场景竞争较少这样可以避免频繁加锁影响性能。不过乐观锁主要针对的对象是单个共享变量参考java.util.concurrent.atomic包下面的原子变量类。
乐观锁的实现方式
乐观锁一般会使用版本号机制 或 CAS算法。CAS算法相对多一些。
方式一版本号机制
一般是在数据表中加上一个数据版本号 version 字段表示数据被修改的次数。当数据被修改时version 值会加一。当线程 A 要更新数据值时在读取数据的同时也会读取 version 值在提交更新时若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新否则重试更新操作直到更新成功。
举一个例子 有一个账户金额表 当前账户金额100表中有一个version字段当前值为1。 线程a向账户中存100线程a读取version为1此时线程b也读取了version的数据为1并要取20。 线程a将version1和金额200一同提交到数据库更新由于线程a的提交版本等于当前数据库的版本更新成功。 线程b也将version1金额80提交到数据库更新由于线程b的提交版本1不等于当前数据库的版本2操作b的提交被驳回。
方式二CAS算法
CAS 的全称是 Compare And Swap比较与交换算法 用于实现乐观锁的一种技术。
CAS算法的思想简单核心思路是比较内存中的当前变量的值与预期值是否相等如果相等则将其值改为新值如果不相等则当前线程放弃更新CAS操作失败。
CAS操作设计的三个操作数
V要更新的变量值(Var)E预期值(Expected)N拟写入的新值(New) 当且仅当 V 的值等于 E 时CAS 通过原子方式用新值 N 来更新 V 的值。如果不等说明已经有其它线程更新了 V则当前线程放弃更新。
举一个简单的例子 线程 a 要修改变量 i 的值为 10i 原值为 1V 1E1N10假设不存在 ABA 问题。 i 与 1 进行比较如果相等 则说明没被其他线程修改可以被设置为 6 。 i 与 1 进行比较如果不相等则说明被其他线程修改当前线程放弃更新CAS 操作失败。 当多个线程同时使用 CAS 操作一个变量时只有一个会胜出并成功更新其余均会失败但失败的线程并不会被挂起仅是被告知失败并且允许再次尝试当然也允许失败的线程放弃操作。
乐观锁存在的问题
1、ABA 问题
ABA 问题是乐观锁最常见的问题。 ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。
JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用并且当前标志是否等于预期标志如果全部相等则以原子方式将该引用和该标志的值设置为给定的更新值
2、只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效当操作涉及跨多个共享变量时 CAS 无效 也无法实现对代码块或是方法进行原子操作。
3、循环时间开销大
CAS 经常会用到自旋操作来进行重试也就是不成功就一直循环执行直到成功。如果长时间不成功会给 CPU 带来非常大的执行开销。