app应用下载网站源码,个人装修设计软件,深圳正规网站建设公司,赣州做网站什么价格volatile关键字大家并不陌生#xff0c;尤其是在面试的时候#xff0c;它被称为“轻量级的synchronized”。但是它并不容易完全被正确的理解#xff0c;以至于很多程序员都不习惯去用它#xff0c;处理并发问题的时候一律使用“万能”的sychronized来解决#xff0c;然而如…volatile关键字大家并不陌生尤其是在面试的时候它被称为“轻量级的synchronized”。但是它并不容易完全被正确的理解以至于很多程序员都不习惯去用它处理并发问题的时候一律使用“万能”的sychronized来解决然而如果能正确地使用volatile的话它比synchronized的使用和执行成本更低因为它不会引起线程上下文的切换和调度。
下面我们从volatile关键字的定义说起。
一、什么是volatile关键字
volatile关键字可以说是Java虚拟机提供的最轻量级的同步机制和synchronized不同volatile是一个变量修饰符只能用来修饰变量用法也比较简单只需要在声明一个可能被多线程方法的变量时使用volatile修饰即可。
但是在并发编程的三大特性——原子性、可见性、有序性中volatile只能保证可见性和有序性禁止指令重排并不能保证原子性而synchronized这三种特性都可以保证。
那么volatile为什么不能保证原子性而synchronized可以还有可见性和有序性它俩又是怎么保证的呢别急接着往下看。
二、Java内存模型
由于volatile关键字与Java内存模型有较多的关联所以在详细介绍volatile关键字之前需要先了解一下Java内存模型。
2.1 什么是Java内存模型
Java内存模型Java Memory Model简称JMM是Java虚拟机JVM对多线程程序中的内存访问和操作进行规范的一种抽象并不真实存在。它定义了线程如何与主存共享内存和工作内存线程私有内存进行交互以及如何同步和互斥地访问共享数据。
简单地说就是JMM定义了程序中各种变量共享的访问规则即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。 《Java虚拟机规范》中曾试图定义一种“Java内存模型”来屏蔽各种硬件和操作系统的内存范根差异以实现让Java程序在各种平台下都能达到一致的访问效果但这并非是一件易事这个模型必须定义的足够严谨才能让Java的并发内存访问操作不会产生歧义但是也必须定义得足够宽松使得虚拟机得实现能有足够得自由空间去利用硬件得各种特性寄存器、高速缓存和指令集中某些特有的指令来获取更好的执行速度。Java内存模型自JDK1.2建立起来随后又经过长时间的验证和修补直到JDK5JSR-133)发布后也就是目前正在使用的Java内存模型才终于成熟、完善起来了。 psJSR-133对旧内存模型的修补主要有两个 增强volatile的内存语义旧内存模型允许volatile变量与普通变量重排序。JSR-133严格限制volatile变量与普通变量的重排序使volatile的写-读和锁的释放-获取具有相同的内存语义。增强final的内存语义在旧内存模型中多次读取同一个final变量的值可能会不相同。为此JSR-133为final增加了写和读重排序规则。在保证final引用不会从构造函数内逃逸出的情况下final具有了初始化安全性。 2.2 主内存与工作内存
Java内存模型规定了所有的变量都存储在主内存中每条线程还有自己的工作内存线程的工作内存保存了被该线程使用到的变量的主内存副本线程对变量的所有操作读取、赋值等都必须在工作内存中进行而不能直接读写主内存中的数据。不同线程之间也无法直接访问对方工作内存中的变量线程间变量值的传递均需要通过主内存来完成。线程、主内存、工作内存三者的交互关系如下图 2.3 内存间交互操作
ps这里做个简单了解即可因为除了虚拟机开发团队外大概没有其他开发人员会以这种方式来思考并发问题。下面会介绍该部分内容的等效判断原则——先行发生规则happens-before相较于这种方式更容易理解。
关于主内存与工作内存之间具体的交互协议Java虚拟机定义了8种原子操作 lock锁定作用于主内存的变量它把一个变量标识为一条线程独占的状态。unlock解锁作用于主内存的变量它把一个处于锁定状态的变量释放出来释放后的变量才可以被其它线程锁定。read读取作用于主内存的变量它把一个变量的值从主内存传输到线程的工作内存中以便随后的load动作使用。load载入作用于工作内存的变量它把read操作从主内存中得到的变量值放入工作内存的变量副本中。use使用作用于工作内存的变量它把工作内存中一个变量的值传递给执行引擎每当虚拟机遇到一个需要使用的变量的值的字节码指令时执行这个操作。assign赋值作用于工作内存的变量它把一个执行引擎接收到的值赋给工作内存的变量每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。stroe存储作用于工作内存的变量它把工作内存中一个变量的值传送到主内存中以便随后的write操作使用。write写入作用于主内存的变量它把store操作从工作内存中得到的变量的值放入主内存的变量中。 由上可见如果要把一个变量从主内存复制到工作内存那就要顺序执行read和load操作而要把变量从工作内存同步回主内存就要顺序执行strore和write操作。注意这里是顺序执行而不代表它们会连续执行如对主内存中的变量a、b进行访问时可能出现的顺序是read a、read b、load b、load a。
除此之外针对上述8种基本操作Java内存模式还制定了8种规则 不允许read和load、store和write操作之一单独出现即不允许一个变量从主内存读取了但工作内存不接受或者从工作内存发起回写了但主内存不接受的情况出现。不允许一个线程丢弃它的最近的assign操作即变量在工作内存中改变了之后必须把该变化同步回主内存。不允许一个线程无原因地没有发生过任何assign操作把数据从线程的工作内存同步回主内存中。一个新的变量只能在主内存中“诞生”不允许在工作内存中直接使用一个未被初始化load或assign的变量换句话说就是对一个变量实施use、store操作之前必须先执行过了assign和load操作。一个变量在同一个时刻只允许一条线程对其进行lock操作但lock操作可以被同一条线程重复执行多次多次执行lock后只有执行相同次数的unlock操作变量才会被解锁。如果对一个变量执行lock操作那将会清空工作内存中此变量的值在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。如果一个变量事先没有被lock操作锁定那就不允许对它执行unlock操作也不允许去unlock一个被其他线程锁定住的变量。对一个变量执行unlock操作之前必须先把此变量同步回主内存中执行store、write操作。 三、原子性、可见性和有序性
读到这相信你已经对Java内存模型有了一定了解那么下面我们就基于Java内存模型来介绍一下Java并发的三大特性原子性、可见性和有序性。
3.1 原子性
3.1.1 什么是原子性 定义一个或多个操作不可拆分、不被中断。 ps数据库中ACID的原子性指的是“要么都执行要么都回滚”这是两个不同的概念。 首先说一句volatile并不能保证原子性。
pssynchronized关键字可以保证原子性因为被synchronized修饰的方法或代码块在进入之前都加了锁同一时刻有且仅有一个线程能执行被“锁”住的代码片段这就保证了它内部的代码可以全部被执行所以它具备原子性基于monitorenter和monitorexit指令实现。
3.1.2 举例说明
先举一个例子加深一下对Java并发编程中原子性的理解。
//代码1
int a 1;
//代码2
a;
//代码3
int a b;
//代码4
k k 1
问上述4个代码哪些是原子操作你可能会说是代码1但我告诉你上述均不是原子操作下面我们来分析一下多线程情况下 代码1别看它只是简单的赋值操作在JMM中包含了两个操作一是从主存中读取a的值到工作内存二是在工作内存中将a的值设置为1这些步骤可以被其它线程中断所以它不具备原子性下面就简单说了。代码2包含了三个操作。一是读取变量a的值二是把变量a的值加一三是将计算后的值再赋值给变量a。代码3包含了两个操作。一是读取变量b的值二是将变量b的值赋值给a。代码4包含三个操作。一是读取变量k的值二是将变量k的值加一三是将计算后的值再赋值给变量k。 从Java内存模型来看直接保证的原子性变量操作包括read、load、assign、use、store和write我们大致可以认为基本数据类型的访问读写时具备原子性的。
从应用场景来看JVM保证原子性操作的主要方式如下 虽然Java内存模型还提供了lock和unlock操作来满足原子性的需求但并未对用户开放使用而是提了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作也就是我们熟悉的同步块——synchronized关键字。 AQS的锁机制比如ReentrantLock、ReentrantReadWriteLock等。 CAS实现java.util.concurrent.atomic包下的原子操作类比如AtomicInteger、AtomicLong等。 3.1.3 为什么volatile不具备原子性
那volatile关键字为什么不能保证原子性呢很简单的一点就是它不是锁而且也没做任何可以保证原子性的处理这当然不能保证原子性了。
下面看个经典的i案例
public class TestIncr {volatile int num 0;public void add() {num;}public static void main(String[] args) {TestIncr test new TestIncr();//启动10个线程for (int i 0; i 10; i) {new Thread(() - {//每个线程执行1000次1操作for (int j 0; j 1000; j) {test.add();}}, String.valueOf(i)).start();}while (Thread.activeCount() 2) {Thread.yield();}System.out.println(最后num的值为 test.num);}
}
多次执行的其中一个结果 以上代码我们的预期结果应该是10000才对但执行起来发现并不是每次都是10000这就是因为i这个操作没办法保证原子性。它其实包含三个指令 执行GETFIELD拿到主内存中的原始值num。 执行IADD进行1操作。 执行PUTFIELD把工作内存中的值写回主内存中。 当多个线程并发执行PUTFILED指令的时候会出现写回主存覆盖的问题所以最终结果可能会比预期的结果要小所以volatile不能保证原子性。
3.2 可见性
volatile的两大特性之一就是可以保证可见性下面我们就详细介绍一下。
3.2.1 定义及实现 定义指多个线程之间共享数据的可见性。即当一个线程修改了共享变量时其它线程能立即看到这个修改。 当对volatile变量进行写操作的时候JVM会向处理器发送一条Lock前缀的指令将这个缓存中的变量写回到系统主存中。
所以如果一个变量被volatile修饰的话在每次数据变化之后其值都会被强制刷入主存。而其它处理器的缓存由于遵守了缓存一致性协议比如MSI、MESI、MOSI、Synapse、Firefly及Dragon Protocaol等也会把这个变量的值从主存加载到自己的缓存中这就保证了一个volatile在并发编程中其值在多个缓存中是可见的。
pssynchronized的可见性是由上述八种规则中的——“对一个变量执行unlock操作之前必须先把此变量同步回主内存中执行store、write操作”这条规则实现的。
3.2.2 举例说明
下面是一个简单示例先看看不用volatile的效果
public class TestVisibility {private boolean flag false;public void start() {new Thread(() - {System.out.println(Thread 1 start);while (!flag) {// 不断循环等待flag变为true}System.out.println(Thread 1 complet);}).start();// 确保线程1先启动try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() - {System.out.println(Thread 2 start);// 修改flag的值为trueflag true;System.out.println(Thread 2 complet);}).start();}public static void main(String[] args) {TestVisibility example new TestVisibility();example.start();}
}
运行结果 从结果看程序“卡”在了Thread 1的while循环中说明Thread 1读到的flag还是flase但是Thread 2已经把它改为true了这是为什么这是因为Thread 1在执行的时候就把flag的副本保存在这就的工作内存中之后就会一直读取自己线程工作内存中flag变量的值而不会去主内存中重新获取新的值。
加了volatile修饰后的运行结果 可以看到Thread 1和2都执行完了volatile能确保对flag的写操作立即刷新到主内存并且对flag的读操作会从主内存中获取最新的值。
3.3 有序性
volatile除了可以保证数据的可见性之外还有一个强大的功能那就是它可以禁止指令重排序所以能在一定程度上保证有序性。
3.3.1 定义和实现 定义一个线程中的所有操作必须按照程序的顺序来执行。 volatile的有序性是它本身的特性——禁止指令重排实现的而禁止指令重排又是由内存屏障来实现的下面有介绍。
pssynchronized的有序性则是由“一个变量在同一时刻只允许一条线程对其进行Lock操作”这条规则实现的。
3.3.2 举例说明
最经典的例子当然是双重检测实现单例的例子了如下
public class Singleton {//私有化构造函数private Singleton(){}//单例对象无volatile修饰private static Singleton instancenull;public static Singleton getInstance(){//第一次检测if (instancenull){//加锁synchronized (Singleton.class){//第二次检测if (instancenull){//初始化instancenew Singleton();}}}return instance;}
}
以上代码我们通过使用synchronized对Singleton.class加锁可以保证同一时间只有一个线程可以执行到同步代码块中的内容这就实现了一个单例。
但是在极端情况下上述的单例对象可能发生空指针异常那么这是如何发生的呢
我们假设线程1和线程2同时请求getSingleton()方法的时候 线程1执行到instancenew Singleton();开始初始化。线程2执行到“第一次检测”的位置判断singleton null。线程2经过判断发现singleton !null于是就直接执行return instance。线程2拿到singleton对象后开始执行后续的操作。 以上过程看似没有什么问题但在第4步执行后续操作的时候是有可能抛空指针异常的这是因为在第3步的时候线程2拿到的singleton对象并不是一个完整的对象。
很明显instancenew Singleton();,这段代码出现了问题那我们来分析一下这个代码的执行过程可以简化成3步 JVM为对象分配一块内存M。在内存上为对象进行初始化。将内存M的地址赋值给singleton变量。 因为将内存的地址赋值给singleton变量是最后一步所以线程1在这一步骤执行之前线程2在对singleton null判断一直都是true那么它会一直阻塞直到线程1执行完。
但是这个过程并不是一个原子操作并且编译器可能会进行重排序如果以上步骤被重排序为 JVM为对象分配一块内存M。将内存M的地址赋值给singleton变量。在内存上为对象进行初始化。 这样的话线程1会先内存分配再执行变量赋值最后执行初始化。也就是说在线程1执行初始化之前线程2对singleton null的判断会提前得到一个false于是便返回了一个不完整的对象所以在执行后续操作时就发生了空指针异常。
很明显这是指令重排造成的问题要解决的话直接禁止它指令重排就行了所以volatile就派上用场了只需要用volatile修饰一下instance即可这里代码就不做展示了。
3.4 有了synchronized为什么还需要volatile 介绍完并发中的三大特性我们发现在这三种特性中synchronized总能作为其中的一种解决方案看起来很“万能”对吧。不过确实是这样绝大部分的并发控制都能用synchronized来完成这就是出现“遇事不决就synchronized”的原因。 虽然synchronized很“万能“但它毕竟是锁那么既然是锁它天然就具备以下缺点 有性能损耗虽然在JDK1.6种对synchronized做了很多优化比如适应性自选、锁消除、锁粗化、轻量级锁和偏向锁等。但它毕竟是一种锁所以无论是同步方法还是代码块在同步操作之前还是要进行加锁同步操作之后解锁这个加锁和解锁的过程都是有性能损耗的。产生阻塞无论是同步方法还是代码块换句话说无论是ACC_SYNCHRONIZED还是monitorenter和monitorexit都是基于Monitor实现的。基于Monitor对象当多个线程同时访问一段同步代码时首先会进入Entry Set当有一个线程获取到对象的锁之后才能进行The Owner区域其他线程还会继续等待。并且当某个线程调用了wait方法后会释放锁并进入Wait Set等待所以synchronized实现的锁本质上是一种阻塞锁。 所以volatiile比synchronized的性能更好除此之外volatile还有一个很好的附加功能就是可以禁止指令重排volatile借助内存屏障来帮助其解决可见性和有序性问题有一个典型的例子就是3.3.2小节的双重校验锁实现的单例模式在没有volatile修饰的intance变量时可能会发生空指针异常有volatile修饰时就可以用volatile禁止指令重排的特性完美解决此问题。
四、指令重排序
上面在介绍有序性的时候提到了“重排序”这里介绍一下。
4.1 什么是重排序 定义指在保证最终结果不受影响的前提下可以改变程序中指令的执行顺序以达到提高代码执行效率的效果。 具体来说指令重排可能会包括以下几种情况 编译器优化编译器在生成目标代码时可以对指令进行重排。处理器优化处理器可以根据指令之间的依赖性重排指令的执行顺序以便更有效地使用处理器资源。内存系统优化处理器可以利用缓存和读写缓冲区等机制来重排对内存的读和写操作。 4.2 数据依赖性
如果两个操作访问同一变量且这两个操作中有一个是写操作此时这两个操作之间就存在数据依赖性。数据依赖分为3种类型如下图 上面的三种情况在单线程的情况下只要重排序了两个操作的执行顺序就会改变最终结果因此这三种情况是不会被重排序的。
int a 1;
int b 2;
上面这段代码的两个操作并没有数据依赖性改变两者的执行顺序也不会影响最终结果因此有可能被重排序。
ps补充一个as-if-serial语义——不管怎么重排序单线程程序的执行结果不能改变。编译器、runtime和处理器都必须遵守as-if-serial语义。为了遵守as-if-serial语义编译器和处理器不会对存在数据依赖性关系的操作做重排序。
五、内存屏障Memory barrier)
上面在介绍有序性的时候也提到了“内存屏障”volatile禁止指令重排的特性就是基于内存屏障来实现的下面我们来看一看。
5.1 什么是内存屏障 概念在Java中内存屏障是一种机制用于控制指令重排和内存可见性确保多线程程序的操作顺序和一致性。 5.2 volatile变量的内存屏障
volatile变量的内存屏障是通过一组指令来实现的包括LoadLoad、LoadStore、StoreStore和StoreLoad。这些指令用于保证在volatile变量的读取和写入操作中相邻指令之间顺序不会被改变 LoadLoad确保在读取一个volatile变量前前面的所有读操作都已经完成。LoadStore确保在读取一个volatile变量后后面的所有写操作都还没有开始。StoreStore确保在写入一个volatile变量前前面的所有写操作都已经完成。StoreLoad确保在写入一个volatile变量后后面的所有读操作都还没有开始。 当一个线程执行一个读取volatile变量的操作时Java会插入LoadLoad和LoadStore屏障。LoadLoad屏障会防止该读操作和前面的任何读取操作被重排序LoadStore屏障则会防止该读取操作和后续的写入操作被重排序。
当一个线程执行一个写入volatile变量的操作时Java会插入StoreStore和StoreLoad屏障。StoreStore屏障会防止该写入操作和前面的任何写入操作被重排序StoreLoad屏障则会防止该写入操作和后续的读取操作被重排序。
5.3 举个例子
public class VolatileTest {private volatile int value 0;public void setValue(int newValue){//Store操作value newValue;}public int getValue(){//Load操作return value;}
}
在上面例子中由于value变量被volatile修饰所以编译器和JVM会在编译和执行过程中插入内存屏障 当执行setValue()方法时编译器会插入StoreStore屏障确保value被修改之前所有的写操作都已经完成。然后编译器会插入StoreLoad屏障确保在value被修改之后所有的读操作都还没开始。当执行getValue()方法时编译器会插入LoadLoad屏障确保在读取value之前前面的所有读操作都已经完成。然后编译器会插入LoadStore屏障确保在读取value之后后面的所有写操作都还没有开始。 通过插入这写内存屏障Java确保了voaltile变量的可见性和禁止重排序从而使多线程访问volatile变量时能够正确地同步数据。
六、先行发生原则happens-before
在2.3节——内存间交互操作中提到了happens-before原则。它是JMM最核心的概念它是判断数据是否存在竞争、线程是否安全的主要依据依靠这个原则我们可以通过几条规则解决并发环境下两个操作之间是否可能存在冲突的所有问题因此对于Java程序员来说理解happens-before是理解JMM的关键。
6.1 什么是happens-before原则 概念“happens-before”原则是Java内存模型中的一种规则用于确定在多线程环境下对于多个操作之间的执行顺序和可见性。 如果说操作A先行发生于操作B其实就是说在发生操作B之前操作A产生的影响能被操作B观察到“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。
6.2 举个例子
举个例子来加深对happens-before原则的理解
k 1;//线程A中执行
j k;//线程B中执行
k 2;//线程C中执行
假设线程A中的操作“k1”先行发生于线程B的操作“jk”那么可以确定在线程B的操作执行后变量j的值一定是1得出这个结论的依据有两个一是线程发生原则“k1”的结果可以被观察到而是线程C还没“登场”线程A操作结束之后没有其它线程会修改k的值。
现在来考虑线程C我们依然保持线程A和线程B的先行发生关系而线程C出现在线程A和线程B操作之间但是线程C与线程B没有先行发生关系那j的值会是多少呢答案是不确定1和2都有可能因为线程C对变量k的影响可能会被B观察到也可能不会这时候线程B就存在读取到过期数据的风险不具备多线程安全性。
6.3 happens-before规则
《JSR-133:Java Memory Model and Thread Specification》定义了如下happens-before规则也是Java内存模型下“天然的”先行发生关系这些先行发生关系无须任何同步器就已经存在可以在编码中直接使用。如果两个操作之间的关系不在此列并且无法从下列规则推导出来的话它们就没有顺序性保障虚拟机可以对它们随意地进行重排序 程序顺序规则一个线程中的每个操作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()操作成功返回。interrupted()规则对线程interrupt()方法的调用happens-before于被中断线程的代码检测到中断事件的发生可以通过Thread.interrupted()方法检测到是否有中断发生。对象终结规则一个对象初始化完成构造函数执行结束happens-before它的finalize()方法的开始。 Java语言无须任何同步手段保障就能成立的先行发生规则就只有上面这些了下面演示一下如何使用这些规则去判定操作间是否具备顺序性对于读写共享变量的操作来说就是线程是否安全读者还可以从下面这个例子中感受一下“时间上的先后顺序”与“先行发生”之间有什么不同。
public class VolatileTest {private int value 0;public void setValue(int newValue){value newValue;}public int getValue(){return value;}
}
上面的代码是一组再普通不过的getter/setter方法假设存在线程A和B线程A先时间上的先后调用了“setValue(1)”然后线程B调用了同一个对象的“getValue()”那么线程B收到的返回值是什么
我们依次分析一下先行发生原则中的各项规则由于两个方法分别由线程A和线程B调用不在一个线程中所以程序次序规则在这里不适用由于没有同步块自然就不会发生lock和unlock操作所以管程锁定规则不适用由于value变量没有被volatile关键字修饰所以volatile变量规则不适用后面的线程启动、终止、中断规则和对象终结规则也和这里完全没有关系。因为没有一个适用的先行发生规则所以最后一条传递性也无从谈起因此我们可以判定尽管线程A在操作时间上先于线程B但是无法确定线程B中“getValue()”方法的返回结果换句话说这里面的操作不是线程安全的。
那怎么修复这个问题呢我们至少有两种比较简单的方案可以选择要么把getter/setter方法都定义为synchronized方法这样就可以套用管程锁定规则要么把value定义为volatile变量由于setter方法对value的修改不依赖value的原值满足volatile关键字使用场景这样就可以套用volatile变量规则来实现先行发生关系。
通过上面的例子我们可以得出结论一个操作“时间上的先发生”不代表这个操作会是“先行发生”那如果一个操作“先行发生”是否就能推导出这个操作必定是“时间上的先发生”呢很遗憾这个推论也是不成立的一个典型的例子就是多次提到的“指令重排序”演示例子如下代码所示。
//以下操作在同一个线程中执行
int i 1;
int j 2;
代码清单的两条赋值语句在同一个线程之中根据程序次序规则“int i1”的操作先行发生于“int j2”但是“int j2”的代码完全可能先被处理器执行这并不影响先行发生原则的正确性因为我们在这条线程之中没有办法感知到这点。
上面两个例子综合起来证明了一个结论时间先后顺序与先行发生原则之间基本没有太大的关系所以我们衡量并发安全问题的时候不要受到时间顺序的干扰一切必须以先行发生原则为准。
七、总结 本篇文章从volatile的定义谈起由于volatile与Java内存模型有较多的关联所以接着介绍Java内存模型的相关概念、线程、主内存和工作内存之间的关系以及内存间的交互规则随后详细介绍了volatile和synchronized在Java并发编程的三大特性——原子性、可见性和有序性中的表现重点介绍volatile。最后介绍了一下“重排序”、“内存屏障”和“先行发生原则happens-before”等重要概念。 End希望对大家有所帮助如果有纰漏或者更好的想法请您一定不要吝啬你的赐教。