1. Overview
1.概述
In this tutorial, we’ll look at the differences between the methods set() and lazySet() of Java atomic classes like AtomicInteger and AtomicReference.
在本教程中,我们将了解Java 原子类(如AtomicInteger和AtomicReference)的方法set()和lazySet()之间的区别。
2. Atomic Variables – A Quick Recap
2.原子变量–快速回顾
Atomic variables in Java allow us to easily perform thread-safe operations on class references or fields without having to add concurrency primitives like monitors or mutexes.
Java中的原子变量使我们能够轻松地对类的引用或字段进行线程安全操作,而无需添加监视器或互斥等并发原语。
They’ve defined under the java.util.concurrent.atomic package, and although their APIs are different depending on the atomic type, most of them support the set() and lazySet() methods.
它们被定义在java.util.concurrent.atomic包下,尽管它们的API根据原子类型的不同而不同,但大多数都支持set()和lazySet() 方法。
To make things simple, we’ll use AtomicReference and AtomicInteger throughout this article, but the same principles apply to other atomic types.
为了简单起见,我们将在本文中使用AtomicReference和AtomicInteger,但同样的原则适用于其他原子类型。
3. The set() Method
3.set()方法
The set() method is equivalent to writing to a volatile field.
set()方法相当于向volatile字段写入。
After calling set(), when we access the field using the get() method from a different thread, the change is immediately visible. This means that the value was flushed from the CPU cache to a memory layer common to all CPU cores.
在调用set()后,当我们使用get()方法从不同的线程访问该字段时,该变化立即可见。这意味着该值被从CPU缓存中刷新到所有CPU内核共有的内存层。
To showcase the above functionality, let’s create a minimal producer-consumer console app:
为了展示上述功能,让我们创建一个最小的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();
}
}
In the console, we should see a series of “Set” and “Get” messages:
在控制台,我们应该看到一系列的 “设置 “和 “获取 “信息。
Set: 3
Set: 4
Get: 4
Get: 5
What indicates cache coherence is the fact that values in the “Get” statements are always equal to or greater than those in the “Set” statements above them.
表明缓存一致性的是,”Get “语句中的值总是等于或大于其上方的 “Set “语句中的值。 。
This behavior, although very useful, comes with a performance hit. It’d be great if we could avoid it in cases where we don’t need the cache coherence.
这种行为虽然非常有用,但也会带来性能上的影响。如果我们能在不需要缓存一致性的情况下避免它,那就太好了。
4. The lazySet() Method
4.lazySet()方法
The lazySet() method is the same as the set() method but without the cache flushing.
lazySet() 方法与set()方法相同,但没有缓存刷新。
In other words, our changes are only eventually visible to other threads. This means that calling get() on the updated AtomicReference from a different thread might give us the old value.
换句话说,我们的改变最终只对其他线程可见。这意味着从不同的线程对更新的AtomicReference调用get()可能会给我们带来旧值。
To see this in action, let’s change the first thread’s Runnable in our previous console app:
为了看看这个动作,让我们在之前的控制台应用程序中改变第一个线程的Runnable。
for (int i = 0; i < 10; i++) {
app.atomic.lazySet(i);
System.out.println("Set: " + i);
Thread.sleep(100);
}
The new “Set” and “Get” messages might not always be incrementing:
新的 “设置 “和 “获取 “信息可能不总是递增的。
Set: 4
Set: 5
Get: 4
Get: 5
Due to the nature of threads, we might need a few re-runs of the app in order to trigger this behavior. The fact that the consumer thread retrieves the value 4 first even though the producer thread has set the AtomicInteger to 5 means that the system is eventually consistent when lazySet() is used.
由于线程的特性,我们可能需要重新运行几次应用程序才能触发这种行为。尽管生产者线程已经将AtomicInteger设置为5,但消费者线程还是先检索到了值4,这意味着在使用lazySet()时,系统最终是一致的。
In more technical terms, we say that lazySet() methods don’t act as happens-before edges in the code, in contrast to their set() counterparts.
在更多的技术术语中,我们说lazySet()方法在代码中不作为发生前的边,与它们的set()对应方法相反。
5. When to Use lazySet()
5.何时使用lazySet()?
It’s not immediately clear when we should use lazySet() since its differences with set() are subtle. We need to carefully analyze the problem, not only to make sure that we’ll get a performance boost but also to ensure correctness in a multi-threaded environment.
我们并不清楚什么时候应该使用lazySet(),因为它与set()的区别很微妙。我们需要仔细分析这个问题,不仅要确保我们会得到性能提升,还要确保在多线程环境下的正确性。
One way we can use it is to replace an object reference with null once we no longer need it. This way, we indicate that the object is eligible for garbage collection without incurring any performance penalties. We assume that the other threads can work with the deprecated value until they see the AtomicReference is null.
我们可以使用的一种方式是,一旦我们不再需要某个对象的引用,就将其替换为null。这样,我们表明该对象有资格进行垃圾回收,而不会产生任何性能损失。我们假设其他线程可以使用废弃的值,直到他们看到AtomicReference是null。
Generally, though, we should use lazySet() when we want to make a change to an atomic variable, and we know that the change doesn’t need to become immediately visible to other threads.
一般来说,当我们想对一个原子变量进行修改,并且我们知道这个修改不需要立即对其他线程可见时,我们应该使用lazySet()。
6. Conclusion
6.结语
In this article, we had a look at the differences between set() and lazySet() methods of atomic classes. We also learned when to use which method.
在这篇文章中,我们看了原子类的set()和lazySet()方法之间的区别。我们还学习了何时使用哪种方法。
As always, the source code for the examples can be found over on GitHub.
一如既往,这些例子的源代码可以在GitHub上找到。