【Java】手把手模拟CAS,瞬间理解CAS的机制
- 话不多少,先看个案例,【模拟100个用户,每个用户访问10次网站】”:
public class ThreadDemo1 {
//总访问量
private static int count = ;
//模拟访问的方法
public static void request() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep();//模拟耗时5s
count++;
}
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
int threadSize = ;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = ; i < ; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//模拟用户行为,每个用户访问10次网站
try {
for (int j = ; j < ; j++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
});
thread.start();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println("访问用时:" + (endTime - startTime) + "ms");
System.out.println(count);
}
}
#结果1:
访问用时:73ms
#结果2:
访问用时:78ms
总之,结果基本都不会达到1000
- 分析一下问题出在哪呢?
【count++】 操作实际上是由三步来完成的(jvm执行引擎)
》获取count的值,记做A: A = count 》将A值+,得到B:B=A+ 》将B值赋给count

- 怎么解决结果不正确的问题呢?
对count++操作的时候,我们让多个线程排队处理,多个线程同时到达处理【request()方法】时,只能允许有一个线程进去操作,其他线程只能在外面等待(即串行化), 等到里面的线程处理完毕后,再让外面等待的线程进去一个,这样操作结果一定是争取的。
- 通常如何实现排队呢?
》synchronized关键字加锁
(详情可以了解《Synchronized底层加锁原理详解》:https://www.cnblogs.com/boluopabo/p/12907916.html)
》ReentrantLock可重入锁
- 那我们试一下【synchronized】加锁后的执行结果:
//我们试着在request方法前加一个synchronized 修饰
//模拟访问的方法
public synchronized static void request() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep();//模拟耗时5s
count++;
}
#结果:
访问用时:5884ms
虽然解决了线程的不安全问题,但是用却多了几十倍。
- 耗时太长的原因是什么呢?
程序中的request方法使用synchronized关键字修饰,保证了并发情况下,request方法同一时刻,只允许一个线程进入, request相当于串行执行了,count结果与预期一致,但耗时太长了
- 如何解决耗时太长问题呢?
文章最初我们简述了【count】的变化过程,这里我们再重复一遍,并延伸一下:
》获取count的值,记做A: A = count 》将A值+,得到B:B=A+ 》将B值赋给count 升级第三步的实现:
a.获取锁
b.获取以下count最新的值,记做LV
c.判断LV是否等于A,如果相等,则把B的值赋值给count,并返回true;否则返回false
d.释放锁
- 我们这里模拟一下上述升级第三步的实现场景:
package com.example.demo.thread; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; /**
* @author Code Farmer
* @date 2020/5/22 15:10
*/
public class ThreadDemo3 { //总访问量
//此处加volatile修改,保证可见性
private volatile static int count = ; //模拟访问的方法
public static void request() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep();//模拟耗时5s
// count++;
int expectCount;
//a.获取锁
//b.获取以下count最新的值,记做LV
while (!compareAndSwap((expectCount = getCount()), expectCount + )) { }
} /**
* @param expectCount count期望值
* @param newCount 需要给count赋予的新值
* @return 交换成功返回true;反之返回false
*/
public static synchronized boolean compareAndSwap(int expectCount, int newCount) {
// c.判断LV是否等于A,如果相等,则把B的值赋值给count,并返回true;否则返回false
// d.释放锁
if (expectCount == getCount()) {
count = newCount;
return true;
}
return false;
} private static int getCount() {
return count;
} public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
int threadSize = ;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = ; i < ; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//模拟用户行为,每个用户访问10次网站
try {
for (int j = ; j < ; j++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
});
thread.start();
} countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println("访问用时:" + (endTime - startTime) + "ms");
System.out.println(count);
} }
#结果:
访问用时:78ms
返回结果可以看到,性能可谓是爆炸式提升啊。
- 小结:在模拟CAS机制中,我们只在【将B值赋给count】这里加锁,从而减小了锁的粒度,以提高性能。
【Java】手把手模拟CAS,瞬间理解CAS的机制的更多相关文章
- 【Java】手把手理解CAS实现原理
先来看看概念,[CAS] 全称“CompareAndSwap”,中文翻译即“比较并替换”. 定义:CAS操作包含三个操作数 —— 内存位置(V),期望值(A),和新值(B). 如果内存位置的值与期望值 ...
- (白话理解)CAS机制
(白话理解)CAS机制 通过一段对话我们来了解cas用意 示例程序:启动两个线程,每个线程中让静态变量count循环累加100次. 最终输出的count结果是什么呢?一定会是200吗? 加了同步锁之后 ...
- Java 中的各种锁和 CAS + 面试题
Java 中的各种锁和 CAS + 面试题 如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一.因此下面 ...
- 无锁同步-JAVA之Volatile、Atomic和CAS
1.概要 本文是无锁同步系列文章的第二篇,主要探讨JAVA中的原子操作,以及如何进行无锁同步. 关于JAVA中的原子操作,我们很容易想到的是Volatile变量.java.util.concurren ...
- Java内存管理的进一步理解-模拟过程图解
Java内存管理的进一步理解-模拟过程图解--转载 java的内存管理分为: 1.堆内存:2.栈内存:3.方法区:4.本地方法区 /* 1:方法区 方法区存放装载的类数据信息包括: ...
- Java并发编程:什么是CAS?这回总算知道了
无锁的思想 众所周知,Java中对并发控制的最常见方法就是锁,锁能保证同一时刻只能有一个线程访问临界区的资源,从而实现线程安全.然而,锁虽然有效,但采用的是一种悲观的策略.它假设每一次对临界区资源的访 ...
- .NET:通过 CAS 来理解数据库乐观并发控制,顺便给出无锁的 RingBuffer。
背景 大多数企业开发人员都理解数据库乐观并发控制,不过很少有人听说过 CAS(我去年才听说这个概念),CAS 是多线程乐观并发控制策略的一种,一些无锁的支持并发的数据结构都会使用到 CAS,本文对比 ...
- 理解cas
前言 CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,Doug lea大神在java同步器中大量使用了CAS技术,鬼斧神工的实现了多线程执行的安全性. CAS ...
- 源码阅读 - java.util.concurrent (二)CAS
背景 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. ...
随机推荐
- 【Linux常见命令】dos2unix命令,unix2dos命令
我们都知道.打回车键就是换行的意思. 在不同系统下打回车键效果是不同的: MAC OS下:dakdhih \r LINUX下:dakdhih \n DOS\WINDOWS下:dakdhih \r\n ...
- JavaScript Array every()&some()&reduce()方法
every()方法测试数组的所有元素是否都通过了指定函数的测试. // 每一项都要满足条件才会返回true,只要有一项不满足返回false var arr = [1, 2, 3, 4]; let bl ...
- 墨仓式进入2.0时代?爱普生商用墨仓式L4158试用
提起"墨仓式"打印机,相信现在已经没有人需要过多的解释,墨仓式打印机在打印市场占有率不断提高就是最佳佐证.为什么用户对于墨仓式这么认可,想必是墨仓式真正洞悉了他们的需求,解决了打印 ...
- CodeForces-1058B B. Vasya and Cornfield
这题,我真的不知道题解是啥,自己看代码吧. #include<iostream> using namespace std; int main() { int n, d,m,i,x,y; c ...
- 转载acm几何基础(2)
判断两条线段是否相交: 矢量 如果一条线段的端点是有次序之分的话,那么这种线段就称为 有向线段,如果有向线段p1p2的起点p1在坐标的原点,则可以把它称为矢量p2 矢量的加减 设二维矢量 P = (x ...
- JAVA基础篇 之 finalize()方法的作用
我们知道java有垃圾回收器负责回收无用对象占据的内存资源,但也有特殊情况:假设你的对象(并非使用new)获得了一块特殊的内存区域,由于垃圾回收器只知道回收那些经由new分配的内存,所以它不知道如 ...
- 201771030117-祁甜 实验一 软件工程准备—<阅读《现代软件工程——构建之法》提出的三个问题>
项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/nwnu2020SE 这个作业要求链接 https://www.cnblogs.com/nwnu- ...
- Coursera课程笔记----Write Professional Emails in English----Week 3
Introduction and Announcement Emails (Week 3) Overview of Introduction & Announcement Emails Bas ...
- 架构设计 | 分布式系统调度,Zookeeper集群化管理
本文源码:GitHub·点这里 || GitEE·点这里 一.框架简介 1.基础简介 Zookeeper基于观察者模式设计的组件,主要应用于分布式系统架构中的,统一命名服务.统一配置管理.统一集群管理 ...
- 【Hadoop离线基础总结】HDFS入门介绍
HDFS入门介绍 概述 HDFS全称为Hadoop Distribute File System,也就是Hadoop分布式文件系统,是Hadoop的核心组件之一. 分布式文件系统是横跨在多台计算机上的 ...