Java并发编程之CAS第三篇-CAS的缺点

通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理。那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论

本篇是《凯哥(凯哥Java:kagejava)并发编程学习》系列之《CAS系列》教程的第三篇:CAS的缺点有哪些?怎么解决。

CAS的缺点

一:do while循环时间长的话开销大

从源码中(见上图),我们可以知道do while中的while返回true会一直循环下去(具体分析步骤见上一篇:《Java并发编程之CAS二源码追根溯源》。凯哥(凯哥Java:kaigejava)就不在这里赘述了)。如果并发量很多的话,比如:有十万个线程来并发处理,这这种业务下,很多线程都会修改共享变量,要保证原子性的话,循环会很长时间,假设每个线程为了保证原子性,循环耗时0.001s的话,那么十万个线程都这么循环下来,对CPU的消耗还是比较大的。

二:只能保证一个共享变量的原子性

从源码中,我们知道 Object var1其实就是对象自己。拿上一篇文章举的例子来说,其实就是atomicInteger自己,也就是共享变量。CAS的do while只能一个this一个this的比较。从这里就可以看出,CAS只能保证一个共享变量的原子性。但是如果用同步锁的话,锁是可以锁对象也可以锁代码块。锁操作的可以不是一个共享变量。

三:会出现新的问题:ABA问题

何为ABA问题呢?

先来看看现实生活的例子:

学校举行运动会,标准操场一圈400米,现在正在进行1200米比赛。1200=400*3.需要跑上三圈。小明和小红比赛,在刚开始的时候,大家都看到小明,小红都在起点,但是小红速度比小明快2/3。这个时候,小明爸爸拿着相机拍摄,在起点时候,拍摄小明,3分钟过后,我们再来看起点,是小红。7分钟之后,在看起点是小明。难道小红就跑了一圈吗?这当然不对。小红比小明快,当我们第二次看到小明的时候,小红其实三圈已经跑完了。最终出现的情况就是:小明(小红)小红小明(小红)小明。最终获胜的当然是小红

这个例子或者不是很恰当。但是凯哥是想通过这个例子告诉大家,当线程如果出现这种情况的话,会影响到数据结果的。

如下图:

说明:

A线程执行一次耗时:1分钟

B线程执行一次耗时“29.5s

B线程在A线程执行一次的时间内操作主内存的数据变化为:202020192020

当B线程执行2次操作之后,1分钟到了。A线程拿着自己工作区copay的副本值i=2020和主内存的值i=2020。正好相等,这个时候会,主内存的共享变量相对于A线程来说,是没有变化的。但是实际上是有变化的(B线程确实操作过的。如上面举例的,小红已经跑完三圈了。可是小明才跑第二圈呢),如果这个时候在操作,有可能导致数据出问题(赛跑最终结果是小红赢了,而不是小明赢了)。

所谓的ABA就是:在某个监控点的时候数据是A,当过了时间N之后,在监控的时候还是A。但是在时间N的这段时间内,监控点的数据有可能不是A了,变成过B。这样就更容易理解了吧。

ABA问题演示代码:

代码说明:

初始的时候,给了变量值为2020.也就是V=2020.如上图1

在经过线程A一顿猛如虎的操作之后,搞出来2020,2021,2020.ABA的效果处理。如上图2.

Sleep了1秒是为了让线程A完成ABA操作的。

然后,线程2在拿着自己副本的变量值A=2020,和主内存V进行比较。发现一致,就更新了2019.

运行结果如下图:

从运行结果来看,线程2也更新成功了。但是,这样是不对的。因为我们已经知道线程A对共享变量操作过了。那么针对CAS的这些缺点,应该怎么解决呢?欢迎继续学习下一篇。凯哥将介绍三个怎么解决。以及会讲解原子引用、时间戳原子引用两个问题。

CAS缺点解决办法

一:循环长,开销大解决方案

解决思路:ConcurrentHashMap(后面凯哥也会详细介绍的)类似的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来。

二:一个共享变量的解决方案

因为CAS只能一个共享变量一个共享变量的处理。如果想要处理类是代码块或者对象的。可以使用同步锁或者是多个变量放到一个对象里面。然后在CAS。因为在JUC包下,有支持对象的原子类,如:AtomicReference(原子引用类)。

原子引用

在Java中变量的类型分为八大基本类型或八大基本类型的对象类型或者是自定义的对象类型。在并发中,atomicInteger就是基本类型就是int/Integer的原子类。那么自定义的对象怎么实现原子性呢?这就要用到原子引用对象- AtomicReference。

原子引用demo:

我们来模拟凯哥心中女神变化过程(注:女神同时只能存在一个,不能存在多个,要保持单一,原子的)。

在X年之前是刘亦菲,X+N年后是林依晨,现在是佟丽娅了。我们知道,这三个女神都是对象。都有年龄、用户名,是个对象。

创建user对象

她们三个在凯哥心中活动如下:

那么请问在21和23行输入的结果是什么?

编辑

我们发现在23行依然输出的是林依晨。而不是佟丽娅。为什么呢?分析思路见:《Java并发编程之CAS一理解》篇文章的三:cas代码演示部分。

我们修改之后再来看:

运行结果:

发现心中女神已经更新为佟丽娅了

三:ABA问题解决

ABA问题产生的根本原因是因为:只是线程自己工作空间的变量预期值(副本)和主内存中的值进行了比较。当值相等的时候,就默认没有被其他线程更新过。那么怎么解决这个问题呢?

是不是可以添加一个东西,用来辅助呢?添加一个标记,或者一个版本号,根据版本号+数值来进行判断呢?当然可以了,JDK中也是这么实现的。JDK使用的是时间戳(stamp),而不是我们说的版本号(version)。我们来看看时间戳原子引用(AtomicStampedReference<V>).

