一, 可见性, 原子性, 有序性(三大特性)
1. 发生背景
由于 CPU, 内存, io 设备的速度差异, 做了以下优化
CPU 增加了缓存, 以均衡与内存的速度差异
操作系统增加了进程, 线程, 以分时复用 CPU, 进而均衡 CPU 与 io 设备的速度差异
编译程序优化指令执行次序, 使得 CPU 缓存能够得到更加合理利用
2. 带来的问题(引出三大特性)
现实中的并发问题往往是三种问题的综合症
缓存导致的可见性问题
线程切换带来的原子性问题
原子性外在表现是不可分割, 本质是多个资源有一致性要求 , 保证中间状态对外不可见
编译优化, 指令优化带来的有序性问题
举例: 双重检查创建单例对象 可能存在问题, 所以要对 instance 进行 volatile 语义声明, 就可以禁止指令重排序
3. 解决办法
本质: 通常是按需禁用缓存以及编译优化, 来保证可见性和有序性
保证对共享变量的修改 是互斥的(同一时刻只有一个线程执行), 来保证原子性
通俗做法分三种:
(1) vollatile
volatile 强制所修饰的变量及它前边的变量刷新至内存, 并且 volatile 禁止了指令的重排序, 解决可见性和有序性问题
(2) synchronized
必须保证是同一把锁, 互斥, 本质上是保证串行执行
在解锁的时候, JVM 需要强制刷新缓存, 使得当前线程所修改的内存对其他线程可见
(3) final
当一个对象包含 final 修饰的实例字段时, 其他线程能够看到已经初始化的 final 实例字段, 这是安全的
二, java 内存模型(两大核心之一)
Java 内存模型定义了线程和内存的交互方式
在 JMM 抽象模型中, 分为主内存, 工作内存. 主内存是所有线程共享的, 工作内存是每个线程独有的. 线程对变量的所有操作 (读取, 赋值) 都必须在工作内存中进行, 不能直接读写主内存中的变量. 并且不同的线程之间无法访问对方工作内存中的变量, 线程间的变量值的传递都需要通过主内存来完成
在这里的工作内存特指物理内存, 是 CPU 的寄存器和高速缓存的抽象描述. 主内存相当于硬件的内存.
内存间的交互操作
Lock(锁定): 主内存变量锁定
Unlock(解锁): 主内存变量解锁
Read(读取): 主内存变量读取, 将值传给工作内存, 待 Load
Load(载入): 工作内存变量载入, 将 Read 读到的值, 存放到本地副本
Use(使用): 把工作内存的变量传递给执行引擎
Assign(赋值): 把从执行引擎接收到的的值赋值给工作内存变量
Store(存储): 把工作内存的变量值传递给主内存, 以便后续的 write 使用
Write(写入): 用于主内存变量, 把 store 获得的变量的值放入主内存变量
内存模型解决并发问题主要采用两种方式: 限制处理器优化和使用内存屏障
语义上, 内存屏障之前的所有写操作都要写入内存; 内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果. 因此, 对于敏感的程序块, 写操作之后, 读操作之前可以插入内存屏障.
三, happens-before 规则(两大核心之一)
本质 : 前面一个操作的结果对后续操作是可见的
程序的顺序性规则
单线程不会出现有序性问题, 原来是 happens-before 第一条规则限制住了编译器的优化
volatile 变量规则
写先于读指的是不会因为 CPU 缓存, 导致 a 线程已经写了, 但是 b 线程没读到的情况
管程中锁的规则
线程 start() 规则
线程 join() 规则
线程中断规则
对象终结规则
传递性(最重要的特性)
四, 分工, 同步, 互斥(构建体系中的三个核心问题)
来源: http://www.jianshu.com/p/7de5849dfa68