7_数据库并发策略

并发控制

并发控制一般采用三种方法,分别是
乐观锁
悲观锁
时间戳

--------------------------------------------------------------------------------------------------------

乐观锁

乐观锁认为一个用户读数据的时候,别人不会去写自己所 读的数据;悲观锁就刚好相反,觉得自 己读数据库的时候, 别人可能刚好在写自己刚读的数据, 其实就是持一种比较 保守的态度;时间 戳就是不加锁,通过时间戳来控制并发 出现的问题。

悲观锁

悲观锁就是在读取数据的时候,为了不让别人修改自己读 取的数据,就会先对自己读取的数据加 锁,只有自己把数据读完 了,才允许别人修改那部分数据,或者反过来说,就是自己修改 某条数 据的时候,不允许别人读取该数据,只有等自己的 整个事务提交了,才释放自己加上的锁,才允 许其他用户 访问那部分数据。

时间戳

时间戳就是在数据库表中单独加一列时间戳, 比 如“TimeStamp”,每次读出来的时候,把该字段也读出 来,当写回去的时候,把该字段加 1,提交之前 , 跟数据库 的该字段比较一次,如果比数 据库的值大的话,就允许保 存,否则不允许保存,这种处理方法虽然不使用数据库系 统提供的锁机制,但是这种方法可以大大提高数据库处理 的并发量, 以上悲观锁所说的加“锁”,其实分为几种锁,分别是: 排它锁(写锁)和共享锁(读锁)。

--------------------------------------------------------------------------------------------------------

乐观锁/悲观锁/两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可 认为一种好于另一种,像乐观锁适用于写比较少的情况下(多 读场景),即冲突真的很少发生的时候,这样可以省去了锁的开 销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经 常产生冲突,这就会导致上层应用会不断的进行 retry,这 样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合 适。

--------------------------------------------------------------------------------------------------------

乐观锁常见的两种实现式版本号机制

一般是在数据表中加上一个数据版本号 version 字段,表示数 据被修改的次数,当数据被修改时,version 值会加一。当线程A 要 更新数据值时,在读取数据的同时也会读取 version 值,在提交更
新时,若刚才读取到的 version 值为当前数据库中的 version 值相等 时才更新,否则重试更新操作,直到更新成功。CAS 算法即 compare andswap(比较与交换),是一种有名的无锁算 法。

无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就 是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同 步(Non-blocking Synchronization)。CAS 算法涉及到三 个操作数需要读写的内存值V进行比较的值A拟写入的新值 B

当且仅当 V 的值等于 A 时,CAS 通过原子方式用 新值B 来更新V 的值, 否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断 的重试。

--------------------------------------------------------------------------------------------------------

乐观锁的缺点

ABA 问 题
如果一个变量 V初次读取的时候是 A值,并且在准 备赋值的时候检查到它仍然是 A 值,那我们就能说明它的 值没有被其他线程修改过了吗?很明显是不能的,因为在 这段时间它的值可能被改为其他值,然后又改回 A,那 CAS操作就会误认为它从来没有被修改过。这个问题被 称为 CAS 操作的 "ABA"问题。

JDK 1.5 以后的 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 类把多个共享 变量合并成一个共享变量来操作

--------------------------------------------------------------------------------------------------------

CAS 与 synchronized 的使用情景

简单的来说 CAS 适用于写比较少的情况下(多读场 景, 冲突一般较少),synchronized 适用于写比较多的 情况下(多写场景,冲突一般较多)

对于资源竞争较少(线程冲突较轻)的情况,使用 synchronized 同步锁进行线程阻塞和唤醒切换以及用户 态内核态间的切换操作额外浪费消耗cpu 资源; 而 CAS 基于硬件实现,不需要进入内核,不需要切换线程,操作 自旋几率较少, 因此可以获得更高的性能。

对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大, 从而浪费更多的 CPU 资源,效率 低于 synchronized。

补充: Java 并发编程这个领域中 synchronized 关 键字一直都是元老级的 角色,很久之前很多人都会称它为 “重量级锁” 。 但是,在JavaSE 1.6 之后进行了主要包括为了减少获得 锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重 了

synchronized 的底层实现主要依靠 Lock-Free 的队 列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁, 稍微牺牲了公平性, 但获得了高吞吐量。在线程冲突较 少的情况下,可 以获得和 CAS 类似的性能;而线程冲突严重的情况下, 性能远高于 CAS。

上一篇:乐观锁&悲观锁&AQS


下一篇:Java多线程(五) 乐观锁和CAS机制