为什么Java中的volatile不会更新变量的值? [英] Why doesn't volatile in Java update the value of a variable?

查看:102
本文介绍了为什么Java中的volatile不会更新变量的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经读过Java中的volatile允许不同的线程访问同一个字段并查看其他线程对该字段所做的更改。如果是这种情况,我预测当第一个和第二个线程完全运行时,d的值将增加到4.但相反,每个线程将d增加到值2。

I've read that "volatile" in Java allows different threads to have access to the same field and see changes the other threads has made to that field. If that's the case, I'd predict that when the first and second thread have completely run, the value of "d" will be incremented to 4. But instead, each thread increments "d" to a value of 2.

public class VolatileExample extends Thread {
private int countDown = 2;
private volatile int d = 0; 

    public VolatileExample(String name) {
        super(name);
        start();
    }

    public String toString() {
       return super.getName() + ": countDown " + countDown;
    }

public void run() {
        while(true) {
        d = d + 1;
    System.out.println(this + ". Value of d is " + d);
    if(--countDown == 0) return;
    }
}

public static void main(String[] args) {
    new VolatileExample("first thread");
    new VolatileExample("second thread");
    }
}

运行此程序的结果是:


第一个线程:countDown 2. d的值是1

first thread: countDown 2. Value of d is 1

第二个线程:countDown 2 .D的值是1

second thread: countDown 2. Value of d is 1

第一个线程:countDown 1. d的值是2

first thread: countDown 1. Value of d is 2

第二个线程: countDown 1. d的值是2

second thread: countDown 1. Value of d is 2

我明白如果我在程序中添加关键字static,
(即是,private static volatile int d = 0;),d将增加到4.
我知道这是因为d将成为整个类共享的变量而不是每个实例获得副本。

I understand that if I add keyword "static" the program, (that is, "private static volatile int d = 0;"), "d" would be incremented to 4. And I know that's because d will become a variable that the whole class shares rather than each instance getting a copy.

结果如下:


第一个线程:countDown 2.值d是1

first thread: countDown 2. Value of d is 1

第一个线程:countDown 1. d的值是3

first thread: countDown 1. Value of d is 3

第二个线程:countDown 2。 d的值是2

second thread: countDown 2. Value of d is 2

第二个线程:countDown 1. d的值是4

second thread: countDown 1. Value of d is 4

我的问题是,为什么不private volatile int d = 0; 如果volatile应该允许在两个线程之间共享d,那么会产生类似的结果吗?也就是说,如果第一个线程将d的值更新为1,那么为什么第二个线程不会抓取d的值为1而不是零?

My question is, why doesn't "private volatile int d = 0; " yield similar results if volatile is supposed to allow the sharing of "d" between the two threads? That is, if the first thread updates the value of d to 1, then why doesn't the second thread grab the value of d as 1 rather than as zero?

推荐答案

这里有一些误解。你似乎没有正确理解线程是什么,实例字段是什么以及静态字段是什么。

There are a couple of misunderstandings here. You seem not to properly understand what a thread is, what an instance field is and what a static field is.

