volatile 关键字有什么用?

发布于 2024-07-05 22:22:03 字数 255 浏览 6 评论 0原文

今天在工作中,我遇到了Java中的易失性关键字。 不太熟悉它,我发现 这个解释

鉴于该文章详细解释了相关关键字,您是否曾经使用过它,或者您是否见过可以以正确方式使用该关键字的情况?

At work today, I came across the volatile keyword in Java. Not being very familiar with it, I found this explanation.

Given the detail in which that article explains the keyword in question, do you ever use it or could you ever see a case in which you could use this keyword in the correct manner?

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

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

发布评论

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

评论(25

一枫情书 2024-07-12 22:22:05

是的,我经常使用它 - 它对于多线程代码非常有用。 你提到的那篇文章是一篇很好的文章。 尽管有两件重要的事情需要记住:

  1. 如果你
    完全理解它的作用
    以及它与同步有何不同。
    在许多情况下会出现波动,
    从表面上看,要更简单一些
    性能替代
    同步,通常更好
    对 volatility 的理解会让
    明确同步是唯一的
    可行的选项。
  2. 挥发性实际上并不能在
    尽管有很多较旧的 JVM
    同步的。 我记得看到过一个文档,其中引用了不同 JVM 中的各种级别的支持,但不幸的是我现在找不到它。 如果您使用的是 Java 1.5 之前的版本,或者您无法控制程序将在其上运行的 JVM,那么一定要研究一下它。

Yes, I use it quite a lot - it can be very useful for multi-threaded code. The article you pointed to is a good one. Though there are two important things to bear in mind:

  1. You should only use volatile if you
    completely understand what it does
    and how it differs to synchronized.
    In many situations volatile appears,
    on the surface, to be a simpler more
    performant alternative to
    synchronized, when often a better
    understanding of volatile would make
    clear that synchronized is the only
    option that would work.
  2. volatile doesn't actually work in a
    lot of older JVMs, although
    synchronized does. I remember seeing a document that referenced the various levels of support in different JVMs but unfortunately I can't find it now. Definitely look into it if you're using Java pre 1.5 or if you don't have control over the JVMs that your program will be running on.
抚你发端 2024-07-12 22:22:05

虽然我在这里提到的答案中看到了许多很好的理论解释,但我在这里添加了一个实际示例和解释:

1.

代码运行而不使用易失性使用

public class VisibilityDemonstration {

private static int sCount = 0;

public static void main(String[] args) {
    new Consumer().start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        return;
    }
    new Producer().start();
}

static class Consumer extends Thread {
    @Override
    public void run() {
        int localValue = -1;
        while (true) {
            if (localValue != sCount) {
                System.out.println("Consumer: detected count change " + sCount);
                localValue = sCount;
            }
            if (sCount >= 5) {
                break;
            }
        }
        System.out.println("Consumer: terminating");
    }
}

static class Producer extends Thread {
    @Override
    public void run() {
        while (sCount < 5) {
            int localValue = sCount;
            localValue++;
            System.out.println("Producer: incrementing count to " + localValue);
            sCount = localValue;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
        System.out.println("Producer: terminating");
    }
}
}

在上面的代码中,有两个线程 - Producer 和消费者。

生产者线程在循环上迭代 5 次(其间睡眠 1000 毫秒或 1 秒)。 在每次迭代中,生产者线程将 sCount 变量的值增加 1。因此,生产者在所有迭代中将 sCount 的值从 0 更改为 5。

消费者线程处于恒定循环中,每当 sCount 的值发生变化时就会打印,直到值达到 5 时结束。

两个循环同时启动。 所以生产者和消费者都应该打印 sCount 的值 5 次。

OUTPUT

Consumer: detected count change 0
Producer: incrementing count to 1
Producer: incrementing count to 2
Producer: incrementing count to 3
Producer: incrementing count to 4
Producer: incrementing count to 5
Producer: terminating

ANALYSIS

在上面的程序中,当生产者线程更新 sCount 的值时,它确实更新了主内存(每个线程所在的内存)中变量的值将首先读取变量的值)。 但使用者线程仅在第一次从该主内存中读取 sCount 的值,然后将该变量的值缓存在其自己的内存中。 因此,即使主内存中原始 sCount 的值已被生产者线程更新,消费者线程也会从其未更新的缓存值中读取。 这称为可见性问题

2.

使用 VOLATILE USE 运行代码

在上面的代码中,将声明 sCount 的代码行替换为以下代码:

private volatile  static int sCount = 0;

OUTPUT

Consumer: detected count change 0
Producer: incrementing count to 1
Consumer: detected count change 1
Producer: incrementing count to 2
Consumer: detected count change 2
Producer: incrementing count to 3
Consumer: detected count change 3
Producer: incrementing count to 4
Consumer: detected count change 4
Producer: incrementing count to 5
Consumer: detected count change 5
Consumer: terminating
Producer: terminating

ANALYSIS

