Java 中的 volatile 关键字具体什么时候使用?

发布于 2024-09-14 18:48:24 字数 236 浏览 4 评论 0原文

我已经读过“何时在 Java 中使用'易失性'?”,但我仍然使困惑。我怎么知道什么时候应该将变量标记为易失性?如果我弄错了,或者在需要它的东西上省略了易失性,或者在不需要的东西上放置了易失性怎么办?在确定多线程代码中哪些变量应该是易失性的时,经验法则是什么?

I have read "When to use 'volatile' in Java?" but I'm still confused. How do I know when I should mark a variable volatile? What if I get it wrong, either omitting a volatile on something that needs it or putting volatile on something that doesn't? What are the rules of thumb when figuring out what variables should be volatile in multithreaded code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

九公里浅绿 2024-09-21 18:48:24

基本上,当您想让一个成员变量被多个线程访问但不需要复合原子性(不确定这是否是正确的术语)时,您基本上可以使用它。

class BadExample {
    private volatile int counter;

    public void hit(){
        /* This operation is in fact two operations:
         * 1) int tmp = this.counter;
         * 2) this.counter = tmp + 1;
         * and is thus broken (counter becomes fewer
         * than the accurate amount).
         */
        counter++;
    }
}

上面是一个糟糕的例子,因为您需要复合原子性。

 class BadExampleFixed {
    private int counter;

    public synchronized void hit(){
        /*
         * Only one thread performs action (1), (2) at a time
         * "atomically", in the sense that other threads can not 
         * observe the intermediate state between (1) and (2).
         * Therefore, the counter will be accurate.
         */
        counter++;
    }
}

现在来看一个有效的例子:

 class GoodExample {
    private static volatile int temperature;

    //Called by some other thread than main
    public static void todaysTemperature(int temp){
        // This operation is a single operation, so you 
        // do not need compound atomicity
        temperature = temp;
    }

    public static void main(String[] args) throws Exception{
        while(true){
           Thread.sleep(2000);
           System.out.println("Today's temperature is "+temperature);
        }
    }
}

现在,为什么不能只使用private static int temp?事实上你可以(从某种意义上说,你的程序不会崩溃或其他什么),但另一个线程对 温度 的更改可能对主线程“可见”,也可能不“可见”。

基本上这意味着您的应用程序甚至有可能。如果您使用易失性,则会永远写入今天的温度为0(实际上,该值往往会最终变得可见。但是,您不应冒险在必要时不使用 volatile,因为它可能会导致严重的错误(由不完全构造的对象等引起)

。易失性,它不会影响代码的正确性(即行为不会改变),这取决于 JVM 实现,理论上您可能会出现微小的性能下降,因为编译器不能。不进行重新排序优化,必须使 CPU 缓存无效等,但编译器可以再次证明您的字段永远不能被多个线程访问,并完全删除 易失性 关键字的影响并将其编译为相同的说明

编辑:
对此评论的回应:

好的,但是为什么我们不能使 TodaysTemperature 同步并创建一个同步的温度 getter?

你可以并且它会正确运行。任何可以用 volatile 完成的事情都可以用 synchronized 完成,但反之则不然。如果可以的话,您可能更喜欢 易失性 有两个原因:

  1. 不易出现错误:这取决于上下文,但在许多情况下使用 易失性 不太容易出现并发错误,例如持有锁时发生阻塞、死锁等。
  2. 性能更高:在大多数 JVM 实现中,易失性 可以具有显着更高的吞吐量和更好的延迟。然而,在大多数应用中,差异太小,不重要。

You basically use it when you want to let a member variable be accessed by multiple threads but do not need compound atomicity (not sure if this is the right terminology).

class BadExample {
    private volatile int counter;

    public void hit(){
        /* This operation is in fact two operations:
         * 1) int tmp = this.counter;
         * 2) this.counter = tmp + 1;
         * and is thus broken (counter becomes fewer
         * than the accurate amount).
         */
        counter++;
    }
}

the above is a bad example, because you need compound atomicity.

 class BadExampleFixed {
    private int counter;

    public synchronized void hit(){
        /*
         * Only one thread performs action (1), (2) at a time
         * "atomically", in the sense that other threads can not 
         * observe the intermediate state between (1) and (2).
         * Therefore, the counter will be accurate.
         */
        counter++;
    }
}

Now to a valid example:

 class GoodExample {
    private static volatile int temperature;

    //Called by some other thread than main
    public static void todaysTemperature(int temp){
        // This operation is a single operation, so you 
        // do not need compound atomicity
        temperature = temp;
    }

    public static void main(String[] args) throws Exception{
        while(true){
           Thread.sleep(2000);
           System.out.println("Today's temperature is "+temperature);
        }
    }
}

Now, why can't you just use private static int temperature? In fact you can (in the sense that that your program won't blow up or something), but the change to temperature by the other thread may or may not be "visible" to the main thread.

