create new file

pull/9/head
hmao 5 years ago committed by GitHub
parent d0671c8052
commit 94dcffa5df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,60 @@
乐观锁顾名思义就是在操作时很乐观,认为操作不会产生并发问题(不会有其他线程对数据进行修改)因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改一般会使用版本号机制或CAS(compare and swap)算法实现。
简单理解:这里的数据,别想太多,你尽管用,出问题了算我怂,即操作失败后事务回滚、提示。
1.1 版本号机制
1.1.1 实现套路:
取出记录时获取当前version
更新时带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对就更新失败
核心SQL
update table set name = 'Aron', version = version + 1 where id = #{id} and version = #{version};
1.1.2 实例-Mybatis-plus 乐观锁实现
原文查看请点击 Mybatis-plus 乐观锁实现
1.2 CAS算法
乐观锁的另一种技术技术当多个线程尝试使用CAS同时更新同一个变量时只有其中一个线程能更新变量的值而其它线程都失败失败的线程并不会被挂起而是被告知这次竞争中失败并可以再次尝试。
CAS 操作中包含三个操作数 :
需要读写的内存位置V
进行比较的预期原值A
拟写入的新值B
如果内存位置V的值与预期原值A相匹配那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况它都会在 CAS 指令之前返回该位置的值(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功而不提取当前值。CAS 有效地说明了“ 我认为位置 V 应该包含值 A如果包含该值则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。
1.2.1 实例-concurrent包的实现
由于java的CAS同时具有 volatile 读和volatile写的内存语义因此Java线程之间的通信现在有了下面四种方式
A线程写volatile变量随后B线程读这个volatile变量。
A线程写volatile变量随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令。同时volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起就形成了整个concurrent包得以实现的基石。
仔细分析concurrent包的源代码实现会发现一个通用化的实现模式
首先声明共享变量为volatile  
然后使用CAS的原子条件更新来实现线程之间的同步
同时配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程
1.2.2 缺点
ABA问题
比如说一个线程T1从内存位置V中取出A这时候另一个线程T2也从内存中取出A并且T2进行了一些操作变成了B然后T2又将V位置的数据变成A这时候线程T1进行CAS操作发现内存中仍然是A然后T1操作成功。尽管线程T1的CAS操作成功但可能存在潜藏的问题。
循环时间长开销大
自旋CAS不成功就一直循环执行直到成功如果长时间不成功会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升pause指令有两个作用第一它可以延迟流水线执行指令de-pipeline,使CPU不会消耗过多的执行资源延迟的时间取决于具体实现的版本在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突memory order violation而引起CPU流水线被清空CPU pipeline flush从而提高CPU的执行效率。
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时我们可以使用循环CAS的方式来保证原子操作但是对多个共享变量操作时循环CAS就无法保证操作的原子性这个时候就可以用锁或者有一个取巧的办法就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i 2,j = a合并一下ij = 2a然后用CAS来操作ij。从Java 1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性你可以把多个变量放在一个对象里来进行CAS操作。
2. 悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加(悲观)锁。一旦加锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。
悲观锁在MySQL、Java有广泛的使用
MySQL的读锁、写锁、行锁等
Java的synchronized关键字
3. 总结
读的多,冲突几率小,乐观锁。
写的多,冲突几率大,悲观锁。
Loading…
Cancel
Save