并发编程——CAS
创始人
2024-05-29 16:58:09
0

如果有兴趣了解更多相关内容的话,可以来我的个人网站看看:耶瞳空间

一:前言

首先看一个案例:我们开发一个网站,需要对访问量进行统计,用户每发送一次请求,访问量+1,如何实现?我们模拟有100个人同时访问,并且每个人对网站发起10次请求,最后总访问次数应该是1000次。

模拟代码如下:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;public class Demo {// 总访问量static int count = 0;// 模拟访问的方法public static void request() throws InterruptedException {// 模拟耗时5毫秒TimeUnit.MILLISECONDS.sleep(5);count++;}public static void main(String[] args) throws InterruptedException {// 开始时间long startTime = System.currentTimeMillis();int threadSize = 100;CountDownLatch countDownLatch = new CountDownLatch(threadSize);for (int i = 0; i < threadSize; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 模拟用户行为,每个用户访问10次网站try {for (int j = 0; j < 10; j++) {request();}} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}});thread.start();}// 利用countDownLatch实现100个线程结束之后再执行以下代码countDownLatch.await();long endTime = System.currentTimeMillis();System.out.println(Thread.currentThread().getName() + ", 耗时:" + (endTime - startTime) + ", count = " + count);}
}

在这里插入图片描述

很明显count不是1000,那么问题出在哪呢?

对于代码中的count++,其实是分这样几个步骤执行的:

  1. 获取count的值,记作A:A=count
  2. 将A值+1,得到B:B=A+1
  3. 将B值赋值给count

如果有A、B这两个线程同时执行count++,他们通知执行到上面步骤的第一步,得到的count是一样的,那么3步操作结束后,count只加了1,导致count结果不正确

那么怎么解决这个问题呢?可以在对count++操作的时候,我们让多个线程排队处理,多个线程同时到达request()方法的时候,只能允许一个线程进去操作,其他的线程在外面等待,等里面的处理完毕出来之后,外面等着的再进去一个,这样操作的count++就是排队进行的,结果一定是正确的

怎么实现排队效果呢?java中synchronized关键字和ReentrantLock都可以实现对资源加锁,保证并发正确性,多线程的情况下可以保证被锁住的资源被串行访问

在代码中就是在request()方法里加synchronized修饰,具体可见我的另一篇博客:并发编程——共享模型之管程

在这里插入图片描述
在这里插入图片描述
可见上锁之后,访问量就正常了,但也注意到,耗时从63ms变成了5335ms,那是因为在方法上加锁,导致TimeUnit.MILLISECONDS.sleep(5);也被串行执行了,因此仅仅等待就花费了5000ms,而结果执行错误只是因为count++被并发访问,因此我们可以只给count++加锁:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;public class Demo {// 总访问量volatile static int count = 0;/*** @param expectCount 期望值count* @param newCount 需要给count赋值的新值* @return boolean*/public static synchronized boolean compareAndSwap(int expectCount, int newCount) {// 判断count当前值是否和期望值expectCount一致,如果一致,则将newCount赋值给countif(getCount() == expectCount) {count = newCount;return true;}return false;}public static int getCount() {return count;}// 模拟访问的方法public static void request() throws InterruptedException {// 模拟耗时5毫秒TimeUnit.MILLISECONDS.sleep(5);int expectCount; // 表示期望值while(!compareAndSwap(expectCount = getCount(), expectCount + 1)) { }}public static void main(String[] args) throws InterruptedException {// 开始时间long startTime = System.currentTimeMillis();int threadSize = 100;CountDownLatch countDownLatch = new CountDownLatch(threadSize);for (int i = 0; i < threadSize; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 模拟用户行为,每个用户访问10次网站try {for (int j = 0; j < 10; j++) {request();}} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}});thread.start();}// 利用countDownLatch实现100个线程结束之后再执行以下代码countDownLatch.await();long endTime = System.currentTimeMillis();System.out.println(Thread.currentThread().getName() + ", 耗时:" + (endTime - startTime) + ", count = " + count);}
}

很明显这样程序的效率得到了很大的提升
在这里插入图片描述

二:CAS介绍

那现在回到正题,CAS全称“Compare And Swap”,即“比较并替换”。CAS操作包含三个操作数:

  • V:要更新的变量(var)
  • E:预期值(expected)
  • N:新值(new)

比较并交换的过程如下:判断V是否等于E,如果等于,将V的值设置为N;如果不等,说明已经有其它线程更新了V,则当前线程放弃更新,什么都不做。所以这里的预期值E本质上指的是“旧值”。

我们以一个简单的例子来解释这个过程:

  1. 如果有一个多线程共享的变量i原本等于5,我现在在线程A中,想把它设置为新的值6,我们使用CAS来做这个事情。
  2. 首先我们用i去与5对比,发现它等于5,说明没有被其它线程改过,那我就把它设置为新的值6,此次CAS成功,i的值被设置成了6;
  3. 如果不等于5,说明i被其它线程改过了(比如现在i的值为2),那么我就什么也不做,此次CAS失败,i的值仍为2。

在这个例子中, i就是V,5就是E,6就是N。

那有没有可能我在判断了i为5之后,正准备更新它的新值的时候,被其它线程更改了i的值呢?不会的。因为CAS是一种原子操作,它是一种系统原语,是一条CPU的原子指令,从CPU层面保证它的原子性。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,这个重试的过程也叫做自旋。当然也允许失败的线程放弃操作。正是基于这样的原理,CAS即时没有使用锁,也能发现其他线程对当前线程的干扰,从而进行及时的处理。

