JVM的线程安全

2023-10-15  本文已影响0人  自天佑之吉无不利

在编程过程中,你有没有遇到过在运行多线程程序时想用共享变量来通讯,却发现程序结果和预期不一致的情况?这个常见的问题就是多线程编程的难点,涉及内存可见性和重排等问题。

可见性问题

第一个问题出现在处理器和内存之间。现代电脑系统中,处理器速度很快,内存的读写速度却远远不能比肩。为了解决这种矛盾,引入了高速缓存,处理器将计算所需数据复制到缓存,从而加快运算速度。然而,这也引发了缓存的一致性问题,也就是说,在多处理器系统里,如果每个处理器都同时使用相同的内存,那么它们的缓存数据可能会出现差异。

处理器必须按照一些协议进行同步操作,在读写时维持缓存一致性。这里就涉及到内存可见性问题,也就是一个线程修改的状态能够即时在其他线程中可见。

重排问题

我们日常写的代码,在实际运行之前,都会经过 JVM 及底层 CPU 等各种优化,来达到最优的执行性能。但是这种优化都是基于单线程考虑的优化,如果你的程序是以多线程的方式运行,反而很容易引发各种问题。所以对于多线程的程序,知道 JVM 实际进行了哪些优化非常重要。

指令序列从源代码到实际执行的过程可能会经历以下三种重新排序。

编译器优化的重排

JVM 的编译器有可能在保持单线程原有含义的同时,对代码的运作流程进行修正,从而提升运行效率。

指令并行的重排

指令并行重排属于 CPU 优化的范畴,对于不存在数据前后依赖的情况,CPU 可能会调整指令的执行,将多条指令重叠执行。

内存系统的重排

因为处理器利用了缓存以及读写缓冲区,所以负载和保存的任务有可能会混乱地进行。这种重新排序可能会使内存与缓存数据同步时间产生偏差。

JMM
Java 内存模型是为了解决这些问题而设计的线程安全的解决方案。它是 JVM 提供的一种抽象概念,描述了多线程之间如何协同工作以及如何访问和改变共享数据。它定义了 Java 程序和虚拟机之间的互动规则,确保程序在多线程环境下具有一致的内存可见性和操作顺序。

JMM 也提供了一些实现这个规则的解决方案。

可见性就是指在一个线程中,共享变量的修改在另一个线程中不能被看到。这主要是由于处理器和内存的速度不同导致的,处理器会把计算所需的数据复制到缓存里,从而产生了缓存一致性问题。为了解决这个问题,线程必须按照一些协议进行同步操作,保证修改的状态能够即时在其他线程中可见。

重排问题是指在翻译器和数据处理器执行代码的时候,对命令顺序执行的再次排列。这种优化是为了提高程序的执行性能,但是在多线程环境下可能导致程序的执行结果不符合预期。为了解决重排问题,Java 内存模型规定了一些规则,如读取和载入操作必须按顺序执行,存储和写入操作也必须按顺序执行等。

Java 提供了一些策略来应对可见性和重排的问题。其中,Synchronized 关键字能确保在同一时间段内只有一个线程能够执行临界区域的代码,从而防止多线程共享资源的情况发生。Volatile 关键字可以保证变量的可见性和有序性。Lock 接口和原子变量也可以提供线程安全的解决方案。此外 final 关键字可以保证被修饰的变量的值只能被赋值一,不可修改。所以说,熟练掌握 JMM 是写出线程安全的 Java 程序的基础。

此文章为10月Day10学习笔记,内容来源于极客时间《云时代JVM实战 》,强烈推荐该课程

上一篇 下一篇

猜你喜欢

热点阅读