CAS无锁机制必须了解的JVM内存模型

本文通过一个并发编程的例子,探讨了在多线程环境下,为何会出现i++操作结果小于预期的问题。这涉及到JVM内存模型的规则,包括变量存储在主内存和工作内存中,以及线程间如何通过主内存交换信息。为解决这个问题,介绍了CAS(Compare and Swap)无锁机制的工作原理,以及其如何避免线程冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前分享过一篇包教包会Cas无锁机制-手写无锁机制Lock锁文章如果看完之后,立马在看这篇文章绝对是趁热打铁送福利

思考一个问题,10个线程对一个变量进行i++赋值1000 结果是什么?

下边是测试的代码

package com.hd.jxd;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class DemoLock {
   int i=0;
    public void incr(){
             i++;
    }
 
    public static void main(String[] args) {
        DemoLock demoLock=new DemoLock();
        for (int i = 0; i <10 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        demoLock.incr();
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(1000);//休息一下等待线程全部执行完在打印不然会执行完一半就没有意思了
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(demoLock.i);
    }
}
代码给你们写好了直接复制粘贴测试一下打印结果为8641是一个小于一万的数(每次测试不一定是这个结果,但是一定小于一万)
为什么结果小于一万这里就涉及到我们的JVM内存模型

一,请看下图

 

二,然后在看下边这段话

JVM描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这些底层细节并遵守以下规则

规则1:所有的变量都存储在主内存中(即共享内存)

规则2:每个线程都有自己独立的工作内存(即本地内存)

规则3:线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写(比如主内存i变量现在是i=9,你t1要把9这个信息拷贝来,然后进行赋值操作不能直接修改主内存,修改好了之后通过“刷新”操作改变主内存的值)

规则4:不同线程之间无法直接访问其他线程工作内存中的变量,线程之间变量的值传递需要通过主内存来完成(t1不能直接访问t2的值必须经过主内存才可以)

一,二都看完了,然后我们分析一下,为什么会小于一万

主内存好比一个大屏幕显示着对i进行i++的操作,1,2,3,4,5,6.....

假如这时候主内存(大屏幕)显示i=8了,于是t1从主内存拷贝一下8然后赋值到自己的本地内存中,t1理所当然的操作自己的本地内存进行i++。可是这个时候别忘了还有t2,因为t2刚才也在大屏幕看到的是(i=8)然后我也在我本地内存操作i++,并且已经操作完了,不但操作完我还把主内存刷新成i=9了。这个时候t1根本不知道发生了什么(因为规则4原因)于是t1又把主内存刷新了一便还是i=9。这样理解了吧,10个线程每个线程进行1000次,总有几率出现那么几次,这样就造成了为什么数值会小于一万。

这个问题怎么解决呢,咱先不不管别人我们自己想想假如遇到这个问题咋办

第一,当大屏幕显示的时候为了避免出错,那么咱这样,当某一个线程拷贝主内存之后其他谁都别操作,就在那里等着,看着。咱一个个来,就不乱了,这种办法就是加锁synchronized,虽说也可以不过效率保证不了,肯定慢对不对。

第二,你想啊,这样行不行,哥哥我t1从主内存中拷贝数据到本地内存中,对不对,我可不可比较一下,就是我本地内存和主内存在比较一下,我拿到8,我比较一下我t1本地内存和主内存都是8,ok可以没问题,我对其进行修改,如果主内存已经是9了  8!=9那么没办法我只能在拿一次(始终以主内存为准)把本地内存再改成9,然后再比较直到和主内存一致才可以,(底层源码用的是do while循环始终比较)。

请看下图

我们平时用的方法只是compareAndSet传2个参数一个expect就是本地内存值,你拷贝主内存之后的值(官方叫期望值一个熊样叫什么无所谓知道它干什么就行),和要进行修改的值update(i++之后的值)。你可能要问了不是还有一个主内存值吗,对这个valueOffset就是主内存的值。OK三个值齐活。

Java是跑虚拟机的,刚才说的拷贝主内存这事Java可干不了,所以有一个类unsafe它可以拿到主内存数据。

unsafe.compareAndSwapObject这个方法底层是用C++写的我们稍微看下

看到这里的do whlie循环了吗,看到这里就可以了,别再深入了,知道这个意思就行了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值