当我们声明一个变量为 volatile,这意味着对该变量或从该变量的所有读取和写入都将直接进入主内存。 这些变量的值永远不会被缓存。

由于 sCount 变量的值永远不会被任何线程缓存,因此消费者始终从主内存中读取 sCount 的原始值(其中由生产者线程更新)。 因此,在这种情况下,两个线程打印 sCount 的不同值 5 次,输出是正确的。

这样, volatile 关键字就解决了 VISIBILITY PROBLEM

While I see many good Theoretical explanations in the answers mentioned here, I am adding a practical example with an explanation here:

1.

CODE RUN WITHOUT VOLATILE USE

public class VisibilityDemonstration {

private static int sCount = 0;

public static void main(String[] args) {
    new Consumer().start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        return;
    }
    new Producer().start();
}

static class Consumer extends Thread {
    @Override
    public void run() {
        int localValue = -1;
        while (true) {
            if (localValue != sCount) {
                System.out.println("Consumer: detected count change " + sCount);
                localValue = sCount;
            }
            if (sCount >= 5) {
                break;
            }
        }
        System.out.println("Consumer: terminating");
    }
}

static class Producer extends Thread {
    @Override
    public void run() {
        while (sCount < 5) {
            int localValue = sCount;
            localValue++;
            System.out.println("Producer: incrementing count to " + localValue);
            sCount = localValue;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
        System.out.println("Producer: terminating");
    }
}
}

In the above code, there are two threads - Producer and Consumer.

The producer thread iterates over the loop 5 times (with a sleep of 1000 milliSecond or 1 Sec) in between. In every iteration, the producer thread increases the value of sCount variable by 1. So, the producer changes the value of sCount from 0 to 5 in all iterations

The consumer thread is in a constant loop and print whenever the value of sCount changes until the value reaches 5 where it ends.

Both the loops are started at the same time. So both the producer and consumer should print the value of sCount 5 times.

OUTPUT

Consumer: detected count change 0
Producer: incrementing count to 1
Producer: incrementing count to 2
Producer: incrementing count to 3
Producer: incrementing count to 4
Producer: incrementing count to 5
Producer: terminating

ANALYSIS

In the above program, when the producer thread updates the value of sCount, it does update the value of the variable in the main memory(memory from where every thread is going to initially read the value of variable). But the consumer thread reads the value of sCount only the first time from this main memory and then caches the value of that variable inside its own memory. So, even if the value of original sCount in main memory has been updated by the producer thread, the consumer thread is reading from its cached value which is not updated. This is called VISIBILITY PROBLEM .

2.

CODE RUN WITH VOLATILE USE

In the above code, replace the line of code where sCount is declared by the following :

private volatile  static int sCount = 0;

OUTPUT

Consumer: detected count change 0
Producer: incrementing count to 1
Consumer: detected count change 1
Producer: incrementing count to 2
Consumer: detected count change 2
Producer: incrementing count to 3
Consumer: detected count change 3
Producer: incrementing count to 4
Consumer: detected count change 4
Producer: incrementing count to 5
Consumer: detected count change 5
Consumer: terminating
Producer: terminating

ANALYSIS

When we declare a variable volatile, it means that all reads and all writes to this variable or from this variable will go directly into the main memory. The values of these variables will never be cached.

As the value of the sCount variable is never cached by any thread, the consumer always reads the original value of sCount from the main memory(where it is being updated by producer thread). So, In this case the output is correct where both the threads prints the different values of sCount 5 times.

In this way, the volatile keyword solves the VISIBILITY PROBLEM .

德意的啸 2024-07-12 22:22:05