Basically this means that it is even possible that your app. keeps writing Today's temperature is 0 forever if you don't use volatile (in practice, the value tends to become eventually visible. However, you should not risk not using volatile when necessary, since it can lead to nasty bugs (caused by in-completely constructed objects etc.).

If you put volatile keyword on something that doesn't need volatile, it won't affect your code's correctness (i.e. the behaviour will not change). In terms of performance, it will depend on the JVM implementation. In theory you might get a tiny performance degradation because the compiler can't do reordering optimisations, have to invalidate CPU cache etc., but then again the compiler could prove that your field cannot ever be accessed by multiple threads and remove the effect of volatile keyword completely and compile it to identical instructions.

EDIT:
Response to this comment:

Ok, but why can't we make todaysTemperature synchronized and create a synchronized getter for temperature?

You can and it will behave correctly. Anything that you can with volatile can be done with synchronized, but not vice versa. There are two reasons you might prefer volatile if you can:

  1. Less bug prone: This depends on the context, but in many cases using volatile is less prone to concurrency bugs, like blocking while holding the lock, deadlocks etc.
  2. More performant: In most JVM implementations, volatile can have significantly higher throughput and better latency. However in most applications the difference is too small to matter.
攒一口袋星星 2024-09-21 18:48:24

易失性在无锁算法中最有用。当您不使用锁定来访问该变量并且您希望一个线程所做的更改在另一个线程中可见,或者您希望创建一个“发生后”关系以确保计算是再次,不重新排序,以确保更改在适当的时间可见。

JMM Cookbook 描述了哪些操作可以重新排序,哪些操作不能。

Volatile is most useful in lock-free algorithms. You mark the variable holding shared data as volatile when you are not using locking to access that variable and you want changes made by one thread to be visible in another, or you want to create a "happens-after" relation to ensure that computation is not re-ordered, again, to ensure changes become visible at the appropriate time.

The JMM Cookbook describes which operations can be re-ordered and which cannot.

泪是无色的血 2024-09-21 18:48:24

volatile 关键字保证 volatile 变量的值始终从主内存读取,而不是从线程的本地缓存读取。

来自 java 并发 教程

使用易失性变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会与同一变量的后续读取建立发生前关系

这意味着对易失性变量的更改始终对其他线程可见。这还意味着,当线程读取 volatile 变量时,它不仅会看到 volatile 的最新更改,还会看到导致更改的代码的副作用。

关于您的询问:

我怎么知道什么时候应该将变量标记为易失性?在确定多线程代码中哪些变量应该是易失性的时,经验法则是什么?

如果您认为所有读取器线程总是获取变量的最新值,则必须将变量标记为易失性

如果您有一个写入器线程来修改变量的值,并且有多个读取器线程来读取变量的值,易失性修饰符保证了内存的一致性。

如果您有多个线程来写入和读取变量,则仅使用 volatile 修饰符并不能保证内存一致性。您必须同步代码或使用高级并发结构,如并发集合原子变量等。

相关SE问题/文章:

Java 文档中的易失性变量解释

Java 中 volatile 和 synchronized 的区别

java 重访 文章

volatile keyword guarantees that value of the volatile variable will always be read from main memory and not from Thread's local cache.

From java concurrency tutorial :

Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable

This means that changes to a volatile variable are always visible to other threads. It also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

Regarding your query:

How do I know when I should mark a variable volatile? What are the rules of thumb when figuring out what variables should be volatile in multithreaded code?

If you feel that all reader threads always get latest value of a variable, you have to mark variable as volatile

If you have one writer thread to modify the value of variable and multiple reader threads to read the value of variable, volatile modifier guarantees memory consistency.

If you have multiple threads to write and read variables, volatile modifier alone does not guaranty memory consistency. You have to synchronize the code or use high level concurrency constructs like Locks, Concurrent Collections, Atomic variables etc.

Related SE questions/articles:

Volatile variable explanation in Java docs

Difference between volatile and synchronized in Java

javarevisited article

つ可否回来 2024-09-21 18:48:24

易失性还可以用于在多线程环境中安全地发布不可变对象。

声明像 public volatile ImmutableObject foo 这样的字段可以确保所有线程始终看到当前可用的实例引用。

有关该主题的更多信息,请参阅 Java 并发实践

The volatile can also be used to safely publish immutable objects in a multi-threaded Environment.

Declaring a field like public volatile ImmutableObject foo secures that all threads always see the currently available instance reference.

See Java Concurrency in Practice for more on that topic.

少跟Wǒ拽 2024-09-21 18:48:24

实际上不同意票数最高的答案中给出的示例,据我所知,它确实没有按照 Java 内存模型正确地说明了易失性语义。易失性具有更复杂的语义。

在提供的示例中,主线程可以继续永远打印“今天的温度为 0”,即使有另一个正在运行的线程应该更新温度(如果该其他线程从未被调度)。

说明易失性语义的更好方法是使用 2 个变量。

为了简单起见,我们假设更新这两个变量的唯一方法是通过方法“setTemperatures”

为了简单起见,我们假设只有 2 个线程正在运行,即主线程和线程 2。

//volatile variable
private static volatile int temperature; 
//any other variable, could be volatile or not volatile doesnt matter.
private static int yesterdaysTemperature
//Called by other thread(s)
public static void setTemperatures(int temp, int yestemp){
    //thread updates yesterday's temperature
    yesterdaysTemperature = yestemp;
    //thread updates today's temperature. 
    //This instruction can NOT be moved above the previous instruction for optimization.
    temperature = temp;
   }

编译器、运行时或硬件不能出于优化目的对最后两个赋值指令重新排序。

public static void main(String[] args) throws Exception{
    while(true){
       Thread.sleep(2000);
       System.out.println("Today's temperature is "+temperature); 
       System.out.println("Yesterday's temperature was "+yesterdaysTemperature );
 }
}

一旦主线程读取了易失性变量温度(在打印它的过程中),

1)可以保证无论有多少个线程,它都会看到该易失性变量的最近写入值写入它,无论他们使用哪种方法更新它,同步与否。

