首页 > 编程知识 正文

reentrantlock是乐观锁吗,悲观锁容易死锁

时间:2023-05-04 14:10:26 阅读:175837 作者:1329

1、悲观锁1.1定义总是假设最坏的情况,每次去取数据都觉得别人会修改,所以每次去取数据都要加锁,直到别人想取这个数据就加锁(共享资源每次一个线程) 传统的关系数据库经常使用这种锁定机制,例如读锁定、写锁定等,如行锁定、表锁定等。 在Java中,同步锁从偏转锁、轻量锁到重量锁,都是悲观锁。 JDK提供的Lock实现类都是悲观锁。

手动锁定悲观者:

读锁定: LOCK tables test_db read,释放锁定: UNLOCK TABLES; 写入锁定: LOCK tables test_db WRITE,解锁: UNLOCK TABLES; 读/写锁定

更新数据时,锁定时直接施加写锁定,一个线程具有写锁定时,另一个线程无论读还是写都必须等待。如果只有前端读取数据,则锁定时显式进行读锁定如果其他线程也要进行读锁定,则不需要等待。 (读锁定计数器1 )读/写锁定感觉类似于乐观锁定,但读/写锁定是一种悲观锁定策略。 因为读/写锁定并不确定更新前的值是否已更改,而是确定在锁定前应该使用读锁定还是写锁定。 1.2特点优点:能完全保证数据的独占性和准确性。 每次请求都要先锁定数据,然后进行数据操作,最后再解锁,解锁的过程会消耗掉,所以性能不高; 缺点:每次请求都会先对数据进行加锁,然后再进行数据操作,最后解锁,但由于解锁过程涉及到消耗,性能不高2、乐观锁2.1定义总是假设最好的情况,每次去拿数据的时候别人都会修改在更新时判断,是否由他人更新该数据,可以使用版本号机制和CAS算法实现。 乐观锁定适用于多种APP应用类型,可以提高吞吐量。 在Java中,java.util.concurrent.atomic包下的原子变量类是使用乐观锁的实现方法CAS。

形象化记忆:乐观锁定会在提交更新时检测是否存在数据冲突,而不是锁定,因为乐观锁定认为对数据的操作没有冲突。 如果发现冲突,是重新开始,还是切换到悲观的战略。 乐观控件应该解决的是数据库并发方案中的写-写冲突,这意味着可以在没有锁定的情况下解决。

2.2特点优点:乐观锁是并发型锁,本身不锁定数据,通过反复重试CAS实现锁定功能。 不锁定数据意味着允许多个线程同时读取数据,但只有一个线程可以成功更新数据,而更新数据的其他线程重试回滚操作在整个过程中没有“锁定操作”

缺点

2.3乐观锁常见的两种实现方式乐观锁一般采用版本号机制或CAS算法实现。

2.3.1版本号的结构

2.3.2 CAS算法2.3.2.1 CAS算法的定义compare and swap是一种著名的无锁算法。 无锁编程是指在不使用锁的情况下实现多线程之间的变量同步,也就是在线程没有阻塞的状态下实现变量的同步,从而实现无阻塞同步(Non-blocking Synchronization ) CAS算法包括三个操作数

需要读写的存储器值v旧的期望值a只要预定写入的新值bv的值为a,CAS就用原子方式用新值b更新v的值。 否则我什么都不做。 比较和置换是原子操作。 通常是旋转操作,也就是不断地重试。

2.3.2.2示例t1线程和t2线程同时修改内存中的相同变量56时,他们会将主内存的值完全复制到自己的工作内存区域,所以t1线程和t2线程的期望值都是56。 假设t1在与t2线程的竞争中,线程t1可以去更新变量值,其他线程都失败了(失败的线程不是被挂起,而是被告知在这次竞争中失败了,可以再次尝试)。 t1线程将变量的值更改为57,并写入内存。 此时,对于t2,存储器值为57,与期望值56不一致,因此操作失败(要变更的值不再是原来的值)。 CPU去更新某个值,但如果要变更的值不再是原来的值,操作将失败。 因为很明显其他操作先变更了这个值。 比较两者时,如果相等,则表示共享数据未更改,用新值替换,然后继续执行;如果不相等,则表示共享数据已更改,放弃已经执行的操作。 在发生同步冲突的机会较少的情况下,该假设可以大大提高2.3.2.3 CAS算法的缺点ABA问题及其解决方案: thread1操作val=1,2,CAS(val,1,2 )。

thread1先读取val=1; 但是,thread1被切断,让thread2执行。

thread2修正val=3,又恢复为1。 thread1继续执行,发现期望值与“原始值”(实际上已修正)相同,CAS操作完成。

添加标记以指示是否已更改。 从Java1.5开始,JDK的atomic包提供了用于解决ABA问题的AtomicStampedReference类。 该类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,以及当前标志是否等于预期标志,如果全部相等,则按原子方式检查引用和标志的值

设置为给定的更新值。

循环时间长,开销大
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。 只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。所以,我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。 3、两者的比较 1、乐观锁并未真正加锁(不是真正的锁),效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。2、悲观锁依赖数据库锁,效率低。更新失败的概率比较低。3、悲观锁阻塞事务,乐观锁回滚重试,它们各有优缺点,不要认为一种一定好于另一种。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

参考:https://zhuanlan.zhihu.com/p/31537871
参考:https://blog.csdn.net/qq_34337272/article/details/81072874
参考:https://blog.csdn.net/qq_34337272/article/details/81072874
参考:https://mp.weixin.qq.com/s/1o-zFPBWYg4bo05MnJqvrQ
参考:https://mp.weixin.qq.com/s/QKyNL-piwFTfEDZJYSwzXA

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。