【volatile关键字】

文章目录

    • volatile关键字
    • 可见性
    • 有序性
    • 小故事

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关键字能够提高多线程程序的可靠性和稳定性。

你可能感兴趣的:(#,Java基础知识点,java,jvm,算法)