0%

并发编程底层原理-上篇

在多线程条件下,出现线程安全的问题一般是因为主内存和工作内存数据不一致性和重排序导致的,那么,理解它们的核心在于理解java内存模型(JMM)。

JMM

JMM(Java内存模型)是一组规范,需要各个JVM的实现来遵循JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。如果没有这样的一个JMM内存模型来规范,那么很可能经过了不同的JVM的不同规则的重排序之后,导致不同的虚拟机上的运行结果不一样。

volatile、synchronized、lock等原理都是JMM,如果没有JMM那就需要我们指定什么时候用内存栅栏等,有了JMM,我们只需要用同步工具和关键字就可以开发并发程序。JMM中最重要的三点内容是:重排序、可见性、原子性。

重排序

在线程内部的两行代码的实际执行顺序和代码在Java文件中的不一致,代码指令并不是严格按照代码语句顺序执行的,它们的顺序被改变了,这就是重排序。比如:

int a = 1;
int b = 2;

你可能会认为,这两行代码的执行顺序就如写代码的顺序一样,实际上是不一定的。java语言是允许重排序的,所以代码的执行顺序可能是先给b赋值。

int a = 1;
int b = a;

像上面这种b对a有数据依赖的,是不会被重排序的,执行顺序必然是a在b之前。

重排序的三种情况分别是:

  1. 编译器优化:编译器(包括JVM,JIT编译器等)出于优化的目的(例如当前有了数据a,那么如果把对a的操作放到一起效率会更高,避免了读取b后又返回来重新读取a的时间开销),在编译的过程中会进行一定程度的重排,导致生成的机器指令和之前的字节码的顺序不一致。
  2. 指令重排序:CPU 的优化行为,和编译器优化很类似,是通过乱序执行的技术,来提高执行效率。所以就算编译器不发生重排,CPU 也可能对指令进行重排,所以我们开发中,一定要考虑到重排序带来的后果。
  3. 内存的“重排序”:内存系统内不存在重排序,但是内存会带来看上去和重排序一样的效果,所以这里的“重排序”打了双引号。由于内存有缓存的存在,在JMM里表现为主存和本地内存,由于主存和本地内存的不一致,会使得程序表现出乱序的行为。

可见性

JMM有以下规定:

  1. 所有的变量都存储在主内存中,同时每个线程也有自己独立的工作内存,工作内存中的变量内容是主内存中的拷贝。
  2. 线程不能直接读写主内存中的变量,而是只能操作自己工作内存中的变量,然后再同步到主内存中。
  3. 主内存是多个线程共享的,但线程间不共享工作内存,如果线程间需要通信,必须借助主内存中转来完成。

所有的共享变量存在于主内存中,每个线程有自己的本地内存,而且线程读写共享数据也是通过本地内存交换的,所以才导致了可见性问题。主内存和本地内存的图示:

memory.jpg

Java作为高级语言,屏蔽了CPU多层缓存这些底层细节,用JMM 定义了一套读写内存数据的规范,虽然我们不再需要关心一级缓存和二级缓存的问题,但是,JMM抽象了主内存和本地内存的概念。这里说的本地内存并不是真的是一块给每个线程分配的内存,而是JMM的一个抽象,是对于寄存器、一级缓存、二级缓存等的抽象。

Happens-Before

Happens-Before规则是用来解决可见性问题的:在时间上,动作A发生在动作B之前,B保证能看见A,这就是
Happens-Before。

Happens-Before的八个规则:

  1. 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
  2. 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
  3. volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作。
  4. happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
  5. 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
  6. 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
  7. 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
  8. 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

总结

本文介绍JMM的基本原理和Happens-Before的规则,下篇我们将介绍volatile关键字和原子性问题。