ZetCode

Java volatile

上次修改时间:2024 年 7 月 16 日

在本文中,我们定义了 Java volatile 关键字,并展示了如何使用它。

Java 中的 volatile 关键字是一种变量修饰符,它告诉 Java 虚拟机 (JVM) 一个变量可以被多个线程访问和修改。

volatile 关键字解决了多线程程序中的可见性问题。它确保对变量所做的更改能立即对其他线程可见。

volatile 变量的值始终从主内存读取和写入到主内存。这可以防止线程看到缓存在 CPU 寄存器或缓存中的过期值。这对于多线程编程至关重要,因为多个线程可能同时访问同一个变量。换句话说,它确保每次读取 volatile 变量都直接从计算机的主内存读取(而不是从 CPU 缓存读取),并且每次写入 volatile 变量都写入到主内存(不仅仅是写入到 CPU 寄存器)。

要点

Happens-Before 关系

Java 内存模型 (JMM) 中的一个关键概念是“happens-before”。这定义了所有线程必须看到的操作顺序。 对 volatile 变量的写入建立了与对同一变量的所有后续读取的 happens-before 关系。这意味着在 volatile 写入之前对其他变量所做的任何更改,在写入之后,对读取 volatile 变量的线程变得可见。

标志示例

在下一个示例中,我们有一个 worker,它会一直运行直到标志设置为 false。

Main.java
class Worker implements Runnable {

    private volatile boolean isRunning = false;

    public void setRunning() {
        isRunning = true;
    }

    public void stopTask() {
        isRunning = false;
    }

    public boolean isRunning() {
        return isRunning;
    }

    @Override
    public void run() {

        System.out.println("worker started");

        while (isRunning) {

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println("doing task " + System.currentTimeMillis());
        }

        System.out.println(isRunning);
        System.out.println("worker ended");
    }
}


void main() throws InterruptedException {

    System.out.println("Main thread started");
    final var worker = new Worker();
    worker.setRunning();

    // Thread to start the task
    var starter = new Thread(worker);
    starter.start();

    Thread.sleep(2000);

    // Thread to stop the task using the flag
    var stopper = new Thread(() -> {

        if (worker.isRunning()) {
            worker.stopTask();
            System.out.println("stopping task");
        }
    });

    stopper.start();

    starter.join();
//    stopper.join();

    System.out.println("Main thread ended");
}

isRunning 标志控制 Worker 类中 run 方法的执行。while 循环持续检查 isRunning 的值。 如果另一个线程修改了 isRunning 的值(例如,通过调用 stopTask),则该更改将立即对执行循环的线程可见。

如果没有 volatile,不同的线程可能拥有 isRunning 的自己的本地副本,从而导致数据不一致。

在我们的例子中,只有可见性很重要。 如果我们还需要确保操作的原子性,我们需要选择同步方法或其他工具来代替。

来源

线程和锁 - Java 语言规范

在本文中,我们使用了 Java 中的 volatile 关键字。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程