在Java并发编程的领域中,有一种看似简单却蕴藏深意的关键字------volatile
。它如同一把钥匙,打开了数据一致性与线程间通信的大门。但volatile
并非全能,尤其是在原子性面前,它有着自己的局限性。本文将带你穿越迷宫,探索volatile
的真谛及其与原子性的关系。
Volatile的面纱:可见性与有序性
Volatile 关键字的核心作用在于确保变量的可见性 与有序性 。所谓可见性,指的是当一个线程修改了某个volatile
变量后,其他线程能够立即看到这一变化。有序性则保证了指令不会被处理器或编译器重排序,从而避免了某些类型的竞争条件。
示例代码:
java
public class VolatileVisibility {
public static volatile boolean ready = false;
public static int number = 0;
public static void main(String[] args) throws InterruptedException {
Thread writerThread = new Thread(() -> {
number = 42;
ready = true;
});
Thread readerThread = new Thread(() -> {
while (!ready) {
Thread.yield();
}
System.out.println(number); // 应该输出42
});
writerThread.start();
readerThread.start();
writerThread.join();
readerThread.join();
}
}
在这个例子中,如果没有volatile
修饰符,number
的更新可能不会被readerThread
及时发现,导致输出错误的结果。但加上volatile
后,readerThread
将能够正确读取到writerThread
写入的新值。
原子性的迷雾:Volatile的盲区
虽然volatile
能提供可见性和有序性,但它并不能保证原子性。原子性意味着一个操作要么完全执行,要么完全不执行,不会被中断。例如,考虑下面的代码片段:
示例代码:
java
public class VolatileNonAtomicity {
public static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 这个操作不是原子的
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("Expected: 10000, Actual: " + count);
}
}
尽管count
是volatile
的,但count++
操作并非原子的,因此多个线程同时进行加法操作时,可能会得到比预期少的结果。这是因为count++
实际上是由读取、计算、写回三个步骤组成的,而在多线程环境下,这些步骤可能被不同的线程交错执行。
原子性的解决方案:Java并发类库
为了弥补volatile
在原子性方面的不足,Java提供了java.util.concurrent.atomic 包,其中包含了一系列原子类,如AtomicInteger
、AtomicLong
等,它们利用内部的锁或其他同步机制来保证原子性。
示例代码:
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicSafety {
public static AtomicInteger atomicCount = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
atomicCount.incrementAndGet(); // 原子性操作
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("Expected: 10000, Actual: " + atomicCount.get());
}
}
在这个版本中,atomicCount
使用AtomicInteger
,其incrementAndGet()
方法是原子性的,从而保证了所有线程操作的完整性,输出结果将始终等于预期。