绝对没错。 (不仅在 Java 中,在 C# 中也是如此。)有时,您需要获取或设置一个保证在给定平台上是原子操作的值,例如 int 或 boolean,但不要求线程锁定的开销。 易失性关键字允许您确保在读取值时获得当前值,而不是刚刚因另一个线程上的写入而过时的缓存值。

Absolutely, yes. (And not just in Java, but also in C#.) There are times when you need to get or set a value that is guaranteed to be an atomic operation on your given platform, an int or boolean, for example, but do not require the overhead of thread locking. The volatile keyword allows you to ensure that when you read the value that you get the current value and not a cached value that was just made obsolete by a write on another thread.

volatile 关键字有两种不同的用法。

  1. 阻止 JVM 从寄存器(假设为缓存)读取值,并强制从内存读取其值。
  2. 降低内存不一致错误的风险。

阻止 JVM 读取寄存器中的值,并强制其
要从内存中读取的值。

忙标志用于防止线程在设备忙且该标志不受锁保护时继续:

while (busy) {
    /* do something else */
}

当另一个线程关闭忙标志时,测试线程将继续< /em>:

busy = 0;

但是,由于在测试线程中频繁访问busy,因此JVM可以通过将busy的值放在寄存器中来优化测试,然后测试寄存器的内容,而不是在每次测试之前读取内存中的busy值。 测试线程永远不会看到 busy 变化,而另一个线程只会更改内存中 busy 的值,从而导致死锁。 将忙标志声明为易失性会强制在每次测试之前读取其值。

降低内存一致性错误的风险。

使用易失性变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会建立一个
“发生在之前”与同一变量的后续读取的关系。 这意味着对易失性变量的更改始终对其他线程可见。

没有内存一致性错误的读取、写入技术称为原子操作

原子动作是指一次有效地发生的动作。 原子动作不能中途停止:它要么完全发生,要么根本不发生。 在操作完成之前,原子操作的副作用是不可见的。

以下是您可以指定的原子操作:

  • 对于引用变量和大多数情况,读取和写入都是原子的
    原始变量(除 long 和 double 之外的所有类型)。
  • 对于所有声明为易失性的变量来说,读取和写入都是原子的
    (包括长整型和双精度变量)。

干杯!

There are two different uses of volatile keyword.

  1. Prevents JVM from reading values from register (assume as cache), and forces its value to be read from memory.
  2. Reduces the risk of memory in-consistency errors.

Prevents JVM from reading values in register, and forces its
value to be read from memory.

A busy flag is used to prevent a thread from continuing while the device is busy and the flag is not protected by a lock:

while (busy) {
    /* do something else */
}

The testing thread will continue when another thread turns off the busy flag:

busy = 0;

However, since busy is accessed frequently in the testing thread, the JVM may optimize the test by placing the value of busy in a register, then test the contents of the register without reading the value of busy in memory before every test. The testing thread would never see busy change and the other thread would only change the value of busy in memory, resulting in deadlock. Declaring the busy flag as volatile forces its value to be read before each test.

Reduces the risk of memory consistency errors.

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.

The technique of reading, writing without memory consistency errors is called atomic action.

An atomic action is one that effectively happens all at once. An atomic action cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side effects of an atomic action are visible until the action is complete.

Below are actions you can specify that are atomic:

  • Reads and writes are atomic for reference variables and for most
    primitive variables (all types except long and double).
  • Reads and writes are atomic for all variables declared volatile
    (including long and double variables).

Cheers!

微凉 2024-07-12 22:22:05

挥发性如下。

1> 不同线程对易失性变量的读写总是从内存中进行,而不是从线程自己的缓存或cpu寄存器中进行。 所以每个线程总是处理最新的值。
2> 当两个不同的线程使用堆中的相同实例或静态变量时,一个线程可能会认为其他线程的操作是无序的。 请参阅杰里米·曼森的博客了解这一点。 但挥发性在这里有帮助。

以下完全运行的代码显示了多个线程如何按预定义的顺序执行并打印输出,而无需使用同步关键字。

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

为了实现这一点,我们可以使用以下完整的运行代码。

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

以下 github 链接有一个自述文件,其中给出了正确的解释。

Volatile does following.

1> Read and write of volatile variables by different threads are always from memory, not from thread's own cache or cpu register. So each thread always deals with the latest value.
2> When 2 different threads work with same instance or static variables in heap, one may see other's actions as out of order. See jeremy manson's blog on this. But volatile helps here.

Following fully running code shows how a number of threads can execute in predefined order and print outputs without using synchronized keyword.

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

To achieve this we may use the following full fledged running code.

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

The following github link has a readme, which gives proper explanation.
https://github.com/sankar4git/volatile_thread_ordering

白衬杉格子梦 2024-07-12 22:22:05

从oracle文档页面来看,需要 volatile 变量修复内存一致性问题:

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

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

正如 Peter Parker 的回答中所解释的,在没有 volatile 修饰符的情况下,每个线程的堆栈可能有自己的变量副本。 通过将变量设为易失性,内存一致性问题已得到解决。

查看 jenkov 教程页面以更好地理解。

查看相关的 SE 问题,了解有关 volatility 和 volatile 的更多详细信息。 使用 易失性的用例:

Java 中 易失性 和同步之间的区别

一个实际用例:

您有许多线程,需要以特定格式打印当前时间,例如:java.text.SimpleDateFormat("HH-mm-ss")。 您可以有一个类,它将当前时间转换为 SimpleDateFormat 并每隔一秒更新一次变量。 所有其他线程都可以简单地使用此易失性变量在日志文件中打印当前时间。

From oracle documentation page, the need for volatile variable arises to fix memory consistency issues:

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.

As explained in Peter Parker answer, in absence of volatile modifier, each thread's stack may have their own copy of variable. By making the variable as volatile, memory consistency issues have been fixed.

Have a look at jenkov tutorial page for better understanding.

Have a look at related SE question for some more details on volatile & use cases to use volatile:

Difference between volatile and synchronized in Java

One practical use case:

You have many threads, which need to print current time in a particular format for example : java.text.SimpleDateFormat("HH-mm-ss"). Yon can have one class, which converts current time into SimpleDateFormat and updated the variable for every one second. All other threads can simply use this volatile variable to print current time in log files.

苦妄 2024-07-12 22:22:05

如果您有一个多线程系统,并且这些多个线程处理某些共享数据,那么这些线程将在自己的缓存中加载数据。 如果我们不锁定资源,则一个线程中所做的任何更改都不会在另一个线程中可用。

输入图片description here

通过锁定机制,我们添加对数据源的读/写访问权限。 如果一个线程修改了数据源,该数据将存储在主内存中,而不是缓存中。 当其他线程需要这些数据时,它们会从主存中读取它。 这将显着增加延迟。

为了减少延迟,我们将变量声明为易失性。 这意味着每当任何处理器中修改变量的值时,其他线程都将被迫读取它。 它仍然有一些延迟,但比从主内存读取要好。

If you have a multithread system and these multiple threads work on some shared data, those threads will load data in their own cache. If we do not lock the resource, any change made in one thread is NOT gonna be available in another thread.

enter image description here

With a locking mechanism, we add read/write access to the data source. If one thread modifies the data source, that data will be stored in the main memory instead of in its cache. When others threads need this data, they will read it from the main memory. This will increase the latency dramatically.

To reduce the latency, we declare variables as volatile. It means that whenever the value of the variable is modified in any of the processors, the other threads will be forced to read it. It still has some delays but better than reading from the main memory.

春庭雪 2024-07-12 22:22:05

易失性变量是轻量级同步。 当需要所有线程之间最新数据的可见性并且原子性可能受到损害时,在这种情况下必须首选易失性变量。 对易失性变量的读取始终返回任何线程完成的最新写入,因为它们既没有缓存在寄存器中,也没有缓存在其他处理器看不到的缓存中。 易失性是无锁的。 当场景满足上述标准时,我使用 volatile。

Volatile Variables are light-weight synchronization. When visibility of latest data among all threads is requirement and atomicity can be compromised , in such situations Volatile Variables must be preferred. Read on volatile variables always return most recent write done by any thread since they are neither cached in registers nor in caches where other processors can not see. Volatile is Lock-Free. I use volatile, when scenario meets criteria as mentioned above.

无人问我粥可暖 2024-07-12 22:22:05

易失性变量基本上用于在主共享缓存行更新后立即更新(刷新),以便更改立即反映到所有工作线程。

volatile variable is basically used for instant update (flush) in main shared cache line once it updated, so that changes reflected to all worker threads immediately.

一笑百媚生 2024-07-12 22:22:05

下面是一个非常简单的代码,演示了对用于控制其他线程的线程执行的变量的 易失性 的要求(这是需要 易失性 的一种场景)。

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

易失性未使用时:即使在“停止于:xxx<”之后,您也永远不会看到“停止于:xxx”消息/em>',程序继续运行。

Stopping on: 1895303906650500

使用易失性时:您将立即看到“停止于:xxx”。

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

演示:https://repl.it/repls/SilverAgonizingObjectcode

Below is a very simple code to demonstrate the requirement of volatile for variable which is used to control the Thread execution from other thread (this is one scenario where volatile is required).

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

When volatile is not used: you'll never see 'Stopped on: xxx' message even after 'Stopping on: xxx', and the program continues to run.

Stopping on: 1895303906650500

When volatile used: you'll see the 'Stopped on: xxx' immediately.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

Demo: https://repl.it/repls/SilverAgonizingObjectcode

留一抹残留的笑 2024-07-12 22:22:05

与变量一起使用时,易失性键将确保读取该变量的线程将看到相同的值。 现在,如果您有多个线程读取和写入一个变量,仅使该变量为易失性是不够的,并且数据将被损坏。 图像线程读取了相同的值,但每个线程都做了一些更改(例如增加了计数器),当写回内存时,数据完整性被破坏。 这就是为什么有必要使变量同步(可以采用不同的方式)。

如果更改由 1 个线程完成,而其他线程只需要读取该值,则 volatile 将是合适的。

The volatile key when used with a variable, will make sure that threads reading this variable will see the same value . Now if you have multiple threads reading and writing to a variable, making the variable volatile will not be enough and data will be corrupted . Image threads have read the same value but each one has done some chages (say incremented a counter) , when writing back to the memory, data integrity is violated . That is why it is necessary to make the varible synchronized (diffrent ways are possible)

If the changes are done by 1 thread and the others need just to read this value, the volatile will be suitable.

汐鸠 2024-07-12 22:22:03

在我看来,除了停止使用 volatile 关键字的线程之外,还有两个重要的场景是

  1. :锁定机制。 常用于单例设计
    图案。 在此,单例对象需要声明为易失性
  2. 虚假唤醒。 即使没有发出通知调用,线程有时也可能从等待调用中唤醒。 这种行为称为虚假唤醒。 这可以通过使用条件变量(布尔标志)来应对。 只要标志为 true,就将 wait() 调用放入 while 循环中。 因此,如果线程由于 Notify/NotifyAll 以外的任何原因从 wait 调用中唤醒,那么它会遇到 flag 仍然为 true 的情况,因此再次调用 wait。 在调用notify之前,将此标志设置为true。 在这种情况下,布尔标志被声明为易失性

In my opinion, two important scenarios other than stopping thread in which volatile keyword is used are:

  1. Double-checked locking mechanism. Used often in Singleton design
    pattern. In this the singleton object needs to be declared volatile.
  2. Spurious Wakeups. Thread may sometimes wake up from wait call even if no notify call has been issued. This behavior is called spurious wakeup. This can be countered by using a conditional variable (boolean flag). Put the wait() call in a while loop as long as the flag is true. So if thread wakes up from wait call due to any reasons other than Notify/NotifyAll then it encounters flag is still true and hence calls wait again. Prior to calling notify set this flag to true. In this case the boolean flag is declared as volatile.
可是我不能没有你 2024-07-12 22:22:03

是的,只要您希望多个线程访问可变变量,就必须使用 volatile。 这不是很常见的用例,因为通常您需要执行多个原子操作(例如,在修改变量之前检查变量状态),在这种情况下,您将使用同步块。

Yes, volatile must be used whenever you want a mutable variable to be accessed by multiple threads. It is not very common usecase because typically you need to perform more than a single atomic operation (e.g. check the variable state before modifying it), in which case you would use a synchronized block instead.

过潦 2024-07-12 22:22:03

没有人提到long和double变量类型的读写操作的处理。 对于引用变量和大多数原始变量来说,读取和写入都是原子操作,除了 long 和 double 变量类型,它们必须使用 volatile 关键字才能成为原子操作。 @link

No one has mentioned the treatment of read and write operation for long and double variable type. Reads and writes are atomic operations for reference variables and for most primitive variables, except for long and double variable types, which must use the volatile keyword to be atomic operations. @link

天赋异禀 2024-07-12 22:22:03

假设某个线程修改了共享变量的值,前提是您没有对该变量使用 volatile 修饰符。 当其他线程想要读取该变量的值时,它们看不到更新后的值,因为它们从 CPU 的缓存而不是 RAM 内存中读取变量的值。 此问题也称为可见性问题

通过声明共享变量易失性,所有对计数器变量的写入都将立即写回主内存。 此外,计数器变量的所有读取都将直接从主存储器读取。

public class SharedObject {
    public volatile int sharedVariable = 0;
}

对于非易失性变量,无法保证 Java 虚拟机 (JVM) 何时将数据从主内存读取到 CPU 缓存,或将数据从 CPU 缓存写入主内存。 这可能会导致几个问题,我将在以下各节中解释这些问题。


示例:

想象一种情况,其中两个或多个线程可以访问包含如下声明的计数器变量的共享对象:

public class SharedObject {
    public int counter = 0;
}

同样想象一下,只有线程 1 递增计数器变量,但线程 1 和线程 2 可能会不时读取计数器变量。

如果计数器变量未声明为 易失性,则无法保证计数器变量的值何时从 CPU 缓存写回主内存。 这意味着,CPU 缓存中的计数器变量值可能与主内存中的不同。 这种情况如下所示:

volatile

由于变量尚未被另一个线程写回到主内存,因此线程看不到变量的最新值的问题称为“可见性”问题。 一个线程的更新对其他线程不可见。

Assume that a thread modifies the value of a shared variable, if you didn't use volatile modifier for that variable. When other threads want to read this variable's value, they don't see the updated value because they read the variable's value from the CPU's cache instead of RAM memory. This problem also known as Visibility Problem.

By declaring the shared variable volatile, all writes to the counter variable will be written back to main memory immediately. Also, all reads of the counter variable will be read directly from main memory.

public class SharedObject {
    public volatile int sharedVariable = 0;
}

With non-volatile variables there are no guarantees about when the Java Virtual Machine (JVM) reads data from main memory into CPU caches, or writes data from CPU caches to main memory. This can cause several problems which I will explain in the following sections.


Example:

Imagine a situation in which two or more threads have access to a shared object which contains a counter variable declared like this:

public class SharedObject {
    public int counter = 0;
}

Imagine too, that only Thread 1 increments the counter variable, but both Thread 1 and Thread 2 may read the counter variable from time to time.

If the counter variable is not declared volatile there is no guarantee about when the value of the counter variable is written from the CPU cache back to main memory. This means, that the counter variable value in the CPU cache may not be the same as in main memory. This situation is illustrated here:

volatile

The problem with threads not seeing the latest value of a variable because it has not yet been written back to main memory by another thread, is called a "visibility" problem. The updates of one thread are not visible to other threads.

拍不死你 2024-07-12 22:22:03

如果您正在开发多线程应用程序,则需要使用“易失性”关键字或“同步”以及您可能拥有的任何其他并发控制工具和技术。 此类应用程序的示例是桌面应用程序。

如果您正在开发一个将部署到应用程序服务器(Tomcat、JBoss AS、Glassfish 等)的应用程序,您不必自己处理并发控制,因为它已经由应用程序服务器处理。 事实上,如果我没记错的话,Java EE 标准禁止 servlet 和 EJB 中的任何并发控制,因为它是“基础设施”层的一部分,您应该免于处理它。 如果您要实现单例对象,则只能在此类应用程序中进行并发控制。 如果您使用像 Spring 这样的框架来编织组件,这个问题甚至已经得到解决。

因此,在大多数 Java 开发情况下(应用程序是 Web 应用程序并使用 Spring 或 EJB 等 IoC 框架),您不需要使用“易失性”。

You'll need to use 'volatile' keyword, or 'synchronized' and any other concurrency control tools and techniques you might have at your disposal if you are developing a multithreaded application. Example of such application is desktop apps.

If you are developing an application that would be deployed to application server (Tomcat, JBoss AS, Glassfish, etc) you don't have to handle concurrency control yourself as it already addressed by the application server. In fact, if I remembered correctly the Java EE standard prohibit any concurrency control in servlets and EJBs, since it is part of the 'infrastructure' layer which you supposed to be freed from handling it. You only do concurrency control in such app if you're implementing singleton objects. This even already addressed if you knit your components using frameworkd like Spring.

So, in most cases of Java development where the application is a web application and using IoC framework like Spring or EJB, you wouldn't need to use 'volatile'.

毁梦 2024-07-12 22:22:03

易失性仅保证所有线程(甚至线程本身)都在递增。 例如:计数器同时看到变量的同一面。 它没有用来代替同步或原子或其他东西,它完全使读取同步。 请不要将其与其他 java 关键字进行比较。 正如下面的示例所示,易失性变量操作也是原子的,它们会立即失败或成功。

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

即使你投入或不投入,结果也总是会有所不同。 但如果您使用 AtomicInteger,如下所示,结果将始终相同。 这与同步也是一样的。

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

volatile only guarantees that all threads, even themselves, are incrementing. For example: a counter sees the same face of the variable at the same time. It is not used instead of synchronized or atomic or other stuff, it completely makes the reads synchronized. Please do not compare it with other java keywords. As the example shows below volatile variable operations are also atomic they fail or succeed at once.

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

Even you put volatile or not results will always differ. But if you use AtomicInteger as below results will be always same. This is same with synchronized also.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }
计㈡愣 2024-07-12 22:22:03

每个访问易失性字段的线程都会在继续之前读取其当前值,而不是(可能)使用缓存的值。

只有成员变量可以是易失性的或瞬态的。

Every thread accessing a volatile field will read its current value before continuing, instead of (potentially) using a cached value.

Only member variable can be volatile or transient.

揽清风入怀 2024-07-12 22:22:03

“... volatile 修饰符保证任何读取字段的线程都将看到最近写入的值。” - Josh Bloch

如果您正在考虑使用易失性,请阅读包java.util.concurrent 处理原子行为。

维基百科关于单例模式的帖子显示使用中不稳定。

“… the volatile modifier guarantees that any thread that reads a field will see the most recently written value.” - Josh Bloch

If you are thinking about using volatile, read up on the package java.util.concurrent which deals with atomic behaviour.

The Wikipedia post on a Singleton Pattern shows volatile in use.

寄意 2024-07-12 22:22:03

易失性具有内存可见性的语义。 基本上,在写操作完成后,易失性字段的值对所有读取器(特别是其他线程)都可见。 如果没有易失性,读者可能会看到一些未更新的值。

回答你的问题:是的,我使用易失性变量来控制某些代码是否继续循环。 该循环测试易失性值,如果为true则继续。 可以通过调用“stop”方法将条件设置为false。 循环在 stop 方法完成执行后测试值时看到 false 并终止。

我强烈推荐的书“Java Concurrency in Practice”对volatile给出了很好的解释。 这本书的作者是问题中引用的 IBM 文章的同一个人(事实上,他在那篇文章的底部引用了他的书)。 我对 volatile 的使用被他的文章称为“模式 1 状态标志”。

如果您想了解有关如何 易失性在幕后工作,请阅读Java 内存模型。 如果您想超越这个水平,请查看一本好的计算机架构书籍,例如 轩尼诗 Patterson 并了解缓存一致性和缓存一致性。

volatile has semantics for memory visibility. Basically, the value of a volatile field becomes visible to all readers (other threads in particular) after a write operation completes on it. Without volatile, readers could see some non-updated value.

To answer your question: Yes, I use a volatile variable to control whether some code continues a loop. The loop tests the volatile value and continues if it is true. The condition can be set to false by calling a "stop" method. The loop sees false and terminates when it tests the value after the stop method completes execution.

The book "Java Concurrency in Practice," which I highly recommend, gives a good explanation of volatile. This book is written by the same person who wrote the IBM article that is referenced in the question (in fact, he cites his book at the bottom of that article). My use of volatile is what his article calls the "pattern 1 status flag."

If you want to learn more about how volatile works under the hood, read up on the Java memory model. If you want to go beyond that level, check out a good computer architecture book like Hennessy & Patterson and read about cache coherence and cache consistency.

红衣飘飘貌似仙 2024-07-12 22:22:03

挥发性(vɒlətʌɪl):在常温下容易蒸发

关于挥发性的要点:

  1. Java中的同步可以通过使用Java关键字synchronized< /code> 和 易失性 和锁。
  2. 在Java中,我们不能有synchronized变量。 对变量使用 synchronized 关键字是非法的,并且会导致编译错误。 除了使用 Java 中的 synchronized 变量,您也可以使用 java volatile 变量,该变量将指示 JVM 线程读取 volatile 变量的值来自主内存并且不在本地缓存它。
  3. 如果变量不在多个线程之间共享,则无需使用 volatile 关键字。

来源

的使用示例>易失性

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

我们在第一个请求到来时延迟创建实例。

如果我们不将_instance变量设置为易失性,那么创建Singleton实例的线程将无法与其他线程通信。 因此,如果线程 A 正在创建 Singleton 实例,并且在创建后不久,CPU 损坏等,所有其他线程将无法看到 _instance 的值不为 null,并且它们会相信它仍然被分配为 null 。

为什么会发生这种情况? 因为读取器线程没有执行任何锁定,并且在写入器线程退出同步块之前,内存将不会同步,并且 _instance 的值不会在主内存中更新。 使用 Java 中的 Volatile 关键字,这是由 Java 本身处理的,并且所有读取器线程都可以看到此类更新。

结论volatile关键字也用于在线程之间传递内存内容。

不使用 volatile 的示例用法:

public class Singleton {    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance() {   
        if (_instance == null) {  
            synchronized(Singleton.class) {  
                if (_instance == null) 
                    _instance = new Singleton(); 
            } 
        }
        return _instance;  
    }
}

上面的代码不是线程安全的。 尽管它在同步块内再次检查实例的值(出于性能原因),但 JIT 编译器可以重新排列字节码,以便在构造函数完成执行之前设置对实例的引用。 这意味着 getInstance() 方法返回的对象可能尚未完全初始化。 为了使代码线程安全,从 Java 5 开始可以使用关键字 volatile 作为实例变量。 一旦对象的构造函数完全完成其执行,标记为易失性的变量仅对其他线程可见。
来源

在此处输入图像描述

易失性在Java中的使用

快速失败迭代器通常使用列表对象上的易失性计数器来实现。

  • 当列表更新时,计数器就会递增。
  • 创建 Iterator 时,计数器的当前值将嵌入到 Iterator 对象中。
  • 当执行Iterator操作时,该方法会比较两个计数器值,如果不同则抛出ConcurrentModificationException

故障安全迭代器的实现通常是轻量级的。 它们通常依赖于特定列表实现的数据结构的属性。 没有一般模式。

Volatile(vɒlətʌɪl): Easily evaporated at normal temperatures

Important point about volatile:

  1. Synchronization in Java is possible by using Java keywords synchronized and volatile and locks.
  2. In Java, we can not have synchronized variable. Using synchronized keyword with a variable is illegal and will result in compilation error. Instead of using the synchronized variable in Java, you can use the java volatile variable, which will instruct JVM threads to read the value of volatile variable from main memory and don’t cache it locally.
  3. If a variable is not shared between multiple threads then there is no need to use the volatile keyword.

source

Example usage of volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

We are creating instance lazily at the time the first request comes.

If we do not make the _instance variable volatile then the Thread which is creating the instance of Singleton is not able to communicate to the other thread. So if Thread A is creating Singleton instance and just after creation, the CPU corrupts etc, all other threads will not be able to see the value of _instance as not null and they will believe it is still assigned null.

Why does this happen? Because reader threads are not doing any locking and until the writer thread comes out of a synchronized block, the memory will not be synchronized and value of _instance will not be updated in main memory. With the Volatile keyword in Java, this is handled by Java itself and such updates will be visible by all reader threads.

Conclusion: volatile keyword is also used to communicate the content of memory between threads.

Example usage of without volatile:

public class Singleton {    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance() {   
        if (_instance == null) {  
            synchronized(Singleton.class) {  
                if (_instance == null) 
                    _instance = new Singleton(); 
            } 
        }
        return _instance;  
    }
}

The code above is not thread-safe. Although it checks the value of instance once again within the synchronized block (for performance reasons), the JIT compiler can rearrange the bytecode in a way that the reference to the instance is set before the constructor has finished its execution. This means the method getInstance() returns an object that may not have been initialized completely. To make the code thread-safe, the keyword volatile can be used since Java 5 for the instance variable. Variables that are marked as volatile get only visible to other threads once the constructor of the object has finished its execution completely.
Source

enter image description here

volatile usage in Java:

The fail-fast iterators are typically implemented using a volatile counter on the list object.

  • When the list is updated, the counter is incremented.
  • When an Iterator is created, the current value of the counter is embedded in the Iterator object.
  • When an Iterator operation is performed, the method compares the two counter values and throws a ConcurrentModificationException if they are different.

The implementation of fail-safe iterators is typically light-weight. They typically rely on properties of the specific list implementation's data structures. There is no general pattern.

青朷 2024-07-12 22:22:03

使用易失性的一个常见示例是使用易失性布尔变量作为终止线程的标志。 如果您已经启动了一个线程,并且希望能够从另一个线程安全地中断它,则可以让该线程定期检查标志。 要停止它,请将标志设置为 true。 通过设置标志易失性,您可以确保正在检查它的线程在下次检查它时会看到它已被设置,甚至无需使用synchronized块。

One common example for using volatile is to use a volatile boolean variable as a flag to terminate a thread. If you've started a thread, and you want to be able to safely interrupt it from a different thread, you can have the thread periodically check a flag. To stop it, set the flag to true. By making the flag volatile, you can ensure that the thread that is checking it will see it has been set the next time it checks it without having to even use a synchronized block.

岁月打碎记忆 2024-07-12 22:22:03

Java 易失性

易失性 -> 同步[关于]

易失性对于程序员来说,该值始终是最新的。 问题是该值可以保存在不同类型的硬件内存上。 例如,它可以是 CPU 寄存器、CPU 缓存、RAM...CPU 寄存器和 CPU 缓存属于 CPU,不能与在多线程环境中救援的 RAM 共享数据

在此处输入图像描述

volatile 关键字表示变量将直接从 RAM 内存读取和写入。 它

通过支持 happens-before[关于]

对易失性字段的写入发生在该字段的每次后续读取之前。

Read is after write

易失性关键字无法解决竞争条件< super>[关于]情况使用synchronized关键字 [关于]

因此,只有当一个线程写入而其他线程仅读取易失性值时才安全

Java Volatile

volatile -> synchronized[About]

volatile says for a programmer that the value always will be up to date. The problem is that the value can be saved on different types of hardware memory. For example it can be CPU registers, CPU cache, RAM... СPU registers and CPU cache belong to CPU and can not share a data unlike of RAM which is on the rescue in multithreading envirompment

enter image description here

volatile keyword says that a variable will be read and written from/to RAM memory directly. It has some computation footprint

Java 5 extended volatile by supporting happens-before[About]

A write to a volatile field happens-before every subsequent read of that field.

Read is after write

volatile keyword does not cure a race condition[About] situation to sove it use synchronized keyword[About]

As a result it safety only when one thread writes and others just read the volatile value

安稳善良 2024-07-12 22:22:03

易失性对于停止线程非常有用。

并不是说您应该编写自己的线程,Java 1.6 有很多不错的线程池。 但如果您确定需要一个线程,则需要知道如何停止它。

我对线程使用的模式是:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

在上面的代码段中,在while循环中读取close的线程与调用close()的线程不同。 如果没有 volatile,运行循环的线程可能永远不会看到要关闭的更改。

请注意如何不需要同步

volatile is very useful to stop threads.

Not that you should be writing your own threads, Java 1.6 has a lot of nice thread pools. But if you are sure you need a thread, you'll need to know how to stop it.

The pattern I use for threads is:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

In the above code segment, the thread reading close in the while loop is different from the one that calls close(). Without volatile, the thread running the loop may never see the change to close.

Notice how there's no need for synchronization

国粹 2024-07-12 22:22:03

使用 volatile 关键字声明的变量有两个使其特殊的主要品质。

  1. 如果我们有一个易失性变量,则任何线程都无法将其缓存到计算机(微处理器)的缓存中。 访问总是从主内存发生。

  2. 如果对易失性变量进行写入操作,并且突然请求读取操作 ,保证写操作会先于读操作完成

上述两个性质推断出

  • 所有读取易失性变量的线程都一定会读取到最新值。 因为没有缓存的值可以污染它。 并且读请求只有在当前写操作完成后才会被授予。

另一方面,

  • 如果我们进一步研究我提到的#2,我们可以看到volatile关键字是维护具有<的共享变量的理想方法。 strong>'n' 个读取器线程,并且只有一个写入器线程 可以访问它。 一旦我们添加了 volatile 关键字,就完成了。 没有任何其他关于线程安全的开销。

相反,

我们不能单独使用volatile关键字来满足有多个共享变量编写器线程访问它

A variable declared with volatile keyword, has two main qualities which make it special.

  1. If we have a volatile variable, it cannot be cached into the computer's(microprocessor) cache memory by any thread. Access always happened from main memory.

  2. If there is a write operation going on a volatile variable, and suddenly a read operation is requested, it is guaranteed that the write operation will be finished prior to the read operation.

Two above qualities deduce that

  • All the threads reading a volatile variable will definitely read the latest value. Because no cached value can pollute it. And also the read request will be granted only after the completion of the current write operation.

And on the other hand,

  • If we further investigate the #2 that I have mentioned, we can see that volatile keyword is an ideal way to maintain a shared variable which has 'n' number of reader threads and only one writer thread to access it. Once we add the volatile keyword, it is done. No any other overhead about thread safety.

Conversly,

We can't make use of volatile keyword solely, to satisfy a shared variable which has more than one writer thread accessing it.

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