JAVA并发理解之重排序问题

摘要:
1) 数据依赖关系与重新排序有关。这里我们将首先讨论数据依赖性的概念。为了遵守串行语义,编译器和处理器不会对具有数据依赖性的操作进行重新排序。就像串行语义保护单线程程序一样。仿佛串行语义使单线程程序员能够担心重新排序干扰和内存可见性。② 以上是JMM对编译器和处理器重新排序的约束原则。

 首先我们先来了解一下什么是重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序,如下图所示

      上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序可能会导致多线程程序出现内存可见性问题。在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

 

1)数据依赖性(针对单个处理器而已)

      关于重排序,这里要先讲一个概念就是数据依赖性问题。如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3种类型,如下表所示。

 JAVA并发理解之重排序问题第1张

上面3种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

2)as-if-serial语义

    as-if-serial语义的意思是:不管怎么重排序,(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

   为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序。as-if-serial语义把单线程程序保护了起来,as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

3)happens-before

       如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。

     对happens-before关系的具体定义如下。

    ① 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
    ②两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照 happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。

      上面的①是JMM对程序员的承诺。从程序员的角度来说,可以这样理解happens-before关系:如果A happens-before B,那么Java内存模型将向程序员保证——A操作的结果将对B可见,且A的执行顺序排在B之前。注意,这只是Java内存模型向程序员做出的保证!上面的②是JMM对编译器和处理器重排序的约束原则。正如前面所言,其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。因此,happens-before关系本质上和as-if-serial语义是一回事。

      ·as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
      ·as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
      · as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

   happens-before规则如下:

    程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
    监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
    volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
    传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
    start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
    join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

免责声明:文章转载自《JAVA并发理解之重排序问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Exchange调整入站SMTP连接超时时间SQL批量添加数据库中所有用户数据表描述下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

用python写多线程

import threading #首先导入threading 模块,这是使用多线程的前提 from time import ctime,sleep def music(func): for i in range(3): print "I was listening to" +func+ct...

Java ScheduledThreadPoolExecutor延迟或周期性执行任务

Java提供的Time类可以周期性地或者延期执行任务,但是有时我们需要并行执行同样的任务,这个时候如果创建多个Time对象会给系统带来负担,解决办法是将定时任务放到线程池中执行。 Java的ScheduledThreadPoolExecutor类实现了ScheduledExecutorService接口中定义的以不同方法执行任务的方法。 之前,我写过一篇关...

linux环境下排查cpu占比高的MySQL数据库sql语句

自MySQL 5.7版本后,PERFORMANCE_SCHEMA.THREADS表中新增一个字段THREAD_OS_ID,对应操作系统中的线程ID 1.安装sysstat工具包(为了使用pidstat命令),更多信息参考: https://www.linuxidc.com/Linux/2019-08/160082.htm Ubuntu: apt-get i...

SocketChannel简述

前言 在前面的Channel概述的分类中提到过SocketChannel主要是用来基于TCP通信的通道。这篇文章详细介绍下SocketChannel SocketChannel是什么 SocketChannel特点 SocketChannel的使用 SocketChannel A selectable channel for stream-orient...

Python-进程与线程

进程与线程的历史 我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心,它承担计算机的所有任务。 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配、任务的调度。 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等。 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就...

C/C++常用库及工具

值得学习的C语言开源项目 - 1. Webbench  Webbench是一个在Linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力。Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行。 下载链接:http://...