1. volatile
关键字的描述及作用
在 Java 里,volatile
是一种类型修饰符,主要用来修饰变量。它具备以下两方面的作用:
保证可见性
在多线程环境中,每个线程都有自己的工作内存,变量会被拷贝到工作内存中进行操作。当一个变量被声明为 volatile
时,意味着该变量会直接从主内存读取,并且每次对它的修改都会立即刷新到主内存。这样一来,其他线程就能马上看到该变量的最新值。
下面是一个简单示例:
class VolatileExample {
// 声明为 volatile 的变量
volatile boolean flag = false;
public void writer() {
// 修改 volatile 变量
flag = true;
}
public void reader() {
while (!flag) {
// 等待 flag 变为 true
}
System.out.println("Flag is now true");
}
}
在上述代码中,flag
被声明为 volatile
。当 writer
方法修改 flag
的值时,reader
方法能立刻看到这个变化。
禁止指令重排序
指令重排序是指编译器和处理器为了提高性能,会对指令进行重新排序。不过,volatile
关键字能禁止指令重排序,保证代码的执行顺序和编写顺序一致。
2. volatile
与 synchronized
关键字的区别
功能层面
volatile
:主要保证变量的可见性和禁止指令重排序,但不能保证原子性。例如,对volatile
变量进行自增操作(i++
)就不是原子的,因为它包含读取、加 1 和写入三个操作。synchronized
:不仅能保证变量的可见性,还能保证代码块或方法在同一时刻只能被一个线程访问,也就是保证了原子性。
性能层面
volatile
:使用成本较低,因为它不会引起线程的阻塞。它只是简单地保证对变量的读写操作是直接在主内存中进行的。synchronized
:使用成本较高,因为它涉及到线程的阻塞和唤醒,会带来上下文切换的开销。
使用场景层面
volatile
:适用于一个变量被多个线程读取,而只有一个线程进行写操作的场景,或者是对变量的操作本身就是原子的情况。synchronized
:适用于多个线程对共享资源进行读写操作,需要保证操作的原子性和可见性的场景。
下面是一个对比两者的示例:
class Counter {
// 使用 volatile 修饰的变量
volatile int volatileCount = 0;
// 普通变量
int synchronizedCount = 0;
public void incrementVolatile() {
// 非原子操作
volatileCount++;
}
public synchronized void incrementSynchronized() {
// 原子操作
synchronizedCount++;
}
}
在这个示例中,incrementVolatile
方法对 volatileCount
的操作不是原子的,而 incrementSynchronized
方法对 synchronizedCount
的操作是原子的。