并发编程的三要素
原子性(Atomicity): 在一次或多次操作中，要么所有的操作都执行并且不会受其他因素干扰而中断，要么所有的操作都不执行
(指令重排:处理器为了提高程序运行效率，处理器根据指令之间的数据依赖性，可能会对指令进行重排序，单线程下可以保证程序最终执行结果和代码顺序执行的结果是一致的，但是多线程下有可能出现问题)
并发编程三要素详解: 原子性: 1.什么是原子性:一个或多个操作要么全部执行成功,要么全部执行失败
2.什么会造成原子性问题 :线程切换会带来原子性的问题 ;
3.原子操作的案例 :
分析一下操作的原子性:
语句2实际上包含2个操作，它先要去读取x的值，再将x的值写入工作内存，虽然 读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作 ，但是合起来就不是原子性操作了， 我们可以用synchronized 或 Lock 来把这个复合操作“变成”原子操作 。
4.如何解决原子性问题 (保证原子性):
原子类解决原子性问题:
可见性: 1.什么是可见性:一个线程对共享变量的修改,其他线程能够立刻看到
2.什么会导致可见性问题 :缓存导致的可见性问题
3.可见性的案例 :
//线程1执行的代码,以下两行代码CPU1执行
//线程2执行的代码，CPU2执行
4.如何解决可见性问题 :
而普通的共享变量(非volatile修饰)不能保证可见性，因为普通共享变量被修改之后，什么时候被写入主存是不确定的，当其他线程去读取时，此时内存中可能还是原来的旧值，因此无法保证可见性。有序性: 1.什么是有序性:程序执行的顺序按照代码的先后顺序执行
2.什么会导致有序性问题 :编译优化带来的有序性问题
3.有序性的案例 :
在Java里面，可以** 通过volatile关键字来保证一定的“有序性” **。volatile关键字的两层语义: 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后，那么就具备了两层语义:1) 禁用缓存，保证内存可见性 。
保证了不同线程对这个变量进行操作时的可见性，即一个线程修改了某个变量的值，它会保证修改的值会立即被更新到主存，这新值对其他线程来说是立即可见的 。
2) 禁止指令重排 。
注意 : volatile关键字能保证可见性，可见性只能保证每次读取的是最新的值，但是volatile没办法保证对变量的操作的原子性 。
volatile应用:
①状态量标记(如上flag)
②单例模式的懒汉式
出现线程安全问题的原因: 线程切换带来的原子性问题缓存导致的可见性问题
解决办法: JDK Atomic开头的原子类、synchronized、LOCK，可以解决原子性问题synchronized、volatile、LOCK，可以解决可见性问题
Happens-Before (之前发生)规则可以解决有序性问题
一、原子性 一个不可再被分割的颗粒，原子性指的是一个或多个操作要么全部执行成功要么全部执行失败，期间不能被中断，也不存在上下文切换，线程切换会带来原子性的问题int num = 1; // 原子操作
num++; // 非原子操作，从主内存读取num到线程工作内存，进行 +1，再把num写到主内存, 除非用原子类，即java.util.concurrent.atomic里的原子变量类
解决办法是可以用synchronized 或 Lock(比如ReentrantLock) 来把这个多步操作“变成”原子操作，但是volatile，前面有说到不能修饰有依赖值的情况
public class XdTest { private int num = 0 ; //使用lock，每个对象都是有锁，只有获得这个锁才可以进行对应的操作 Lock lock = new ReentrantLock ( ) ; public void add1 ( ) { lock . lock ( ) ; try { num ++ ; } finally { lock . unlock ( ) ; } } //使用synchronized，和上述是一个操作，这个是保证方法被锁住而已，上述的是代码块被锁住 public synchronized void add2 ( ) { num ++ ; } }
解决核心思想:把一个方法或者代码块看做一个整体，保证是一个不可分割的整体
二、有序性: 程序执行的顺序按照代码的先后顺序执行，因为处理器可能会对指令进行重排序JVM在编译java代码或者CPU执行JVM字节码时，对现有的指令进行重新排序，主要目的是优化运行效率(不改变程序结果的前提)
假如下面的场景，正常是顺序处理 //线程1 before ( ) ; //处理初始化工作，处理完成后才可以正式运行下面的run方法 flag = true ; //标记资源处理好了，如果资源没处理好，此时程序就可能出现问题 //线程2 while ( flag ) { run ( ) ; //核心业务代码 }
指令重排序后，导致顺序换了，程序出现问题，且难排查
flag = true; //标记资源处理好了，如果资源没处理好，此时程序就可能出现问题
run(); //核心业务代码
before();//处理初始化工作，处理完成后才可以正式运行下面的run方法
三、可见性: 一个线程A对共享变量的修改,另一个线程B能够立刻看到// 线程 A 执行 int num = 0 ; // 线程 A 执行 num ++ ; // 线程 B 执行 System . out . print ( "num的值:" + num ) ;
所以需要保证线程的可见性
synchronized、lock和volatile能够保证线程可见性
java并发 并发编程 高并发 高并发编程
并发编程三要素学习。
以 count ++ 为例，至少需要三条 CPU 指令:
指令 1:首先，需要把变量 count 从内存加载到 CPU 的寄存器;
我们假设 count=0，如果线程 A 在指令 1 执行完后做线程切换，线程 A 和线程 B 按照下图的序列执行，那么我们会发现两个线程都执行了 count+=1 的操作，但是得到的结果不是我们期望的 2，而是 1。1.3 原子性操作 多线程环境下中，Java 只保证了基本数据类型的变量和赋值操作才是原子性的(注:在32位的JDK环境下，对64位数据的读取不是原子性操作*，如long、double )
synchronized 和锁都可以保证可见性。2.4 可见性规则是什么 可见性规则就是 Happens-Before 规则 。
Happens-Before 规则:
Happens-Before 约束了编译器的优化行为，虽允许编译器优化，但是要求编译器优化后一定遵守 Happens-Before 规则。2.5 Happens-Before 规则 程序的顺序性规则在一个线程中，按照程序顺序，前面的操作 Happens-Before 于后续的任意操作。
volatile 变量规则
传递性规则
// 这里x会是多少呢
② Happens-Before ③，满足规则2-volatile 变量规则。
① Happens-Before ③，满足规则3-传递性规则。
管程中锁的规则
线程 start() 规则
以双重检查代码为例:
上面的代码有问题，问题在 ② 操作上:经过优化后的执行路径是这样的:
将 M 的地址赋值给 instance 变量;
优化后会导致什么问题呢
如何解决双重检查问题
但这三种问题分别又是什么，接下来做一个解释说明。三要素 1、原子性 一个操作或者多个操作，要么全部执行并且执行的过程不会被任何因素打断 ，要么全部不执行!
这里说到了 不能被打断 ，如果使用 volatile 关键字修饰多个操作中的某个变量，当数据在其他线程被修改时，则会导致当前线程中的操作被打断，使得操作数据失效!假设上述的案例中，不存在原子性，那么将会出现以下问题:
但B账户中未增加1000块!
同样，在计算机世界中，也会出现类似的问题。
假如为一个 32 位的变量赋值过程不具备原子性的话，会发生什么后果
假若一个线程执行到这个语句时，暂且假设为一个 32 位的变量赋值包括两个过程:
为低 16 位赋值，
那么就可能发生一种情况:
1、当将低 16 位数值写入之后， 突然被中断 ，
那么读取到的就是错误的数据。
在java中，对 基本数据类型的变量的读取 和 赋值 操作是 原子性 操作。
即:这些操作不可被中断，要么执行，要么不执行。
【注意:】 对于 32位系统 的来说， long 类型数据和 double 类型数据，它们的读写并非原子性的。
对于 基本数据类型 byte,short,int,float,boolean,char 读写 是 原子 操作
【结论:】
如:x = 10;
变量之间相互赋值，不是原子操作;如:y = x;
变量进行计算操作也不是原子操作，如:x++;
1、先去主存中读取x的值至工作内存，这个操作是原子操作。
对于串行程序来说，可见性是不存在的。
在任何一个操作中修改了某个变量的值，后续的操作中都能读取这个变量值，并且是修改过的新值。
但在多线程的环境下，就不一定了。
之前就说过:
线程对数据的操作，都是将数据从主存中拷贝至自己的工作内存中进行操作，然后才将操作后的数据写回主存中。
这样看就会发现一个问题:
此时线程B也获取了主存中的变量数据，对其进行操作。
由于线程和线程之间，各自独立的工作内存并不可见，这种工作内存与主内存同步延迟现象就造成了可见性问题。
另外指令重排以及编译器优化也可能导致可见性问题，通过前面的分析，我们知道无论是编译器优化还是处理器优化的重排现象，在多线程环境下，确实会导致程序轮序执行的问题，从而也就导致可见性问题。3、有序性 程序执行的顺序按照代码的先后顺序执行。
对于单线程而言确实如此，但对于多线程环境，则可能出现乱序现象，因为程序编译成机器码指令后可能会出现 指令重排现象 ，重排后的指令与原指令的顺序未必一致。
【疑问:】 这里说到了 指令重排 ，那什么是指令重排
假设有下列一段代码:
int i = 0 ; boolean flag = false ; i = 1 ; //语句1 flag = true ; //语句2
它 不保证 程序中各个语句的执行先后顺序 同代码中的顺序 一致 ，
但是它会 保证 程序 最终执行结果 和 代码顺序 执行的结果是一致的。
比如上述代码在进行计算操作之前，计算机会通过指令重排，将其执行顺序做一定的优化，优化为:
int i = 0 ; i = 1 ; //语句1 boolean flag = false ; flag = true ; //语句2
下图为从源码到最终执行的指令序列示意图:
通常在Java中，可以采取 volatile 关键字来 保证一定的有序性 。
synchronized和Lock保证每个时刻，都只有一个线程执行代码块，将多线程转化为串行。
另外，Java内存模型(JMM)具备一些先天的有序性 ，即不需要通过任何手段就能够得到保证的有序性，这个通常也称为 happens-before 原则 。
如果两个操作的执行次序无法从happens-before原则推导出来 ，那么它们就不能保证它们的有序性 ，虚拟机 可以随意地对它们进行 重排序 。
【疑问:】为什么JMM具备先天的有序性
在JMM内存模型中，每个线程都有自己独立的工作内存。
(类似之前说的高速缓存)
线程对变量信息的所有操作，都是在各自独立的工作内存中完成的，
并且每个线程都不能访问其他线程的工作内存。happens-before 【疑问:】什么是 happens-before 原则
从 JDK 5 开始，Java使用新的 JSR-133内存模型 ，提供了 happens-before 原则 来 辅助保证 程序执行的原子性 、 可见性 以及 有序性的问题，它是判断数据是否存在竞争、线程是否安全的依据。
其中， happens-before 原则 内容如下( 先行发生原则 ):
2、 锁规则: 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前，
如果对于 一个锁 解锁后再加锁 ，那么 加锁的动作 必须在解锁动作之后 ( 同一个锁 )
volatile变量在每次被线程访问时，都强迫从主内存中读该变量的值。
当该变量发生变化时，又会强迫将最新的值刷新到主内存。
任何时刻，不同的线程总是能够看到该变量的最新值
如果线程A 在执行线程B的start方法之前 修改了 共享变量的值
那么 当线程B执行start方法时 ，
线程 A对共享变量的修改 对线程B可见
6、 线程终止规则: 线程的所有操作先于线程的终结
线程中所有的操作都先行发生于线程的终止检测，
可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
7、 线程中断规则: 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
8、 对象终结规则: 对象的构造函数执行，结束先于finalize()方法
如果操作之间 不存在数据依赖关系 ，这些操作就可能被编译器和处理器重排序 。参考资料 Java并发编程:volatile关键字解析volatile为什么不能保证原子性java 1、原子性 原子性指的是一个或者多个操作，要么全部执行并且在执行的过程中不被其他操作打断，要么就全部都不执行。
Java并发编程三要素 【原子性，可见性，有序性】 java 死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题，如果线程出现 了这三种情况，即线程不再活跃，不能再正常地执行下去了。