JMM与并发三大特性
一、JMM与原子性
Java中对基本数据类型变量的读取赋值都是原子性的,对引用类型变量的读取和赋值也是原子性。这类操作都是不可被中断的,要么执行,要么不执行。
(1) 赋值操作:x = 10;
操作是原子性。
(2) 赋值操作:y = x;
操作是非原子性。将变量x赋值给y,包含两个重要步骤:
1)执行线程从主存中读取x的值,然后将其存入当前线程的工作内存
2)在执行线程的工作内存中修改y的值为x,然后将其写入主存
两个步骤都是原子类型操作,但组合在一起就不是了
(3) 自增操作:y++;
操作是非原子性:它包含了三个重要步骤:
1)执行线程从主存中读取y的值,将其存入当前线程的工作内存
2)在执行线程工作内存中为y执行加1操作
3)将y值写回主存
- 多个原子性操作合在一起就不再是原子性操作
- 简单的读取与赋值操作是原子性操作,但将一个变量赋值给另一个变量的操作是非原子性操作
- JMM只保证基本读取和赋值的原子性操作,其他的均不保证
二、JMM与可见性
多线程环境下,某线程首次读取共享变量,首先到主存中获取,存入工作内存。如果对该变量执行修改操作,则将新值写入工作内存,再刷新到主存。但是这一刷新的时间是不确定的,其他线程不知道该线程的修改操作,因此Java提供三种方式保障可见性:
- 使用volatile关键字,该关键字实现基础为MESI缓存一致性协议
- 通过synchornized关键字
- 通过JUC提供的显示锁
三、JMM与有序性
Java内存模型中,允许编译器和处理器对指令进行重排序,多线程下重排序会影响程序的正确运行,Java提供volatile、synchornized和显示锁三种方式保证有序性。
3.1 happens-before原则
除三种方式之外,JMM本身存在有序性规则,这个规则不需要任何同步手段就能保证有序性,这个规则称为happens-before原则。若两个操作不满足happens-before原则以及由它推导出的规则,那么这两个操作就没有顺序保障,JVM或处理器可以对它们任意排序:
- 程序次序规则:在一个线程内,按照代码次序执行,编写在后面的操作发生于编写在前面的操作之后。
这一条看似是说程序按照次序执行,但虚拟机、处理器还是会对指令进行重排序,只能保障程序执行的结果与顺序执行的结果一致。多线程下无法保障正确性。
- 锁定规则:一个unLock操作要先行发生于后面对同一个锁的lock操作。
这条规则意思是,无论单线程还是多线程环境下,一个锁出于被锁定状态,那么必须先释放锁再加锁。
- volatile变量规则:对一个变量的写操作要先行发生于后面对这个变量的读操作。
这条规则意思是,如果一个变量是volatile修饰的,一个线程对它进行读操作,一个线程对它进行写操作,那么写入操作肯定要先行发生于读操作。这条规则标志了线程间的可见性。
- 传递规则:如果操作A先于操作B,而B由先于操作C。则肯定可以得到A先于C。
这条规则体现了happens-before原则具有传递性。
- 线程启动规则:Thread对象的start( )方法先行发生于对该线程的任何动作。
只有start( )之后线程才真正运行,否则Thread也只是一个对象。
- 线程中断规则:对interrupt( )方法的调用先行发生于被中断线程捕获到中断信号。
这条规则是指,如果线程收到中断信号,那么在此之前势必调用了interrupt( )方法。
- 线程终结规则:线程中所有的操作都要先行发生于线程的终止检测。
这条规则意思是,线程任务的执行、逻辑单元执行肯定要先发生于线程死亡之前。
- 对象的终结规则:一个对象初始化的完成先行发生于finalize( )方法之前。即先生后死。