悲观者与乐观者的做事方式完全不一样,悲观者的人生观是一件事情我必须要百分之百完全控制才会去做,否则就认为这件事情一定会出问题;而乐观者的人生观则相反,凡事不管最终结果如何,他都会先尝试去做,大不了最后不成功。这就是悲观锁与乐观锁的区别,悲观锁会把整个对象加锁占为自有后才去做操作,乐观锁不获取锁直接做操作,然后通过一定检测手段决定是否更新数据。这一节将对乐观锁进行深入探讨。

上节讨论的Synchronized互斥锁属于悲观锁,它有一个明显的缺点,它不管数据存不存在竞争都加锁,随着并发量增加,且如果锁的时间比较长,其性能开销将会变得很大。有没有办法解决这个问题?答案是基于冲突检测的乐观锁。这种模式下,已经没有所谓的锁概念了,每条线程都直接先去执行操作,计算完成后检测是否与其他线程存在共享数据竞争,如果没有则让此操作成功,如果存在共享数据竞争则可能不断地重新执行操作和检测,直到成功为止,可叫CAS自旋。

乐观锁的核心算法是CAS(Compareand Swap,比较并交换),它涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。如图2-5-4-1,有两个线程可能会差不多同时对某内存操作,线程二先读取某内存值作为预期值,执行到某处时线程二决定将新值设置到内存块中,如果线程一在此期间修改了内存块,则通过CAS即可以检测出来,假如检测没问题则线程二将新值赋予内存块。

 

假如你足够细心你可能会发现一个疑问,比较和交换,从字面上就有两个操作了,更别说实际CAS可能会有更多的执行指令,他们是原子性的吗?如果非原子性又怎么保证CAS操作期间出现并发带来的问题?我是不是需要用上节提到的互斥锁来保证他的原子性操作?CAS肯定是具有原子性的,不然就谈不上在并发中使用了,但这个原子性是由CPU硬件指令实现保证的,即使用JNI调用native方法调用由C++编写的硬件级别指令,jdk中提供了Unsafe类执行这些操作。另外,你可能想着CAS是通过互斥锁来实现原子性的,这样确实能实现,但用这种方式来保证原子性显示毫无意义。下面一个伪代码加深对CAS的理解:

public class AtomicInt {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
Unsafe类提供的硬件级别的compareAndSwapInt方法;
}
}

其中最重要的方法是getAndIncrement方法,它里面实现了基于CAS的自旋。

现在已经了解乐观锁及CAS相关机制,乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但它也有缺点:

① 观锁只能保证一个共享变量的原子操作。如上例子,自旋过程中只能保证value变量的原子性,这时如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。

② 长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。

ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一

乐观锁是对悲观锁的改进,虽然它也有缺点,但它确实已经成为提高并发性能的主要手段,而且jdk中的并发包也大量使用基于CAS的乐观锁。

乐观锁&CAS问题的更多相关文章

  1. 并发系列2:Java并发的基石,volatile关键字、synchronized关键字、乐观锁CAS操作

    由并发大师Doug Lea操刀的并发包Concurrent是并发编程的重要包,而并发包的基石又是volatile关键字.synchronized关键字.乐观锁CAS操作这些基础.因此了解他们的原理对我 ...

  2. JUC原子操作类与乐观锁CAS

    JUC原子操作类与乐观锁CAS ​ 硬件中存在并发操作的原语,从而在硬件层面提升效率.在intel的CPU中,使用cmpxchg指令.在Java发展初期,java语言是不能够利用硬件提供的这些便利来提 ...

  3. 乐观锁--CAS

    悲观锁与乐观锁的区别 悲观锁会把整个对象加锁占为已有后才去做操作,Java中的Synchronized属于悲观锁.悲观锁有一个明显的缺点就是:它不管数据存不存在竞争都加锁,随着并发量增加,且如果锁的时 ...

  4. java 乐观锁CAS

    乐观锁是一种思想,本身代码里并没有lock或synchronized关键字进行修饰.而是采用一种version. 即先从数据库中查询一条记录得到version值,在更新这条记录时在where条件中对这 ...

  5. 【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?

    前言 乐观锁和悲观锁问题,是出现频率比较高的面试题.本文将由浅入深,逐步介绍它们的基本概念.实现方式(含实例).适用场景,以及可能遇到的面试官追问,希望能够帮助你打动面试官. 目录 一.基本概念 二. ...

  6. [数据库事务与锁]详解八:底理解数据库事务乐观锁的一种实现方式——CAS

    注明: 本文转载自http://www.hollischuang.com/archives/1537 在深入理解乐观锁与悲观锁一文中我们介绍过锁.本文在这篇文章的基础上,深入分析一下乐观锁的实现机制, ...

  7. AtomicInteger源码分析——基于CAS的乐观锁实现

    AtomicInteger源码分析——基于CAS的乐观锁实现 1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时 ...

  8. AtomicInteger源码分析——基于CAS的乐观锁实

    1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换.切换涉及 ...

  9. 【Java】CAS的乐观锁实现之AtomicInteger源码分析

    1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换.切换涉及 ...

随机推荐

  1. Java实现 蓝桥杯VIP 算法提高 夺宝奇兵

    算法提高 夺宝奇兵 时间限制:1.0s 内存限制:512.0MB [题目描述] 在一座山上,有很多很多珠宝,它们散落在山底通往山顶的每条道路上,不同道路上的珠宝的数目也各不相同.下图为一张藏宝地图: ...

  2. Java实现 洛谷 P1008 三连击

    public class Main { public static void main(String[] args){ for(int i = 123; i <= 329; i++){ int[ ...

  3. 【Python源码剖析】对象模型概述

    Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...

  4. 循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数

    在我们使用VUE+Element 处理界面的时候,往往碰到需要利用JS集合处理的各种方法,如Filter.Map.reduce等方法,也可以设计到一些对象属性赋值等常规的处理或者递归的处理方法,以前对 ...

  5. 彻底搞懂 etcd 系列文章(二):etcd 的多种安装姿势

    0 专辑概述 etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管.etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件 ...

  6. 掌握SpringBoot-2.3的容器探针:深入篇

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:原创分类汇总及配套源码,涉及Java.Docker.K8S.DevOPS等 关于<Spr ...

  7. 关于adb命令的基本使用

    在我们使用adb命令之前,我们要安装一个安卓模拟器(夜神.逍遥.海马王......),这里以夜神安卓模拟器为准(个人推荐,没用过可以使用夜神模拟器). 进入夜神安卓模拟器官网:https://www. ...

  8. Java 多线程基础(三) start() 和 run()

    Java 多线程基础(三) start() 和 run() 通过之前的学习可以看到,创建多线程过程中,最常用的便是 Thread 类中的 start() 方法和线程类的 run() 方法.两个方法都包 ...

  9. 传递函数-微分方程-差分方程-Matlab阶跃响应曲线

    Transfer function: 1 ------- 5 s + 1 写成微分方程: 5y'(t)+y(t)=u(t) 向前差分: 5y(k+1)+(T-5)y(k)=Tu(k) T:Sample ...

  10. CentOS6.5 开机启动自动运行redis服务

    [一].查找和设置自己的redis路径参数 环境变量 PATH=/usr/local/bin:/sbin/:/usr/bin:/bin 端口 REDISPORT=6379 文件位置 EXEC=/usr ...