volatile关键字
volatile是Java中的关键字,它用于修饰变量,可以保证多个线程修改该变量时的可见性和有序性。下面我们来详细介绍一下volatile关键字。
可见性
当一个线程修改了一个volatile变量的值,其他线程能够立即看到这个变化。这是因为volatile变量会被强制从主内存中读取和写入,而不是从线程的本地内存中读取和写入。举个例子,我们来看一个计数器:
public class VolatileDemo { private volatile int counter = 0; public void increment() { counter++; } public int getCounter() { return counter; } }
在这个计数器中,我们使用了volatile修饰变量counter,当一个线程调用increment()方法时,它会增加counter的值,其他线程可以通过调用getCounter()方法获取到修改后的值。
public class VolatileTest { public static void main(String[] args) throws InterruptedException { final VolatileDemo demo = new VolatileDemo(); Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) { demo.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) { demo.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Counter: " + demo.getCounter()); } }
在这个测试中,我们创建了两个线程分别对计数器进行10000次累加,最终输出的结果应该是20000。如果不使用volatile修饰变量counter,那么我们无法保证counter的可见性,最终输出的结果可能小于20000。
有序性
volatile变量的修改会被强制立即写入主内存,线程读取变量时也会强制从主内存中读取变量的最新值,因此可以保证volatile变量的有序性。举个例子,我们来看一个生产者消费者模型:
public class VolatileDemo { private volatile boolean flag = false; private int data; public void produce(int data) { while (flag) { // 等待消费者取走数据 } this.data = data; flag = true; } public int consume() { while (!flag) { // 等待生产者产生数据 } int result = this.data; flag = false; return result; } }
在这个模型中,我们使用了volatile修饰变量flag,生产者在生产数据时会将数据写入data变量,然后将flag设置为true;消费者在消费数据时会先等待flag变成true,然后读取data变量的值,最后将flag设置为false。
public class VolatileTest { public static void main(String[] args) throws InterruptedException { final VolatileDemo demo = new VolatileDemo(); Thread t1 = new Thread(() -> { for (int i = 1; i <= 10; i++) { demo.produce(i); System.out.println("Producer produce: " + i); } }); Thread t2 = new Thread(() -> { for (int i = 1; i <= 10; i++) { int data = demo.consume(); System.out.println("Consumer consume: " + data); } }); t1.start(); t2.start(); t1.join(); t2.join(); } }
在这个测试中,我们创建了一个生产者线程和一个消费者线程,生产者会生产1到10的数字,消费者会消费这些数字。由于flag变量被volatile修饰,因此我们可以保证生产者和消费者对flag变量的读写是有序的,即先设置值再读取值,从而避免了数据不一致的问题。
总之,volatile关键字用于保证多个线程之间某些变量的可见性和有序性,从而避免了数据竞争和一些隐藏的bug。但是,它并不能保证所有的线程安全问题,例如原子性问题。因此,在需要保证线程安全的代码中,我们需要使用更加强大的锁和同步机制。
小故事
有一个故事是这样的:
有两个小孩在抢玩具,他们会先问对方玩具是否“空闲”,然后才能抢到玩具。如果其中一个小孩没有使用volatile关键字,那么他在查询玩具是否空闲的时候,可能会因为缓存的原因得到一个错误的结果,导致他抢到了被占用的玩具,造成了混乱。但如果他使用了volatile关键字,那么他的查询结果会及时更新到主存中,保证了他拿到的玩具一定是空闲的,不会造成混乱。
这个故事可以帮助理解volatile关键字的底层工作原理:当我们将一个变量定义为volatile类型时,编译器会强制将该变量的读写操作直接从主存中进行,而不是从缓存中进行。这样能保证不同线程之间读取的数据是最新的,避免了由于缓存不一致导致的数据错误。因此,使用volatile关键字能够提高多线程程序的可靠性和稳定性。