实例字段是一个实例化类时分配的内存位置(即,当您 VolatileExample v = new VolatileExample()时,为字段 d 分配内存位置。在类中的内存位置,你做 this.d (然后你可以写入并读取该内存位置)。要从类外部引用该内存位置,它必须是可访问的(即,不是私人),然后你会做 vd 。作为你好你可以看到,一个类的每个实例都有自己的字段 d 的内存位置。因此,如果您有2个不同的 VolatileExample 实例,则每个实例都有自己的独立字段 d

An instance field is a memory location that gets allocated once you instantiate a class (ie, a memory location gets allocated for a field d when you VolatileExample v = new VolatileExample()). To reference that memory location from within the class, you do this.d (then you can write to and read from that memory location). To reference that memory location from outside the class, it must be acessible (ie, not private), and then you'd do v.d. As you can see, each instance of a class gets its own memory location for its own field d. So, if you have 2 different instances of VolatileExample, each will have its own, independent, field d.

静态字段是一个内存位置,在初始化类时会被分配(忘记使用多个 ClassLoader s,恰好发生一次)。因此,您可以认为静态字段是某种全局变量。要引用该内存位置,您可以使用 VolatileExample.d (可访问性也适用(即,如果它是 private ,它只能在类中完成))。

A static field is a memory location that gets allocated once a class is initialized (which, forgetting about the possibility of using multiple ClassLoaders, happens exactly once). So, you can think that a static field is some kind of global variable. To reference that memory location, you'd use VolatileExample.d (accessibility also applies (ie, if it is private, it can only be done from within the class)).

最后,执行线程是将要执行的一系列步骤由JVM。您不能将线程视为类,或者类 Thread 的实例,它只会让您感到困惑。它很简单:一系列步骤。

Finally, a thread of execution is a sequence of steps that will be executed by the JVM. You must not think of a thread as a class, or an instance of the class Thread, it will only get you confused. It is as simple as that: a sequence of steps.

步骤序列是 main(...)方法。当您启动程序时,JVM将开始执行的步骤序列。

The main sequence of steps is what is defined in the main(...) method. It is that sequence of steps that the JVM will start executing when you launch your program.

如果要启动新的执行线程以同时运行(即,您想要一个单独的步骤序列同时运行),在Java中你可以通过创建类的实例 Thread 并调用它的 start() 方法。

If you want to start a new thread of execution to run simultaneously (ie, you want a separate sequence of steps to be run concurrently), in Java you do so by creating an instance of the class Thread and calling its start() method.

让我们稍微修改你的代码,以便更容易理解发生了什么:

Let's modify your code a little bit so that it is easier to understand what is happening:

public class VolatileExample extends Thread {
  private int countDown = 2;
  private volatile int d = 0;

  public VolatileExample(String name) {
    super(name);
  }

  public String toString() {
    return super.getName() + ": countDown " + countDown;
  }

  public void run() {
    while(true) {
      d = d + 1;
      System.out.println(this + ". Value of d is " + d);
      if(--countDown == 0) return;
    }
  }

  public static void main(String[] args) {
    VolatileExample ve1 = new VolatileExample("first thread");
    ve1.start();
    VolatileExample ve2 = new VolatileExample("second thread");
    ve2.start();
  }
}

VolatileExample ve1 = new VolatileExample(first thread); 创建 VolatileExample 的实例。这将分配一些内存位置:倒计时 4个字节, d 4个字节。然后启动一个新的执行线程: ve1.start(); 。此执行线程将访问(读取和写入)本段前面描述的内存位置。

The line VolatileExample ve1 = new VolatileExample("first thread"); creates an instance of VolatileExample. This will allocate some memory locations: 4 bytes for countdown and 4 bytes for d. Then you start a new thread of execution: ve1.start();. This thread of execution will access (read from and write to) the memory locations described before in this paragraph.

下一行 VolatileExample ve2 = new VolatileExample(second thread); 创建另一个 VolatileExample 的实例,它将分配2个新的内存位置:ve2的<$ c为4个字节$ c>倒计时和ve2的 d 的4个字节。然后,您启动一​​个执行线程,它将访问这些新的内存位置,而不是在此之前段落中描述的内容。

The next line, VolatileExample ve2 = new VolatileExample("second thread"); creates another instance of VolatileExample, which will allocate 2 new memory locations: 4 bytes for ve2's countdown and 4 bytes for ve2's d. Then, you start a thread of execution, which will access THESE NEW memory locations, and not those described in the paragraph before this one.

现在,有或没有 volatile ,你看到你有两个不同的字段 d :每个线程在不同的字段上运行。 因此,您认为 d 会增加到4是不合理的,因为没有单个 d

Now, with or without volatile, you see that you have two different fields d : each thread operates on a different field. Therefore, it is unreasonable for you to expect that d would get incremented to 4, since there's no single d.

如果你使 d 一个静态字段,那么两个线程都会(据说)在同一个内存位置上运行。只有这时 volatile 才能发挥作用,因为只有这样你才会在不同的线程之间共享一个内存位置。

If you make d a static field, only then both threads would (supposedly) be operating on the same memory location. Only then volatile would come into play, since only then you'd be sharing a memory location between different threads.

如果你创建一个字段 volatile ,你可以保证写入直接进入主存储器并且读取直接来自主存储器(即,它们不会被缓存在一些 - 非常快 - 处理器本地缓存,操作需要更长时间,但保证其他线程可见。)

If you make a field volatile, you are guaranteed that writes go straight to the main memory and reads come straight from the main memory (ie, they won't get cached on some -- extremely fast -- processor-local cache, the operations would take longer but would be guaranteed to be visible to other threads).

然而,它不会,保证您看到 d 中存储的值4。这是因为 volatile 解决了可见性问题,但没有解决原子性问题: increment =从主内存读取+操作值+写入主内存。正如您所看到的,2个不同的线程可以读取初始值(0),在其上操作(本地)(获得1),然后将其写入主存储器(两者最终都会写入1) - 2个增量将是感知只有1。

It wouldn't, however, guarantee that you'd see the value 4 stored on d. That's because volatile solves visibility problem, but not atomicity problems: increment = read from main memory + operation on the value + write to main memory. As you can see, 2 different threads could read the initial value (0), operate (locally) on it (obtaining 1), then writing it to the main memory (both would end up writing 1) -- the 2 increments would be perceived as only 1.

要解决这个问题,必须使增量成为原子操作。为此,您需要使用同步机制 - 互斥锁( synchronized(...){...} 或显式锁定) - - 或专为此事设计的课程: AtomicInteger

To solve this, you must make the increment an atomic operation. To do so, you'd need to either use a synchronization mechanism -- a mutex (synchronized (...) { ... }, or an explicit lock) -- or a class designed specifically for this things: AtomicInteger.

这篇关于为什么Java中的volatile不会更新变量的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