Java内存模型,volatile关键字和CAS算法,

2023-11-09

引言:

在前几篇博文中我详细介绍了HashMap的底层实现原理,后来我接连写了三天JVM和GC的一些知识,那些知识偏向于理论。今天换点口味,和大家一起研究学习一下ConcurrentHashMap的底层实现,因为jdk1.8在HashMap和concurrentHashMap和以往都发生了变化。知识一直都是连续的,如果我觉得光说ConcurrentHashMap的源码,很多小伙伴会云里雾里,所以为了让所有人,甚至是新手都能通透理解,限于篇幅和时间,我打算把ConcurrentHashMap分为三部分来写,第一篇作为基础,第二篇为认识,第三篇为熟知。今天第一篇我主要介绍一下Java内存模型,volatile关键字和CAS算法,如果理解这三点,对后面的源码理解会有极大的帮助。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290

技术点:

1、悲观锁与乐观锁: 
悲观锁是指如果一个线程占用了一个锁,而导致其他所有需要这个锁的线程进入等待,一直到该锁被释放,换句话说就是这个锁被独占,比如说典型的就是synchronized;乐观锁是指操作并不加锁,而是抱着尝试的态度去执行某项操作,如果操作失败或者操作冲突,那么就进入重试,一直到执行成功为止。

2、原子性,指令有序性和线程可见性: 
这三个性质在多线程编程中是核心的问题。原子性和事务的原子性一样,对于一个操作或者多个操作,要么都执行,要么都不执行。指令有序性是指,在我们编写的代码中,上下两个互不关联的语句不会被指令重排序。指令重排序是指处理器为了性能优化,在无关联的代码的执行是可能会和代码顺序不一致。比如说int i = 1;int j = 2;那么这两条语句的执行顺序可能会先执行int j = 2;线程可见性是指一个线程修改了某个变量,其他线程能马上知道。

3、无锁算法(nonblocking algorithms): 
使用低层原子化的机器指令, 保证并发情况下数据的完整性。典型的如CAS算法。

4、内存屏障: 
在《深入理解JVM》中解释是:它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;它会强制将对缓存的修改操作立即写入主存;如果是写操作,它会导致其他CPU中对应的缓存行无效。在使用volatile修饰的变量会产生内存屏障(后面会详细解释)。

Java内存模型

下面是我从百度上引入的一张具有代表性的图: 
这里写图片描述

①解释:我根据这张图来解释java内存模型,从图中可以看出每个线程都需要从主内存中读取操作,这个就是java内存模型的规定之一,所有的变量存储在主内存中,每个线程都需要从主内存中获得变量的值。

然后从图中可以看到每个线程获得数据之后会放入自己的工作内存,这个就是java内存模型的规定之二,保证每个线程操作的都是从主内存拷贝的副本,也就是说线程不能直接写主内存的变量,需要把主内存的变量值读取之后放入自己的工作内存中的变量副本中,然后操作这个副本。

最后线程与线程之间无法直接访问对方工作内存中的变量。最后需要解释一下这个访问规则局限于对象实例字段,静态字段等,局部变量不包括在内,因为局部变量不存在竞争问题。

②基本执行步骤: 
a、lock(锁定):在某一个线程在读取主内存的时候需要把变量锁定。 
b、unlock(解锁):某一个线程读取玩变量值之后会释放锁定,别的线程就可以进入操作 
c、read(读取):从主内存中读取变量的值并放入工作内存中 
d、load(加载):从read操作得到的值放入工作内存变量副本中 
e、use(使用):把工作内存中的一个变量值传递给执行引擎 
f、assign(赋值):它把一个从执行引擎接收到的值赋值给工作内存的变量 
g、store(存储):把工作内存中的一个变量的值传送到主内存中 
h、write(写入):把store操作从工作内存中一个变量的值传送到主内存的变量中。

这里我再引入一张别的地方被我搜来的图供大家一起理解:

这里写图片描述

volatile关键字

在基本清除了java内存模型之后,我们开始详细说明一下volatile关键字,在concurrentHashMap之中,有很多的成员变量都是用volatile修饰的。被volatile修饰的变量有如下特性:

①使得变量更新变得具有可见性,只要被volatile修饰的变量的赋值一旦变化就会通知到其他线程,如果其他线程的工作内存中存在这个同一个变量拷贝副本,那么其他线程会放弃这个副本中变量的值,重新去主内存中获取

②产生了内存屏障,防止指令进行了重排序,关于这点的解释,请看下面一段代码:

package com.brickworkers;

public class VolatileTest {

    int a = 0;                 //1
    int b = 1;                 //2
    volatile int c = 2;        //3
    int d = 3;                 //4
    int e = 4;                 //5

}

