并发编程二:JMM的三大特性
1.原子性
指一个操作是不可中断的,即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰,要么全成功,要么全失败。
Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行(高低位是分开赋值),即 load、store、read 和 write 操作可以不具备原子性。
2.可见性
指在多线程环境下,当一个线程修改了共享内存中某一个共享变量的值,其他线程是否能够立即知道这个修改。
显然,对于串行程序来说(单线程环境),可见性问题是不存在。因为你在任何一个操作步骤中修改某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值。
但是这个问题在并行程序中就不见得了。如果一个线程修改了某一个全局变量,那么其他线程未必可以马上知道这个改动。
主要有有三种实现可见性的方式:
- volatile,会强制将该变量自己和当时其他变量的状态都刷出缓存
- synchronized,对一个变量执行
- unlock, 操作之前,必须把变量值同步回主内存
- final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this
逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值
3有序性
对于一个线程的执行代码而言,我们总是习惯地认为代码的执行时从先往后,依次执行的。这样的理解也不能说完全错误,因为就一个线程而言,确实会这样。
但是在并发时,程序的执行可能就会出现乱序。给人直观的感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排(happen-before),重排后的指令与原指令的顺序未必一致。
指令重排(happen-before):是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序.
指令重排(happen-before)的表现:
- 代码的执行顺序
- unlock必须发生在lock之后
- volatile修饰的变量,写操作要优先于读操作
- 传递规则,操作a先于b,b先于c,那么a肯定先于c
- 线程的启动规则,start方法先于线程的其他任意操作
- 线程的中断规则,interrupt动作,必须发生在捕获改动作之前
- 对象的销毁规则,初始化必须发生在finalize之前
- 线程终结规则,所有的操作都要发生在线程死亡之前