Java volatile关键字解读

111 阅读6分钟

Java中的volatile关键字可以保证多线程之间的可见性和有序性,也就是说当一个变量被声明为volatile时,它的值的变化可以被其他线程立即感知,同时对该变量的读写操作会按照程序的代码顺序依次执行,而不会被编译器或CPU重排。

volatile关键字的实现原理主要涉及到三个方面:可见性、有序性和禁止指令重排。

  1. 可见性:当一个变量被声明为volatile时,每个线程都会从主内存中读取该变量的最新值,而不是从线程自己的本地缓存中读取。当一个线程修改了volatile变量的值,它会立即刷新到主内存中,而其他线程也会立即感知到该变量的变化。
  2. 有序性:对volatile变量的写操作会在修改之后立即将修改后的值刷新到主内存中,而对volatile变量的读操作也会从主内存中读取该变量的最新值。这种机制可以保证volatile变量的读写操作不会被编译器或CPU重排,从而保证了程序的执行顺序。
  3. 禁止指令重排:编译器和CPU都会对程序进行指令重排优化,这种优化可以提高程序的性能。但是对于volatile变量,编译器和CPU会限制重排操作,以保证程序的正确性。具体来说,当一个变量被声明为volatile时,编译器和CPU会禁止在该变量周围的代码中进行指令重排,以保证程序的执行顺序和正确性。

在操作系统层面,volatile关键字的实现依赖于CPU和内存的硬件支持。具体来说,当一个变量被声明为volatile时,CPU会在修改该变量的值时,向内存发出写入请求,并在写入完成后发送一个“屏障”(也称“内存栅栏”)信号,该信号可以保证前面的写操作对其他线程可见。类似地,当一个线程读取volatile变量的值时,CPU会向内存发出读取请求,并在读取完成后发送一个“屏障”信号,该信号可以保证后面的读操作按照程序顺序执行。这些硬件层面的支持,使得Java中的volatile关键字可以在不同平台上实现相同的语义。

在Java中,volatile关键字的具体实现是通过在编译器和虚拟机中生成特定的指令来实现的。在Java虚拟机中,每个线程都会有一个线程工作内存,用于存储线程私有的本地变量和缓存数据。当一个变量被声明为volatile时,该变量的读写操作会被编译器和虚拟机转换为特定的指令,这些指令会在执行时向内存发送读取和写入请求,以保证volatile变量的可见性、有序性和禁止指令重排。

具体来说,在Java虚拟机中,volatile关键字的实现依赖于以下两种指令:

  1. lock指令:这是一种原子性的指令,可以用于保证对内存的原子操作。当一个变量被声明为volatile时,它的读写操作会被编译器转换为lock指令,该指令会向CPU发出一个请求,要求在执行该指令时锁定内存总线,以保证该指令执行时其他线程无法访问内存。这样可以保证volatile变量的读写操作是原子性的,并且可以保证其他线程在执行该指令时无法访问该变量,从而保证了volatile变量的可见性。
  2. memory barrier指令:这是一种内存屏障指令,可以用于保证程序的执行顺序。当一个变量被声明为volatile时,编译器会在该变量周围插入memory barrier指令,以保证该变量的读写操作不会被编译器和CPU重排。具体来说,当一个线程执行到memory barrier指令时,它会将本地缓存中的所有数据刷新到主内存中,并且会清空所有在该指令之前的读写操作的缓存,从而保证程序的执行顺序。

以上就是Java中volatile关键字的实现原理和源码解析,涵盖了从语言层面到操作系统层面的实现细节。虽然volatile关键字的实现原理比较复杂,但是它在多线程编程中的应用非常广泛,可以帮助开发者解决很多并发编程中的问题。因此,对于Java开发人员来说,掌握volatile关键字的使用和实现原理是非常重要的。

附:lock指令的实现是锁总线么

在早期的处理器中,lock指令确实是通过锁定内存总线来实现的,但是随着处理器的发展,现代处理器采用了更加高效的方式来实现lock指令。

具体来说,现代处理器通常使用缓存一致性协议来实现lock指令。缓存一致性协议是处理器使用的一种机制,用于保证不同CPU缓存中的相同内存数据的一致性。当一个CPU想要读取或写入内存时,缓存一致性协议会检查该内存地址是否被其他CPU缓存中的数据所占用,如果是,则会将该内存地址的缓存状态设置为“共享”,表示该内存数据已被共享,并且会向其他CPU发送信号,要求它们将该内存地址的缓存状态设置为“无效”,以保证内存数据的一致性。

在这种情况下,当一个CPU执行lock指令时,它会向其他CPU发送信号,要求它们释放对该内存地址的缓存占用,从而保证该指令执行时其他CPU无法访问该内存地址,从而实现了锁的效果。

总之,现代处理器使用缓存一致性协议来实现lock指令,而不是通过锁定内存总线。这种方式比锁定内存总线更加高效,因为锁定内存总线会导致所有CPU的内存访问都被阻塞,而使用缓存一致性协议可以最小化锁的范围,只锁定访问的内存地址,从而减少锁的开销,提高程序的执行效率。