在如上的代码中,因为c变量是用volatile进行修饰,那么就会对该段代码产生一个内存屏障,用以保证在执行语句3的时候语句1和语句2是绝对执行完毕的,而且在执行语句3的时候,语句4和语句5肯定没有执行。同时说明一下,在上述代码中虽然保证了语句3的执行顺序不可变换,但是语句1和语句2,语句4和语句5可能发生指令重排序哦。

总结:volatile修饰的变量具有可见性与有序性。

下面,我们用一段代码来进一步解释volatile和原子性的概念:

package com.brickworkers;

public class VolatileTest {

//  int a = 0;                 //1
//  int b = 1;                 //2
    public static volatile int c = 0;        //3
//  int d = 3;                 //4
//  int e = 4;                 //5


    public static void increase(){
        c++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                public void run() {
                    increase();
                }
            }
            ).start();
        }
        Thread.sleep(5000);
        System.out.println(c);
    }
}

//运行3次结果分别是:997,995,989

这个就是典型的volatile操作与原子性的概念。执行结果是小于等于1000的,为什么会这样呢?不是说volatile修饰的变量是具有原子性的么?是的,volatile修饰的变量的确具有原子性,也就是c是具有原子性的(直接赋值是原子性的),但是c++不具有原子性,c++其实就是c = c +1,已经存在了多步操作。所以c具有原子性,但是c++这个操作不具有原子性。

根据前面介绍的java内存模型,当有一个线程去读取主内存的过程中获取c的值,并拷贝一份放入自己的工作内存中,在对c进行+1操作的时候线程阻塞了(各种阻塞情况),那么这个时候有别的线程进入读取c的值,因为有一个线程阻塞就导致该线程无法体现出可见性,导致别的线程的工作内存不会失效,那么它还是从主内存中读取c的值,也会正常的+1操作。如此便导致了结果是小于等于1000的。

注意,这里笔者也有个没有深刻理解的问题,首先在java内存模型中规定了:在对主内存的unlock操作之前必须要执行write操作,那意思就是c在写回之前别的线程是无法读取c的。然而结果却并非如此。如果哪位朋友能理解其中的原委,请与我联系,大家一起讨论研究。

CAS算法

CAS的全称叫“Compare And Swap”,也就是比较与交换,他的主要操作思想是: 
首先它具有三个操作数,a、内存位置V,预期值A和新值B。如果在执行过程中,发现内存中的值V与预期值A相匹配,那么他会将V更新为新值A。如果预期值A和内存中的值V不相匹配,那么处理器就不会执行任何操作。CAS算法就是我再技术点中说的“无锁定算法”,因为线程不必再等待锁定,只要执行CAS操作就可以,会在预期中完成。

