摘要
本文的主题是Java内存模型的可见性,主要解决以下几个问题:
1. 概念
1.1 什么可见性
可见性是指当一个线程修改了共享变量的值以后,其他线程可以立即得知这个修改。
1.2 什么是有序性
Java中的有序性在不加干预的情况下可以总结为:在线程中观察自身的操作是有序的(线程内表现为串行语义),在一个线程观察另一个线程所有的操作都是无序的(指令重排序和工作内存与主内存同步延迟)。线程之间的有序性需要我们通过一些措施来保证。
2. 指令重排序
指令重排序是CPU在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待,只需要保证最后能得出正确的结果。通过乱序执行的技术,处理器可以大大提高执行效率。
Java虚拟机的JIT即时编译器也会采用指令重排序的技术,即生成的机器指令与字节码指令顺序不一致。
3. 如何保证有序性
由于指令重排序和内存同步延迟的问题,Java中提供了volatile和synchronized关键字来保证线程间的有序性。这两个关键字我们放在后面单独讲(毕竟可见性、有序性都和他们有关)。
除了上述两个关键字,Java语言中有一个先行发生原则,这个原则是判断数据是否存在竞争,线程是否安全的主要依据。
先行原则是Java内存模型中定义两项操作的偏序关系,如果A操作先行发生于操作B,那么操作A产生的影响要能被操作B观察到,比如修改了内存变量的共享值。
3.1 先行发生原则
如果两个操作不在先行发生原则中,并且无法推导出来的话,这两个操作就没有顺序性保障,虚拟机可以对他们进行任意排序。
先行发生原则如下:
4. volatile关键字
volatile关键字在可见性和有序性有着关键性作用,但读者一定要记住一点:volatile关键字修饰的变量并不一定是线程安全的,他只保证变量被一个线程修改了以后,另一个线程可以立即观察到。
volatile关键字之所以能保证可见性、有序性,是因为Java内存模型对volatile修饰的变量使用有着以下规则:
上述两条规则保证了volatile变量的可见性。
上述规则确保了volatile变量不会被指令重排序优化。
5. synchronized
synchronized是Java开发最熟悉不过的用来保证线程安全的关键字,上面我们提到lock和unlock是两个原子性指令,但是这两个指令并未开放给用户使用,而是提供monitorenter和monitorexit字节码指令来隐式的使用这两个操作,这两个操作反映到Java语言层面便是synchronized关键字。synchronized块中的操作也具备原子性。
synchronized关键字也可以保证变量可见性,原因是:对一个变量的unlock操作之前,必须把此变量同步回主内存中(store和write操作)。
synchronized关键也可以保证有序性,原因是:一个变量在同一时刻只允许一条线程对其执行lock操作,这条规则决定了持有同一个锁的两个同步块只能串行执行。
本期的Java内存模型可见性-有序性介绍到这,我是shysh95,顺手关注+在看,我们下期再见!!!
往期推荐