在Java并发编程中,synchronized
和volatile
关键字是实现内存可见性和同步的关键工具,它们都是基于Java内存模型(Java Memory Model, JMM)的规定来保证多线程环境下的数据一致性。
synchronized
关键字
synchronized
关键字用于确保线程之间对共享资源的访问是互斥的,它可以应用于方法或者代码块。以下是其内存语义和可见性特点:
-
互斥性:同一时刻只允许一个线程访问被
synchronized
修饰的代码块或方法,阻止了并发访问带来的数据不一致性问题。 -
可见性:当线程A退出一个
synchronized
块或方法时,对共享变量所做的更改对随后进入该synchronized
块或方法的任何线程(包括线程A本身)都是可见的。这是因为退出synchronized
区域时会执行内存屏障操作,确保本地缓存的数据刷新到主内存。 -
原子性:对于简单的基本类型变量的读写操作(如int、long等),在
synchronized
代码块中通常是原子的。但对于复合操作(如++、–等),则需要整个操作都在synchronized
块中才能保证原子性。 -
内存语义:
synchronized
会确保“先行发生原则”(Happens-Before)的成立,即对一个监视器锁的解锁操作先行发生于随后对同一个锁的加锁操作。
volatile
关键字
volatile
关键字主要用来保证变量的可见性和一定程度的有序性,但不提供互斥性:
-
可见性:当一个线程修改了
volatile
变量的值时,其他线程总是能看到最新值,因为它强制从主内存读取变量的值,而不是使用寄存器或高速缓存中的副本。对volatile
变量的写入会flush掉线程的工作内存,并且会使其他线程load该变量时从主内存中读取。 -
禁止指令重排序:
volatile
关键字还能防止编译器和处理器对指令进行重排序优化,确保代码的执行顺序与程序员的书写顺序一致,尤其是在多线程环境中有重要意义。 -
不保证原子性:虽然
volatile
变量的读写是原子的,但是对于复合操作(例如递增、递减等)来说,并不是一个原子操作,因此无法保证线程安全性。
在JUC源码分析中,可以看到synchronized
和volatile
在很多并发类和工具类中起到了至关重要的作用,如AtomicInteger
、CountDownLatch
等内部都利用了volatile
关键字来实现高效的无锁同步。同时,JVM在底层实现上,会依据Java内存模型规范,为synchronized
和volatile
生成相应的内存屏障指令,确保并发控制的有效性。