在ConcurrentHashMap中,很多的操作都会依靠CAS算法完成,具体的在后面两篇博文再深入体会。对于ConcurrentHashMap的基础就先写到这里,中间还有一些笔者也没有参悟,希望大家来一起研究。

 

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012403290/article/details/67636469

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java内存模型,volatile关键字和CAS算法, 的相关文章

  • 热点 JVM 字节码解释器是跟踪 JIT 吗?

    这个问题几乎说明了一切 我一直在寻找答案 甚至通过 VM 规范 但我没有明确说明 No 不过 还有一些其他 JVM 具有跟踪 JIT HotPath http HotPath GoogleCode Com and Maxine http L
  • 为什么 JVM 同时具有“invokespecial”和“invokestatic”操作码?

    两条指令都使用静态而不是动态调度 似乎唯一的实质性区别是invokespecial始终将一个对象作为其第一个参数 该对象是分派方法所属类的实例 然而 invokespecial实际上并没有把物体放在那里 编译器负责通过在发出之前发出适当的堆
  • 如何查看JVM中JIT编译的代码?

    有什么方法可以查看 JVM 中 JIT 生成的本机代码吗 一般用法 正如其他答案所解释的 您可以使用以下 JVM 选项运行 XX UnlockDiagnosticVMOptions XX PrintAssembly 根据特定方法进行过滤 您
  • STS 无法在我的计算机上启动

    我试图在 eclipse 上设置 Spring mvc 项目 基本项目进展顺利 但是使用 Restful 服务 Jersey 等开始出现许多与依赖项相关的错误 所以我打算转到STS 我正在使用 STS 2 9 2 它给我 无法创建java虚
  • 在intellij中为java启用ssl调试

    从我的问题开始 上一期尝试通过 tls ssl 发送 java 邮件 https stackoverflow com questions 39259578 javamail gmail issue ready to start tls th
  • Intellij Idea 使用什么 JVM 来启动?

    我是 Eclipse 用户 最近决定尝试 Intellij Idea 我的操作系统是 Ubuntu 12 使用 Eclipse 时 可以通过在 eclipse ini 中指定来轻松选择用于启动 Eclipse 的 JVM http wiki
  • 通过cas进行ajax调用

    我需要编写一个谷歌小工具来读取谷歌群组的提要 问题是我正在进行 ajax 调用来检索提要 而我们的 google apps 域受 CAS 中央身份验证服务 保护 因此 我在拨打电话时收到 400 错误请求 我怀疑浏览器在进行 ajax 调用
  • 可以混合使用 JVM 语言吗?即:Groovy 和 Clojure

    我知道你可以轻松地混合groovy java clojure java 无论什么JvmLang java 这是否也意味着我也可以让 clojure 和 groovy 代码进行交互 如果我使用 Grails 或 jRoR 我也可以在该环境中使
  • jvm 如何以及何时何地更改 Linux 的最大打开文件值?

    在linux中 每个登录用户的每个进程的最大打开文件数有限制 如下所示 ulimit n 1024 当我学习java nio时 我想检查这个值 因为channel在Linux中也是一个文件 所以我编写了一个客户端代码来不断创建socketC
  • Bipush 在 JVM 中如何工作?

    我知道 iload 接受整数 1 到 5 但是如何使用 bipush 指令扩展到更高的数字 特定整数如何与字节码一起存储 有几种不同的指令可用于推送整数常量 最小的是iconst 指令 这些只是一个字节 因为该值是在操作码本身中编码的 ic
  • 使用 javac 和 javax.tools.JavaCompiler 有什么区别?

    Maven 编译器插件文档states http maven apache org plugins maven compiler plugin 编译器插件用于编译项目的源代码 从 3 0 开始 默认编译器是 javax tools Java
  • Java 语言中不可用的字节码功能

    当前 Java 6 是否有一些事情可以在 Java 字节码中完成而在 Java 语言中无法完成 我知道两者都是图灵完备的 所以将 可以做 理解为 可以做得更快 更好 或者只是以不同的方式 我正在考虑额外的字节码 例如invokedynami
  • 测量 tomcat 的排队请求数

    因此 使用tomcat 您可以设置acceptCount值 默认为100 这意味着当所有工作线程都忙时 新连接被放置在队列中 直到队列满 之后它们被拒绝 我想要的是监视此队列中项目的大小 但无法确定是否有办法通过 JMX 获取此值 即不是队
  • 调整 Java 类以提高 CPU 缓存友好性

    在设计java类时 对于实现CPU缓存友好性有哪些建议 到目前为止我学到的是应该尽可能多地使用 POD 即 int 而不是整数 这样 在分配包含对象时 数据将被连续分配 例如 class Local private int data0 pr
  • 使用 Coldfusion 11 的 CFdirectory,文件名中存在非 ASCII 字符问题

    我有一个类似的问题 ColdFusion CFDirectory 和法语 https stackoverflow com questions 1715632 coldfusion cfdirectory and the french从而没有
  • 将 Kotlin .kt 类打包到 JAR 中

    我如何构建HelloWorld kt as a JAR以便它运行 thufir dur kotlin thufir dur kotlin kotlinc HelloWorld kt include runtime d HelloWorld
  • JVM锯齿状空闲进程

    我目前正在进行一项涉及 JVM 及其内存使用工作原理的研究 我不明白的是 JVM在空闲时用什么填充它的内存 只是为了在堆几乎达到时释放它 为什么使用的内存不只有一条平线 顺便说一句 这个 java 应用程序托管在 glassfish 上 但
  • 如何制作.Net或JVM语言?

    我看到了 NET 和 JVM 的所有这些新语言 一个人如何开始制作一个 我找不到关于 JVM 或 MSIL 规范的任何好的文档 Edit 我已经知道如何解析 我更感兴趣的是如何有这么多人基于这些平台创建新语言 你有点幸运 为 NET 开发的
  • Java:为什么.class文件中的方法类型包含返回类型,而不仅仅是签名?

    class 文件的常量池中有一个 NameAndType 结构 它用于动态绑定 该类可以 导出 的所有方法都被描述为 签名 返回类型 喜欢 getVector Ljava util Vector 当某些 jar 中方法的返回类型发生更改时
  • Dart/Flutter 如何编译到 Android?

    我找不到任何具体的资源 Dart 是否被编译到 JVM 或者 Google 的团队是否编译了 Dart VM 以在 JVM 上运行 然后在 JVM 内的 Dart VM 中运行 Dart 前者更有意义 并且符合 无桥 的口号 但后者似乎更符

随机推荐