Java最高

使用Spring 5和Spring Boot 2开始,通过学习的春天课程:

>>查看课程

1.概述

如果没有必要的同步,编译器、运行时或处理器可能会应用各种优化。尽管这些优化在大多数情况下都是有益的,但有时它们也会导致一些微妙的问题。

缓存和重新排序是在并发上下文中可能让我们感到惊讶的优化中。Java和JVM提供了许多方法来控制记忆订单,而且易挥发的关键字是其中之一。

在本文中,我们将专注于这个基础,而是误解了Java语言的误解概念 - 该易挥发的关键词。首先,我们将从底层计算机体系结构如何工作的一系列背景,然后我们将熟悉Java中的内存顺序金宝搏官网188be。

2.共享多处理器架构

处理器负责执行程序指令。因此,他们需要从RAM检索两个程序指令和所需数据。

由于cpu每秒可以执行相当数量的指令,因此从RAM中获取数据对它们来说并不理想。为了改善这种情况,处理器使用了诸如秩序执行分支预测投机执行当然,还有缓存。

这是以下内存层次结构的位置:

随着不同的内核执行更多的指令和操作更多的数据,它们用更多相关的数据和指令填充缓存。这将以引入为代价提高整体性能缓存一致性挑战

简而言之,我们应该三思而后行地思考一个线程更新缓存值时发生的事情。金宝搏官网188be

3.何时使用易挥发的

为了更多地扩展缓存的一致性,让我们从书中借一个例子实践中的Java并发

public class TaskRunner {private static int number;private static boolean ready;private static class Reader extends Thread {@Override public void run() {while (!ready) {Thread.yield();} System.out.println(数量);}} public static void main(String[] args) {new Reader().start();数量= 42;准备好= true;}}

TaskRunner类维护两个简单的变量。在其主要方法中,它创造了另一个旋转的螺纹准备好了可变,只要它错误的。变量变为真的,线程将简单地打印数字多变的。

许多人可能希望在短暂延迟之后简单地打印42。但是,实际上,延迟可能更长。它甚至可能永远挂起,甚至打印零!

这些异常的原因是缺乏适当的内存可见性和重新排序。让我们更详细地评估它们。

3.1。记忆的可见性

在这个简单的示例中,我们有两个应用程序线程:主线程和读取器线程。让我们想象这样一个场景:操作系统在两个不同的CPU核上调度这些线程:

  • 主线程有其副本准备好了数字在其核心缓存中的变量
  • 读者线程也含有副本
  • 主线程更新缓存值

在大多数现代处理器上,在发出时,写请求将不会立即应用。实际上,处理器倾向于在特殊的写缓冲区中排队那些写入。过了一会儿,他们会立即将这些写入主动留给主记忆。

尽管如此,当主线程更新时数字准备好了变量,不能保证读取线程会看到什么。金宝搏官网188be换句话说,读取线程可能马上看到更新的值,或者有一些延迟,或者根本看不到!

这种内存可见性可能会导致依赖于可见性的程序的活动问题。

3.2。重新排序

更糟糕的是,读取线程可以看到这些写操作的任何顺序,而不是实际程序的顺序。例如,自从我们第一次更新数字多变的:

public static void main(String[] args) {new Reader().start();数量= 42;准备好= true;}

我们可能期望读取线程打印42。但是,它实际上可以看到零作为印刷价值!

重新排序是用于性能改进的优化技术。有趣的是,不同的组件可能适用这种优化:

  • 处理器可以按照程序顺序以外的任何顺序刷新其写缓冲区
  • 处理器可以应用乱序执行技术
  • JIT编译器可以通过重新排序优化

3.3。易挥发的记忆订单

要确保对变量的更新可以预期地传播到其他线程,我们应该应用易挥发的这些变量的修饰符:

公共类TaskRunner {私有挥发性静态int号码;私人挥发性静态布尔准备;// 和之前一样 }

通过这种方式,我们与运行时和处理器通信,不重新排序任何涉及的指令易挥发的多变的。此外,处理器明白,它们应该立即刷新这些变量的任何更新。

4.易挥发的和线程同步

对于多线程应用程序,我们需要确保一些规则以获得一致的行为:

  • 互斥——一次只有一个线程执行一个临界段
  • 可见性 - 一个线程对共享数据进行的更改对于其他线程可见以维护数据一致性

同步方法和块在应用性能成本下提供上述性质。

易挥发的是一个非常有用的关键词,因为它帮助确保数据更改的可见性,当然,不提供互斥。因此,它在我们可以在那些并行执行代码块的多个线程的地方有用,但我们需要确保可见性属性。

5.之前订购

记忆可见性的影响易挥发的变量超出了易挥发的变量本身。

让事情变得更具体,让我们假设线程写入易挥发的变量,然后线程B读取相同的易挥发的多变的。在这种情况下,在写入之前对A可见的值易挥发的变量在读取后对B可见易挥发的多变的:

从技术上讲,任何写作易挥发的字段发生在每次后续读取同一字段之前。这是易挥发的Java内存模型的可变规则(JMM)。

5.1。肩扛

由于内存排序前发生的力量,有时我们可以捎带对另一个的可见性属性易挥发的变量。例如,在我们的例子中,我们只需要标记准备好了变量,易挥发的

public class TaskRunner {private static int number;// not volatile private volatile static boolean ready;// 和之前一样 }

写作之前的任何事情真正的准备好了阅读后的变量可见准备好了多变的。因此,这是数字可变背包对由此执行的内存可见度准备好了多变的。简单地说即使它不是一个易挥发的变量a易挥发的的行为。

通过利用这些语义,我们可以仅在课堂上定义一些变量易挥发的优化能见度保证。

6.结论

在本教程中,我们已经探讨了更多关于的信息金宝搏官网188be易挥发的关键字及其功能,以及与Java 5开始的改进。

一如既往,可以找到代码示例在github上

Java底部

使用Spring 5和Spring Boot 2开始,通过学习的春天课程:

>>查看课程
7.评论
最老的
最新
内联反馈
查看所有评论
评论在本文上关闭!