1. 内存模型(JMM)
2. 内存间的交互操作
2.1. 定义操作
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态.
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定.
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用.
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中.
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到
变量的值的字节码指令时将会执行这个操作. - assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个
给变量赋值的字节码指令时执行这个操作. - store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用.
- write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中.
2.2. 约束条件
- 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操作)
2.3. volatile
假定T表示一个线程,V和W分别表示两个volatile型变量,那么在进行read、 load、 use、 assign、store和write操作时
需要满足如下规则:
只有当线程T对变量V执行的前一个动作是load的时候,线程T才能对变量V执行use动作;并且,只有当线程T对变量V执行的后一个动作是use
的时候,线程T才能对变量V执行load动作. 线程T对变量V的use动作可以认为是和线程T对变量V的load、 read动作相关联,必须连续一起出现
(这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改后的值).
只有当线程T对变量V执行的前一个动作是assign的时候,线程T才能对变量V执行store动作;并且,只有当线程T对变量V执行的后一个动作
是store的时候,线程T才能对变量V执行assign动作. 线程T对变量V的assign动作可以认为是和线程T对变量V的store、 write动作相关
联,必须连续一起出现(这条规则要求在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改)
假定动作A是线程T对变量V实施的use或assign动作,假定动作F是和动作A相关联的load或store动作,假定动作P是和动作F相应的对变量V
的read或write动作;类似的,假定动作B是线程T对变量W实施的use或assign动作,假定动作G是和动作B相关联的load或store动作,假定动作
Q是和动作G相应的对变量W的read或write动作. 如果A先于B,那么P先于Q(这条规则要求volatile修饰的变量不会被指令重排序优化,保证代
码的执行顺序与程序的顺序相同).
volatile不仅保证了共享变量的可见性,还通过内存屏障保证了代码执行顺序与程序顺序相同,通过内存屏障来使变量不被指令重排优化
2.4. long和double的非原子性协定
读写操作可分为2次32位操作,所以一定不是原子操作
注:现在商用虚拟机本身几乎都已经实现了原子操作,所以不用volatile修饰符
2.5 原子性
变量操作的read、load、use、assign、store、write不保证了原子性
通过synchronized对lock、unlock操作也保证了原子性
2.6 可见性
volatile保证在读取共享变量之前去主内存刷最新值,还保证了最新值能及时同步至主内存
2.7 有序性
在本线程内观察所有操作都是有序的,在另一个线程观察所有操作都是无序的
2.8 先发性
先发生的线程对修改了共享变量的值、发送了消息或调用了方法会被后发生的线程所观察到
2.9 先行发生(happens-before)规则
《JSR-133:Java Memory Model and Thread Specification》定义了如下happens-before规则。
- 1、程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 2、监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- 3、volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
- 4、传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
- 5、start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
- 6、join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
3. 常见比较
3.1 volatile与synchronized比较
- 1、被volatile描述的共享变量通过内存屏障是代码不被指令重排优化,从而保证了共享变量在线程间的可见性,更新变量时会及时
的将最新值同步至主内存,读变量时会及时的去主内存获取最新值,volatile只能修饰变量,只保证可见性,不保证原子性,不能用来
同步。 - 2、synchronized是通过对象头的锁标志位来实现加锁与释放锁,不仅保证可见性,还保证了原子性,只有获得了对象锁的线程才能
进入临界区,其他线程会阻塞等待锁释放后再争抢锁。
3.2 Lock(ReentrantLock)与synchronized比较
- 1、synchronized是JAVA关键字,Lock是一个java接口,ReentrantLock实现了该接口
- 2、Lock锁需要在finally{}代码里手动释放,synchronized会自动释放锁
- 3、Lock根据实现不同有多种锁类型,如公平锁、非公平锁(默认非公平锁)
- 4、性能方面,在锁竞争很大的情况下Lock性能更优于synchronized