Contents

Java原子变量中的set和lazyset之间的差异

1. 概述

在本教程中,我们将研究AtomicIntegerAtomicReference等Java原子 类的*set()*和 *lazySet()*方法之间的区别。

2. 原子变量——快速回顾

Java 中的原子变量使我们能够轻松地对类引用或字段执行线程安全操作,而无需添加诸如监视器或互斥锁之类的并发原语。 它们在java.util.concurrent.atomic包下定义,虽然它们的 API 因原子类型而异,但它们中的大多数都支持*set()lazySet()*方法。

为简单起见,我们将在本文中使用AtomicReferenceAtomicInteger,但同样的原则也适用于其他原子类型。

3. *set()*方法

**set()方法等效于写入volatile 字段

调用*set()后,当我们从不同的线程使用get()*方法访问该字段时,更改立即可见。这意味着该值已从 CPU 缓存刷新到所有 CPU 内核共有的内存层。

为了展示上述功能,让我们创建一个最小的生产者-消费者 控制台应用程序:

public class Application {
    AtomicInteger atomic = new AtomicInteger(0);
    public static void main(String[] args) {
        Application app = new Application();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                app.atomic.set(i);
                System.out.println("Set: " + i);
                Thread.sleep(100);
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                synchronized (app.atomic) {
                    int counter = app.atomic.get();
                    System.out.println("Get: " + counter);
                }
                Thread.sleep(100);
            }
        }).start();
    }
}

在控制台中,我们应该看到一系列“Set”和“Get”消息:

Set: 3
Set: 4
Get: 4
Get: 5

表明缓存一致性 的事实是“Get”语句中的值总是等于或大于它们上面的“Set”语句中的值。

这种行为虽然非常有用,但会影响性能。如果我们可以在不需要缓存一致性的情况下避免它,那就太好了。

4. *lazySet()*方法

*lazySet()方法与set()*方法相同,但没有缓存刷新。

*换句话说,我们的更改最终只对其他线程可见。这意味着  从不同线程对更新的AtomicReference调用*get()可能会给我们旧值。

要查看实际情况,让我们在之前的控制台应用程序中更改第一个线程的Runnable

for (int i = 0; i < 10; i++) {
    app.atomic.lazySet(i);
    System.out.println("Set: " + i);
    Thread.sleep(100);
}

新的“Set”和“Get”消息可能并不总是递增:

Set: 4
Set: 5
Get: 4
Get: 5

由于线程的性质,我们可能需要重新运行应用程序才能触发此行为。即使生产者线程已将AtomicInteger设置为 5,消费者线程首先检索值 4 的事实意味着在使用*lazySet()*时系统最终是一致 的。

用更专业的术语来说,我们说*lazySet()*方法不充当代码中的happens-before 边缘,与其对应的*set()*方法相反。

5. 何时使用lazySet()

我们何时应该使用*lazySet()尚不清楚,因为它与set()*的区别很微妙。我们需要仔细分析问题,不仅要确保我们将获得性能提升,还要确保在多线程环境中的正确性。

**我们可以使用它的一种方法是在不再需要对象引用时将其替换为null。**这样,我们就表明该对象有资格进行垃圾回收,而不会产生任何性能损失。我们假设其他线程可以使用已弃用的值,直到他们看到AtomicReferencenull

但是,一般来说,当我们想要对原子变量进行更改时,我们应该使用lazySet(),并且我们知道更改不需要立即对其他线程可见。