CAS的缺点:

  • CPU开销较大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
  • 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

三:基本使用

java提供了对CAS操作的支持,现在用CAS修改上面的例题:

public class Demo {// 总访问量static AtomicInteger count = new AtomicInteger(0);// 模拟访问的方法public static void request() throws InterruptedException {// 模拟耗时5毫秒TimeUnit.MILLISECONDS.sleep(5);// 修改访问量while (true) {int oldCount = count.get();int newCount = oldCount + 1;if (count.compareAndSet(oldCount, newCount)) {break;}}}public static void main(String[] args) throws InterruptedException {long startTime = System.currentTimeMillis();int threadSize = 100;CountDownLatch countDownLatch = new CountDownLatch(threadSize);for (int i = 0; i < threadSize; i++) {Thread thread = new Thread(() -> {// 模拟用户行为,每个用户访问10次网站try {for (int j = 0; j < 10; j++) {request();}} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}});thread.start();}// 利用countDownLatch实现100个线程结束之后再执行以下代码countDownLatch.await();long endTime = System.currentTimeMillis();System.out.println(Thread.currentThread().getName() + ", 耗时:" + (endTime - startTime) + ", count = " + count);}
}

不过需要注意的是,在获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。volatile可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,而是到主存中获取它的值,线程操作volatile变量都是直接操作主存。即一个线程对volatile变量的修改,对另一个线程可见。CAS必须借助volatile才能读取到共享变量的最新值来实现比较并交换的效果。关于volatile,详细介绍可以看我的这篇博客:并发编程——可见性与有序性

相关内容

热门资讯

安卓系统车机互联软件 你有没有发现,现在开车的时候,手机和车机之间的互动越来越频繁了呢?想象你正驾驶着爱车,一边享受着音乐...
荣耀play4安卓系统,智能生... 你有没有发现,最近手机圈里又掀起了一股热潮?没错,就是荣耀Play4这款新机!它不仅外观时尚,性能强...
安卓模拟苹果多开系统,打造跨平... 你有没有想过,在安卓手机上也能享受到苹果系统的多开功能呢?没错,这就是今天我要跟你分享的神奇话题——...
广汽埃安是安卓系统吗,搭载安卓... 你有没有想过,你的爱车广汽埃安,它的操作系统是不是安卓呢?这个问题,估计不少车友都在心里默默琢磨过。...
安卓系统是google的吗,谷... 你有没有想过,安卓系统是不是谷歌的亲生孩子呢?这个问题,就像是在问苹果是不是苹果树的宝宝一样,让人好...
安卓系统的设置程序下载,轻松掌... 你有没有发现,手机里的安卓系统设置程序真是五花八门,让人眼花缭乱?今天,就让我带你一起探索这个神秘的...
安卓系统的手机家园,打造个性化... 你有没有发现,现在手机的世界里,安卓系统的手机家园可是越来越热闹了呢!想象每天打开手机,就像走进了一...
安卓系统哪些相机可以调,支持实... 你有没有发现,手机拍照已经成为我们生活中不可或缺的一部分呢?无论是记录生活的点滴,还是捕捉美丽的瞬间...
安卓手机系统英文变中文,And... 你有没有发现,你的安卓手机上那些英文字母的系统提示,突然间变成了熟悉的中文呢?是不是觉得瞬间亲切感倍...
系统语言可以删除吗安卓,基于系... 你有没有想过,手机里的那些系统语言,是不是有时候觉得有点多余呢?比如,你是个地道的英语使用者,却总是...
安卓系统收取专利吗,揭秘科技巨... 你有没有想过,那个陪伴你每天刷手机、玩游戏、办公的安卓系统,它背后是不是也有自己的“小金库”呢?没错...
安卓屏幕怎么更新系统,轻松掌握... 亲爱的安卓手机用户们,你是不是也和我一样,时不时地想给手机来个“大变身”,让它焕然一新呢?没错,说的...
安卓系统有没有类似carpla... 你有没有想过,在安卓系统上,能不能也像苹果用户那样,轻松连接汽车音响系统呢?没错,我说的就是那个神奇...
安卓系统哪里升级包,生成与更新... 亲爱的安卓用户们,你是不是也和我一样,时不时地想给手机来个“大变身”,让它焕然一新呢?那就得提到那个...
安卓手机怎么恢复老系统,轻松恢... 手机用久了,是不是觉得系统越来越卡,功能也越来越不跟得上潮流了呢?别急,今天就来教你怎么把安卓手机恢...
安卓系统如何安装windows... 亲爱的安卓用户们,你是否曾幻想过在手机上体验Windows 7的韵味?别再羡慕那些拥有Windows...
旧的安卓系统怎么备份,轻松保存... 亲爱的安卓用户们,你是否曾经因为系统升级或者手机意外重启而担心丢失了珍贵的照片、联系人或者应用数据呢...
安卓手机系统文件被破坏,全面解... 手机突然间罢工了,是不是很崩溃?别急,今天就来聊聊安卓手机系统文件被破坏的那些事儿。相信我,掌握了这...
街头霸王四安卓系统,畅享格斗盛... 你知道吗?最近在安卓系统上,有一款游戏可是火得一塌糊涂,那就是《街头霸王四》!这款经典格斗游戏在安卓...
阿里tv安卓系统刷机,畅享智能... 你有没有发现,家里的阿里TV用久了,系统有点卡卡的呢?别急,今天就来教你怎么给它来个焕然一新的刷机大...