并发编程——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,详细介绍可以看我的这篇博客:并发编程——可见性与有序性

相关内容

热门资讯

苹果怎么倒进安卓系统,一键倒装... 你有没有想过,把苹果手机里的宝贝倒腾到安卓系统里去?听起来是不是有点像变魔术?别急,今天就来手把手教...
安卓系统都能双系统么吗,揭秘双... 你有没有想过,你的安卓手机是不是也能来个“双胞胎”呢?没错,就是那种一个手机里同时运行两个操作系统,...
长安汽车升级安卓系统,安卓系统... 你知道吗?最近长安汽车可是来了一次大变身呢!没错,就是那个我们熟悉的国产汽车品牌,这次他们竟然升级了...
mac电脑装安卓系统,轻松实现... 亲爱的电脑迷们,你是否曾幻想过在你的Mac电脑上运行安卓系统?想象那些你钟爱的安卓应用,在你的Mac...
安卓p系统流畅吗,畅享无忧 你有没有发现,最近安卓P系统成了大家热议的话题呢?不少朋友都在问,这个新系统到底流畅不流畅啊?今天,...
剑灵2安卓系统,畅游东方奇幻世... 你知道吗?最近在安卓系统上,有一款游戏可是火得一塌糊涂,那就是《剑灵2》!这款游戏不仅画面精美,操作...
安卓系统是否指定品牌,品牌定制... 你有没有想过,为什么你的安卓手机总是那么独特,而别人的安卓手机却看起来差不多呢?这背后,其实隐藏着一...
安卓系统和iso系统 照片共享... 你有没有发现,现在手机拍照功能越来越强大,拍出来的照片美得不要不要的!但是,当你想和朋友们分享这些精...
安卓系统领夹麦,便携式音频解决... 你有没有发现,现在手机通话越来越方便了,但是有时候,手机那点小小的麦克风,真的有点力不从心呢!尤其是...
安卓系统经常无法唤醒,探究系统... 你是不是也遇到过这种情况?手机屏幕黑了,手指在屏幕上疯狂地滑动,却怎么也唤醒不了安卓系统。这可真是让...
自己编译安卓系统源码,编译安卓... 你有没有想过,安卓系统其实就像一个巨大的宝藏,里面藏着无数的秘密和可能性?今天,就让我带你一起探索这...
鸿蒙系统属于安卓,基于安卓的全... 你知道吗?最近有个话题在科技圈里可是闹得沸沸扬扬的,那就是鸿蒙系统。没错,就是那个华为自主研发的操作...
lephone是安卓系统吗,深... 你有没有听说过lephone这个品牌呢?最近,身边的朋友都在讨论这个手机,说它性价比超高,但是,有人...
pc双系统安卓系统下载软件,P... 你有没有想过,在电脑上同时运行安卓系统,是不是就像在手机上玩电脑游戏一样酷炫呢?没错,这就是今天我要...
电量壁纸安卓系统设置,安卓系统... 手机电量告急,是不是又得赶紧找充电宝了?别急,今天就来和你聊聊如何通过设置电量壁纸来给安卓系统来个“...
ip是安卓系统吗,通过IP地址... 你有没有想过,那个陪伴你每天刷剧、玩游戏、办公的IP,它是不是安卓系统呢?别急,今天就来揭开这个谜底...
安卓系统谁负责升级,揭秘幕后负... 你有没有想过,你的安卓手机为什么有时候会突然收到系统更新的通知呢?是不是好奇,是谁在背后默默地为你的...
安卓系统需要降级吗,安卓系统升... 你有没有发现,你的安卓手机最近有点儿“老态龙钟”了呢?运行速度慢吞吞的,有时候还卡个不停。这时候,你...
性价比手机安卓系统,盘点安卓系... 你有没有想过,在这个手机更新换代如此迅速的时代,如何用最少的钱,买到最满意的手机呢?没错,我要说的是...
虚拟大师安卓2.0系统,安卓新... 你有没有听说最近虚拟大师安卓2.0系统火得一塌糊涂?这可不是空穴来风,而是真的让不少手机用户都跃跃欲...