JMM( Java Memory Model )
什么是JMM:
因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。
JMM特征
-
原子性: 操作不可分割的最小单元。
-
可见性: 当某个线程修改完某个变量之后,在其他的线程中,可以观察到该变量已经被修改。
-
有序性: Java的有序性跟线程相关。如果在线程内部观察,会发现当前线程的一切操作都是有序的。如果在线程的外部来观察的话,会发现线程的所有操作都是无序的。
Happen-Before(先行发生规则)
在常规的开发中,如果我们通过上述规则来分析一个并发程序是否安全,估计脑壳会很疼。因为更多时候,我们是分析一个并发程序是否安全,其实都依赖Happen-Before原则进行分析。Happen-Before被翻译成先行发生原则,意思就是当A操作先行发生于B操作,则在发生B操作的时候,操作A产生的影响能被B观察到,“影响”包括修改了内存中的共享变量的值、发送了消息、调用了方法等。
Happen-Before的规则有以下几条:
- 程序次序规则(Program Order Rule):在一个线程内,程序的执行规则跟程序的书写规则是一致的,从上往下执行。
- 管程锁定规则(Monitor Lock Rule):一个Unlock的操作肯定先于下一次Lock的操作。这里必须是同一个锁。同理我们可以认为在synchronized同步同一个锁的时候,锁内先行执行的代码,对后续同步该锁的线程来说是完全可见的。
- volatile变量规则(volatile Variable Rule):对同一个volatile的变量,先行发生的写操作,肯定早于后续发生的读操作
- 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的没一个动作
- 线程中止规则(Thread Termination Rule):Thread对象的中止检测(如:Thread.join(),Thread.isAlive()等)操作,必行晚于线程中所有操作
- 线程中断规则(Thread Interruption Rule):对线程的interruption()调用,先于被调用的线程检测中断事件(Thread.interrupted())的发生
- 对象中止规则(Finalizer Rule):一个对象的初始化方法先于一个方法执行Finalizer()方法
- 传递性(Transitivity):如果操作A先于操作B、操作B先于操作C,则操作A先于操作C
valatile 和 synchronized区别
- volatile仅能使用在变量上,synchronized可以使用在变量和方法上;
- volatile仅能实现变量的可见性,不能保证原子性,synchronized可以保证变量的可见性和原子性;
- volatile不会造成线程阻塞,synchronized可能会造成线程阻塞(因为volatile只是将当前变量的值及时告知所有线程,而synchronized是锁定当前变量不让其它线程访问);
- volatile标记的变量不会被编译器优化(因为不能指令重排),synchronized标记的变量可以被编译器优化;
- volatile修饰变量适合于一写多读的并发场景,而多写场景一定会产生线程安全问题(因此使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域)。
- 因为所有的操作都需要同步给内存变量,所以volatile一定会使线程的执行速度变慢。