Java中CAS 基本实现原理「建议收藏」

Java中CAS 基本实现原理「建议收藏」一、前言了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称。它有核心就是CAS与AQS。CAS是java.util.concurrent.atomic包的基础,如AtomicInteger、AtomicBoolean、AtomicLong等等类都是基于CAS。什么是CAS呢?全称CompareAndSwap,比较并交换。CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什

大家好,又见面了,我是你们的朋友全栈君。

一、前言

了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称。它有核心就是CAS与AQS。CAS是java.util.concurrent.atomic包的基础,如AtomicInteger、AtomicBoolean、AtomicLong等等类都是基于CAS。

什么是CAS呢?全称Compare And Swap,比较并交换。CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。

Java中CAS 基本实现原理「建议收藏」

二、实例

如果我们需要对一个数进行加法操作,应该怎样去实现呢?我们模拟多个线程情况下进行操作。

ThreadDemo.java 实现一个Runnable接口

package com.spring.security.test;

public class ThreadDemo implements Runnable {
	
	private int count = 0;

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			addCount();
		}
	}
	
	private void addCount() {
		count++;
	}
	
	public int getCount() {
		return count;
	}
}

ThreadTest.java 创建线程池,提交10个线程执行,预期结果应该是1000

package com.spring.security.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadTest {
	public static void main(String[] args) {
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		ThreadDemo threadDemo = new ThreadDemo();
		for (int i = 0; i < 10; i++) {
			threadPool.submit(threadDemo);
		}
        threadPool.shutdown();
		System.out.println(threadDemo.getCount());
	}
}

 运行结果:874 或其他,与预期结果不符合。

 执行出来的结果并不是想象中的结果。这是为什么呢?这跟线程的执行过程有关。

Java中CAS 基本实现原理「建议收藏」

所以我们需要在改变count,将值从高速缓冲区刷新到主内存后,让其他线程重新读取主内存中的值到自己的工作内存。

此时可以用volatile关键字。它的作用是保证对象在内存中的可见性。

修改ThreadDemo中的count字段

private volatile int count = 0;

此时执行结果:900 或其他,与预期结果不符合。

此时还是并未得出正确执行结果。为什么?听我细细道来。

线程安全主要体现在三个方面:

  • 原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
  • 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

目前可见性已经实现了,缺少原子性的操作,因为同一时刻,多个线程对其操作,会将改动后的最新值读取到自己的工作内存进行操作,最终只能得到后一个执行线程操作的结果,所以相当于少了一步操作,就会造成数据的不一致。

此时可以使用JUC的Atomic包下面的类来进行操作。

Java中CAS 基本实现原理「建议收藏」

Atomic类是使用CAS+volatile来实现原子性与可见性的

我们来改造一下TheadDemo.java中的实现方法

package com.spring.security.test;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo implements Runnable {
	
	private AtomicInteger count = new AtomicInteger(0);

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			// 递增
			count.getAndIncrement();
		}
	}
	
	
	public int getCount() {
		return count.get();
	}
}

 执行结果: 1000,符合预期值。

Java中CAS 基本实现原理「建议收藏」

 接下来我们来分析一下AtomicInteger类的源码:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

Unsafe类是不安全的类,它提供了一些底层的方法,我们是不能使用这个类的。AtomicInteger的值保存在value中,而valueOffset是value在内存中的偏移量,利用静态代码块使其类一加载的时候就赋值。value值使用volatile,保证其可见性。

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
	int var5;
	do {
		var5 = this.getIntVolatile(var1, var2);
	} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

	return var5;
}

var1表示当前对象,var2表示value在内存中的偏移量,var4为增加的值。var5为调用底层方法获取value的值

compareAndSwapInt方法通过var1和var2获取当前内存中的value值,并与var5进行比对,如果一致,就将var5+var4的值赋给value,并返回true,否则返回false

由do while语句可知,如果这次没有设置进去值,就重复执行此过程。这一过程称为自旋。

compareAndSwapInt是JNI(Java Native Interface)提供的方法,可以是其他语言写的。

三、与synchronized比较

使用synchronized进行加法:

