查看原文
其他

Java 原子变量中set()和lazySet()的区别

脚本之家 2022-09-23

The following article is from Java技术指北 Author 指北君

 关注脚本之家”,与百万开发者在一起

来源 | Java技术指北 (ID:javanorth)
已获得原公众号的授权转载

在本教程中,我们将讲讲 Java atomic 类(如 AtomicIntegerAtomicReference )的方法 set()lazySet() 之间的区别。

原子变量

Java中的原子变量使我们能够轻松地对类的引用或字段进行线程安全的操作,而不需要添加监视器或互斥等并发原语。

它们被定义在 java.util.concurrent.atomic 包下,虽然它们的API根据原子类型的不同而不同,但大多数都支持set()lazySet()方法。

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

3.The set() 方法

在调用set()后,当我们从不同的线程使用get()方法访问该字段时,该变化是立即可见的。这意味着该值被从CPU缓存中刷新到了所有CPU核共有的内存层。为了展示上述功能,让我们创建一个最小的 producer-consumer 控制台应用。

    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: 3
    Set: 4
    Get: 4
    Get: 5

表明缓存一致性的是,"Get "语句中的值总是等于或大于其上方的 "Set "语句中的值。。

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

The 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: 4
Set: 5
Get: 4
Get: 5

由于线程的特性,我们可能需要重新运行几次应用程序,以便触发这种行为。尽管生产者线程已经将AtomicInteger设置为5,但消费者线程还是先检索到了值4,这意味着当lazySet()被使用时,系统最终是一致的。

在更多的技术术语中,我们说lazySet()方法在代码中不作为发生在前的边,与它们的set()对应的方法相反。

什么时候使用lazySet()

我们并不清楚什么时候应该使用lazySet(),因为它与set()的区别很微妙。我们需要仔细分析这个问题,不仅要确保我们会得到性能上的提升,还要确保在多线程环境下的正确性。

我们可以使用的一种方式是,一旦我们不再需要一个对象的引用,就用null替换它。这样,我们表明该对象有资格进行垃圾回收,而不会产生任何性能上的损失。我们假设其他线程可以使用废弃的值,直到他们看到AtomicReferencenull。不过一般来说,我们应该使用lazySet(),当我们想对一个原子变量进行修改,而且我们知道这个修改不需要立即对其他线程可见。

总结

在这篇文章中,我们看了原子类的set()lazySet()方法之间的区别。我们还学习了何时使用哪种方法。

<END>

程序员专属T恤

商品直购链接 👇

  推荐阅读:

这是一件程序员才懂的T恤

4年经验,去面了波高级 Java 工程师
7 段小代码,玩转Java程序常见的崩溃场景!

阿里云二面:简单聊聊 Java 虚拟机栈!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存