2) 如果主线程中的system.out语句运行,线程2运行语句温度= temp的时刻之后,将保证打印昨天的温度和今天的温度当线程 2 运行语句Temperature=temp 时,对其进行设置。

如果 a) 多个线程正在运行,并且 b) 除了 setTemperatures 方法之外,还有其他方法可以更新这些变量主动调用的变量昨天的温度和今天的温度,则这种情况会变得更加复杂。其他线程。我认为需要一篇相当大的文章来分析基于 Java 内存模型如何描述易失性语义的含义。

简而言之,尝试仅使用 volatile 进行同步是极其危险的,您最好坚持同步您的方法。

Actually disagree with the example given in the top voted answer, to my knowledge it does NOT properly illustrate volatile semantics as per the Java memory model. Volatile has way more complex semantics.

In the example provided, the main thread could continue to print "Today's temperature is 0" forever even if there is another thread running that is supposed to update the temperature if that other thread never gets scheduled.

A better way to illustrate volatile semantics is with 2 variables.

For simplicity's sake, we will assume that the only way to update the two variables is through the method "setTemperatures".

For simplicity's sake, we will assume that only 2 threads are running, main thread and thread 2.

//volatile variable
private static volatile int temperature; 
//any other variable, could be volatile or not volatile doesnt matter.
private static int yesterdaysTemperature
//Called by other thread(s)
public static void setTemperatures(int temp, int yestemp){
    //thread updates yesterday's temperature
    yesterdaysTemperature = yestemp;
    //thread updates today's temperature. 
    //This instruction can NOT be moved above the previous instruction for optimization.
    temperature = temp;
   }

the last two assignment instructions can NOT be reordered for optimization purposes by either the compiler, runtime or the hardware.

public static void main(String[] args) throws Exception{
    while(true){
       Thread.sleep(2000);
       System.out.println("Today's temperature is "+temperature); 
       System.out.println("Yesterday's temperature was "+yesterdaysTemperature );
 }
}

Once the main thread reads the volatile variable temperature (in the process of printing it),

1) There is a guarantee that it will see the most recently written value of this volatile variable regardless of how many threads are writing to it, regardless of which method they are updating it in, synchronized or not.

2) If the system.out statement in the main thread runs, after the time instant at which thread 2 has run the statement temperature = temp, both yesterday's temperature and todays temperature will be guaranteed to print the values set in them by thread 2 when it ran the statement temperature=temp.

This situation gets a LOT more complex if a) Multiple threads are running and b) There are other methods than just the setTemperatures method that can update the variable yesterday's temperature and todays temperature that are actively being called by these other threads. I think it would take a decent size article to analyze the implications based on how the Java Memory Model describes the volatile semantics.

In short, attempting to just use volatile for synchronization is extremely risky, and you would be better off sticking to synchronizing your methods.

终难遇 2024-09-21 18:48:24

http://mindprod.com/jgloss/volatile.html

" volatile 关键字用于可能被其他线程同时修改的变量。”

“由于其他线程无法看到局部变量,因此无需将局部变量标记为 volatile。您需要使用同步来协调不同线程对变量的更改,但通常 volatile 只需要查看它们即可。”

http://mindprod.com/jgloss/volatile.html

"The volatile keyword is used on variables that may be modified simultaneously by other threads."

"Since other threads cannot see local variables, there is never any need to mark local variables volatile. You need synchronized to co-ordinate changes to variables from different threads, but often volatile will do just to look at them."

眼趣 2024-09-21 18:48:24

voltalie 意味着不断改变值。该变量的值永远不会在线程本地缓存:所有读取和写入都将直接进入“主内存”。换句话说,Java 编译器和线程不会缓存该变量的值并始终读取它来自主存储器。

voltalie Means Keep changing value.The value of this variable will never be cached thread-locally: all reads and writes will go straight to "main memory".In other words Java compiler and Thread that do not cache value of this variable and always read it from main memory.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文