文章目录
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关键字能够提高多线程程序的可靠性和稳定性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?