并发编程之CAS(二)
更多Android架构进阶视频学习请点击:https://space.bilibili.com/474380680
本篇文章将从以下几个内容来阐述CAS:
- [CAS原理]
- [CAS带来的ABA问题]
一、什么是CAS?
在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成(摘自维基本科)
JAVA1.5开始引入了CAS,主要代码都放在JUC的atomic包下,如下图:
二、CAS(Compare And Swap)导致的ABA问题
问题描述
多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次。当并发的时候,其中一个线程已经将A成功的改成了B,但是在线程并发调度过程中尚未被调度,在这个期间,另外一个线程(不在并发中的请求线程)将B又修改成了A,那么原来并发中的线程又可以通过CAS操作将A改成B
测试用例:
public class AbaPro {
private static final Random RANDOM = new Random();
private static final String B = "B";
private static final String A = "A";
public static final AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<>(A);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i=0; i < 20; i++){
threads[i] = new Thread(){
@Override
public void run() {
String oldValue = ATOMIC_REFERENCE.get();
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(RANDOM.nextInt()&500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ATOMIC_REFERENCE.compareAndSet(oldValue, B )){
System.out.println(Thread.currentThread().getName()+ " 已经对原始值进行了修改,此时值为: "+ ATOMIC_REFERENCE.get());
}
}
};
threads[i].start();
}
startLatch.countDown();
Thread.sleep(200);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt() & 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String oldVal = ATOMIC_REFERENCE.get();
while (!ATOMIC_REFERENCE.compareAndSet(ATOMIC_REFERENCE.get(), A));
System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
}
}.start();
}
}
结果:
Thread-12 已经对原始值进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
Thread-14 已经对原始值进行了修改,此时值为: B
可以看到并发中的线程Thread-12已经成功的将A修改成B,其他线程Thread-20在某一时刻将B修改成A,而并发中的线程Thread-14又能再次成功的将A修改成B,虽然最终结果是B,但是中途经历了一次被修改的过程,在某些情况下是致使的
解决方案
java中提供了AtomicStampedReference来解决这个问题,它是基于版本或者是一种状态,在修改的过程中不仅对比值,也同时会对比版本号
public class AabProResolve {
private static final Random RANDOM = new Random();
private static final String B = "B";
private static final String A = "A";
private static final AtomicStampedReference<String> ATOMIC_STAMPED_REFERENCE = new AtomicStampedReference<>(A,0);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i=0; i < 20; i++){
threads[i] = new Thread(){
@Override
public void run() {
String oldValue = ATOMIC_STAMPED_REFERENCE.getReference();
int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(RANDOM.nextInt() & 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ATOMIC_STAMPED_REFERENCE.compareAndSet(oldValue, B, stamp, stamp+1)){
System.out.println(Thread.currentThread().getName()+ " 已经对原始值: "+oldValue+" 进行了修改,此时值为: "+ ATOMIC_STAMPED_REFERENCE.getReference());
}
}
};
threads[i].start();
}
Thread.sleep(200);
startLatch.countDown();
new Thread(){
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt() & 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
String oldVal = ATOMIC_STAMPED_REFERENCE.getReference();
while (!ATOMIC_STAMPED_REFERENCE.compareAndSet(
B,
A,stamp, stamp+1)){
stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
}
System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
}
}.start();
}
}
结果:
Thread-1 已经对原始值: A 进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
可以看到并发期间的线程只有Thread-1对A进行了修改,保证了只有一个线程对数据的修改,短暂的并发时间之后的其他线程Thread-20对其修改自然也就没有影响
更多Android架构进阶视频学习请点击:[https://space.bilibili.com/474380680]
参考:https://blog.csdn.net/u013887008/article/details/79784298
https://www.cnblogs.com/javalyy/p/8882172.html
并发编程之CAS(二)的更多相关文章
- Java并发编程之CAS二源码追根溯源
Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...
- Java并发编程之CAS第三篇-CAS的缺点及解决办法
Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...
- Java并发编程之CAS第一篇-什么是CAS
Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
- 并发编程之 CAS 的原理
前言 在并发编程中,锁是消耗性能的操作,同一时间只能有一个线程进入同步块修改变量的值,比如下面的代码 synchronized void function(int b){ a = a + b: } 如 ...
- python并发编程之multiprocessing进程(二)
python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之J.U.C的第二篇
并发编程之J.U.C的第二篇 3.2 StampedLock 4. Semaphore Semaphore原理 5. CountdownLatch 6. CyclicBarrier 7.线程安全集合类 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
随机推荐
- 图片模式CMYK和RGB在浏览器下的变化
手机浏览器里面不能显示出模式为CMYK的图片, 模式为CMYK的图片在IE浏览器里面显示和模式为RGB的一样 模式为CMYK的图片在火狐里面显示有色差
- Git与GitHub同步
如何通过Git Bash实现本地与远端仓库——GitHub的同步 1.下载安装Git:下载网址 2.在自己的github上新建一个repository 例如我这里新建了一个叫test的reposito ...
- vb写文件时报'Invalid procedure call or argument'
原来的一段代码是这样的: Set fso3 = CreateObject("Scripting.FileSystemObject") 'msgbo ...
- python 自带http服务
python2: python -m SimpleHTTPServer python3: python3 -m http.server
- [轉]关于CR0.WP
关于CR0.WP 我们知道CR0的WP位可以关闭内核写保护.他和页表的R/W位有关.Intel手册中的描述绕来绕去似乎一直没有说到重点. When the processor is in superv ...
- 【目录】循序渐进学.Net Core Web Api开发系列
当前标签: 循序渐进学.Net Core Web Api开发系列 循序渐进学.Net Core Web Api开发系列[16]:应用安全续-加密与解密 NET未来之路 2019-03-13 15: ...
- 统计List中相同的元素
public static <E> List<E> getCommonsElements(List<E> list) { return list.stream() ...
- 关于KiCAD
关于KiCAD 使用背景 一直以来公司都在用AD作为EDA软件,但是最近公司频繁收到律师函,所以决定找一款开源的软件来代替AD,目前市场上开源的只找到了KiCAD和Easy EDA(中文版立创EDA) ...
- JSON.parse 解析json字符串时,遇字符串换行符,解析失败
今天遇到json字符串转对象时报错了,发现有个字符串有换行符,仔细找了原因. 结果是因为JSON.parse转json字符串时遇到一些特殊字符需要先转义,如图所示 然后尝试了各路大神介绍的办法,均不适 ...
- redis数据操作篇
服务器端 服务器端的命令为redis-server 可以使⽤help查看帮助⽂档 redis-server --help 个人习惯 ps aux | grep redis 查看redis服务器进程su ...