我们来看看这个类。

时间戳原子引用demo

先看构造器:

参数说明:

initialRef:初始值

initialStamp:初始值的时间戳

再来看看CompareAndSet方法:

参数说明:

expectedReference:预期值

newReference:更新值

expectedStamp:预期时间戳值

newStamp:更改后时间戳值

我们发现这个AtomicStampedReference类和AtomicReference的方法中的区别就是时间戳原子引用类中的方法都添加了预期的时间戳值和修改后的时间戳的值这两个参数。

我们来看看,使用带有时间戳的原子引用类解决ABA问题的代码:

1:声明共享变量

static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(127,1);

(需要说明,如果用数值做demo的话,主要int的取值范围。如果大于127,就会始终返回false。因为 Integer(128) == Integer(128)返回的是false)

线程一先修改执行一个ABA的过程:

编辑

执行完成之后,当前的主内存中版本号应该是3了。

我们在用线程2来执行compareAndSet:

此时,在线程2中的版本号:tamp应该是1,但是主内存中的版本号已经是3了。所以执行后返回false.执行不成功的。

我们来看看运行结果和我们预期结果:

运行结果,和我们预期结果是一致的。说明,添加这个时间戳(版本号)可以解决ABA问题

Java并发编程之CAS第三篇-CAS的缺点及解决办法的更多相关文章

  1. Java并发编程之CAS第一篇-什么是CAS

    Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...

  2. Java并发编程之CAS二源码追根溯源

    Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...

  3. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

  4. Java并发编程之set集合的线程安全类你知道吗

    Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...

  5. Java并发编程之AQS

    一.什么是AQS AQS(AbstractQueuedSynchronize:队列同步器)是用来构建锁或者其他同步组件的基础框架,很多同步类都是在它的基础上实现的,比如常用的ReentrantLock ...

  6. Java并发编程之synchronized关键字

    整理一下synchronized关键字相关的知识点. 在多线程并发编程中synchronized扮演着相当重要的角色,synchronized关键字是用来控制线程同步的,可以保证在同一个时刻,只有一个 ...

  7. Java 并发编程之 Condition 接口

    本文部分摘自<Java 并发编程的艺术> 概述 任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait().wait(long timeout).notify() 以及 not ...

  8. Java并发编程之Lock

    重入锁ReentrantLock 可以代替synchronized, 但synchronized更灵活. 但是, 必须必须必须要手动释放锁. try { lock.lock(); } finally ...

  9. Java并发编程之volatile关键字解析

    一内存模型的相关概念 二并发编程中的三个概念 三Java内存模型 四深入剖析volatile关键字 五使用volatile关键字的场景 volatile这个关键字可能很多朋友都听说过,或许也都用过.在 ...

随机推荐

  1. 维生素D补充过多会中毒

    虽然我们的物质生活越来越丰富,各种食材几乎一年四季都能够吃到,然而却越来越多的人选择进行补充各种维生素,但是你知道吗?维生素不是我们想象中多吃无害的,补充过多也会要人命,特别是最近非常流行补充的一种维 ...

  2. jQuery2.0.0版本以后不再支持ie8的原因

    在引用jQuery时,引用高版本的Jq会在IE8下报错,在网上查了一下,jq在2.0+的版本就已经放弃对ie8的支持了.之前没有仔细研究过jq版本,借此机会去看了一下jq版本的知识.一.如何查看jq的 ...

  3. CentOS7安装Ceph

    CentOS 7 下安装Ceph-nautilus 本问主要记录在CentOS 7下如何安装Ceph-nautilus,安装过程中遇到的一些问题及解决方法. 实验准备 以下是本次实验所用到的机器(采用 ...

  4. 添砖加瓦:[OpenCV]入门(一)

    1.OpenCV安装 (1)下载: 本文采用的是源码的方式进行安装,源码可以从OpenCV官网下载.这里以3.4.1为例. (2)安装 这里下载到的文件为3.4.1.zip."unzip 3 ...

  5. 记录R的一些黑魔法

    通路富集结果可视化 12345678 pathway<-read.table("PTC+_transcript_pep_supp_KEGG.txt",header=T,sep ...

  6. 利用python代码操作git

    python操作git 安装模块 pip3 install gitpython 基本使用 import os from git.repo import Repo # 创建本地路径用来存放远程仓库下载的 ...

  7. Pandorabox固件路由器上申请Let's Encrypt证书,为内网里的多个web服务提供SSL支持

    对于家中宽带有公网IP的用户,有时我们需要将路由器内部网络的某些web服务通过端口转发暴露到外网(例如NAS远程访问),但HTTP是明文传输,有被监听的风险:如果在NAS上使用自签名证书,再端口转发, ...

  8. 前阿里数据库专家总结的MySQL里的各种锁(上篇)

    0.前言 MySQL按照加锁的范围,分为全局锁.表级锁.行级锁. 本文作为上篇,主要介绍MySQL的全局锁 和 表级锁. 重要的实战总结为,如何安全地变更一个表的表结构. 1.全局锁 定义: 全局锁就 ...

  9. Java enum枚举在实际项目中的常用方法

    在项目实际开发过程中,经常会遇到对某些固定的值.字典项的定义的需求,很多项目经常使用常量来定义,其实在jdk1.5就已经引入了枚举,使用枚举可以更好的解决这类需求,本文主要记录枚举的优势以及经常在项目 ...

  10. p标签内不能嵌套块级标签

    今天突然发现一个问题,那就是p标签内不能嵌套块级标签 例如: <p><p></p></p> 会被浏览器解析成 我又把 div 嵌套在里面,发现还是这样 ...