package com.spring.security.test;


public class ThreadDemo implements Runnable {
	
	private int count = 0;

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			// 递增
			synchronized (ThreadDemo.class) {
				count++;
			}
		}
	}
	
	
	public int getCount() {
		return count;
	}
}

运行结果: 1000,符合预期值。

Java中CAS 基本实现原理「建议收藏」

使用synchronized和AtomicInteger都能得到预期结果,但是他们之间各有什么劣势呢?

synchronized是重量级锁,是悲观锁,就是无论你线程之间发不发生竞争关系,它都认为会发生竞争,从而每次执行都会加锁。

在并发量大的情况下,如果锁的时间较长,那将会严重影响系统性能。

CAS操作中我们可以看到getAndAddInt方法的自旋操作,如果长时间自旋,那么肯定会对系统造成压力。而且如果value值从A->B->A,那么CAS就会认为这个值没有被操作过,这个称为CAS操作的”ABA”问题。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/156910.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月8日 下午5:16
下一篇 2022年7月8日 下午5:36


相关推荐

  • vue.js打包后,接口安全问题

    vue.js打包后,接口安全问题

    2021年10月13日
    87
  • 无线视频传输技术「建议收藏」

    无线视频传输技术「建议收藏」原文地址:http://www.ed-china.com/ART_8800047847_400002_500018_HN_151edec4.HTMWiDi/Miracast将在无线高清视频传输市场占最大份额据IHSiSuppli公司的显示电子特别报告,在消费与移动产品高清视频无线传输市场,三种基于Wi-Fi的视频技术将胜过两种应用范围较窄的专有技术。今年能传输高清视频

    2026年4月14日
    6
  • 什么是粘包?_网络粘包

    什么是粘包?_网络粘包TCP/IP协议簇建立了互联网中通信协议的概念模型,该协议簇中的两个主要协议就是TCP和IP协议。TCP/IP协议簇中的TCP协议能够保证数据段(Segment)的可靠性和顺序,有了可靠的传输层协议之后,应用层协议就可以直接使用TCP协议传输数据,不在需要关心数据段的丢失和重复问题。图1-TCP协议与应用层协议IP协议解决了数据包(Packet)的路由和传输,上层的TCP协议不再关注路由和寻址,那么TCP协议解决的是传输的可靠性和顺序问题,上层不需要.

    2022年8月11日
    11
  • Pycharm中Debug的基本用法和高级技巧

    Pycharm中Debug的基本用法和高级技巧今天测试自己写的代码,测试了很多次都是实际结果与心里预测不相符,甚至一度怀疑Pycharm除了问题,哈哈。最后debug了一下,才发现是自己的操作问题才导致了错误的结果,看来Debug真的是个好侦探,让你不会乱怀疑。下面就和大家分享一下我在Pycharm上Debug的心得1.在Pycharm中打开一个.py文件,并设置断点鼠标左键单击箭头处需要设置断点的语句即可设置断点2.运行debug…

    2022年8月26日
    7
  • python字符串与数字类型转换

    python字符串与数字类型转换str 与 inti 10s str i s 10 s 1 i int s i 1str 与 floatst 4 t float st t 0 4st 0 4 t float st t 0 4t 0 4st str t st 0 4 注意把字符串转换为数字类型时

    2026年3月18日
    1
  • 【云原生专题】基于Docker+Neo4j图数据库搭建企业级分布式应用拓扑图

    【云原生专题】基于Docker+Neo4j图数据库搭建企业级分布式应用拓扑图图数据库是 NoSQL 领域中的一种 在处理相关联的大数据方面比关系型数据库天然具有优势 近年来在知识图谱 金融风控 社交关系等场景中发挥了重要的角色功能 同时 图数据库在 AI 领域 天然适合诸如记忆提取 关联推理 归纳探索等场景 成为了人工智能领域不可缺少的部分 Neo4J 是比较通用和常见的图数据库 具有社区版和企业版之分 普通学习使用免费的社区版即可 Neo4J 还提供了一个 Web 访问的可视化执行与查询的界面 类似 ElasticSearc 一样 非常容易上手

    2026年3月16日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号