6

我只是从线程的角度问......可能回答了很多次,但请帮助我理解这一点。

参考这里的帖子 Volatile Vs Static in java

询问一个静态变量值也将是所有线程的一个值,那么我们为什么要选择 volatile 呢?我找到了以下示例:

public class VolatileExample {  
public static void main(String args[]) {  
    new ExampleThread("Thread 1 ").start();  
    new ExampleThread("Thread 2 ").start();  
}  

}  
class ExampleThread extends Thread {  
private static volatile int testValue = 1;  
public ExampleThread(String str){  
    super(str);  
}  
public void run() {  
    for (int i = 0; i < 3; i++) {  
        try {  
            System.out.println(getName() + " : "+i);  
            if (getName().compareTo("Thread 1 ") == 0)  
            {  
                testValue++;  
                System.out.println( "Test Value T1: " + testValue);  
            }  
            if (getName().compareTo("Thread 2 ") == 0)  
            {  
                System.out.println( "Test Value T2: " + testValue);  
            }                 
            Thread.sleep(1000);  
        } catch (InterruptedException exception) {  
            exception.printStackTrace();  
          }  
       }  
       }  
       }

输出:

Thread 1 : 0
Test Value T1: 2
Thread 2 : 0
Test Value T2: 2
Thread 1 : 1
Test Value T1: 3
Thread 2 : 1
Test Value T2: 3
Thread 1 : 2
Test Value T1: 4
Thread 2 : 2
Test Value T2: 4

如果我从 testValue 中删除静态,则获得的结果:

Thread 1 : 0
Test Value T1: 2
Thread 2 : 0
Test Value T2: 1
Thread 1 : 1
Test Value T1: 3
Thread 2 : 1
Test Value T2: 1
Thread 1 : 2
Test Value T1: 4
Thread 2 : 2
Test Value T2: 1

为什么线程 2 没有读取更新的值?如果必须将其设为 static ,那么 volatile 有什么用?

有人可以提供一个很好的 volatile 示例的链接,其中该变量未声明为静态的。

谢谢

4

4 回答 4

4

的用途volatile是确保所有线程同时看到相同的值。本质上是说这个变量永远不应该被缓存。

很难在代码中演示,因为缓存策略从 cpu 到 cpu,从 JRE 到 JRE。

volatile对 JRE 的看法是,如果您想缓存这个值,因为您认为这样做可以使代码运行得更快一点……不要...我对编程的了解比您多得多,而且我毫无疑问地知道,如果您这样做,您将破坏我的代码。.

于 2013-02-25T14:17:19.520 回答
4

问题是 ++ 不是原子的。该代码应AtomicInteger改为使用。使用该static变量,两个线程都在尝试更新相同的值,而 ++ 实际上是 3 个操作:get、+1 和 store。这意味着两个线程之间存在竞争条件并且它们正在相互覆盖。

为什么线程 2 没有读取更新的值?如果必须将其设为 static ,那么 volatile 有什么用?

staticvolatile做不同的事情。 static使字段与类相关联,而不是对象实例。 volatile强制对该字段的任何读取或写入跨越内存屏障。这允许多个线程读取和更新一个公共字段,但这并不能保护您免受多个操作的影响。如果您使变量不是静态的,那么您将不需要,volatile因为每个线程都将使用它自己的字段实例。

你应该使用AtomicInteger. 这包装了 avolatile int但也提供了对增量操作的特殊处理:

private static AtomicInteger testValue = new AtomicInteger(1);
...
testValue.incrementAndGet();

有人可以提供一个很好的 volatile 示例的链接,其中该变量未声明为静态的。

volatile当它由多个线程共享时,您将需要一个非静态字段。例如,如果您将testValueup 移到VolatileExample类中,然后将类的相同实例传递给VolatileExample两个线程,以便它们可以访问testValue.

于 2013-02-25T14:19:18.927 回答
0

在这种情况下,当 tou 删除static修饰符时,您更改了变量,因为现在,每个ExampleThread都有自己的testValue,为此,每个线程实例的存储值是不同的。

static修饰符的含义:

有时,您希望拥有所有对象共有的变量。这是通过静态修饰符完成的。声明中带有 static 修饰符的字段称为静态字段或类变量。它们与类相关联,而不是与任何对象相关联。类的每个实例共享一个类变量,该变量位于内存中的一个固定位置。任何对象都可以更改类变量的值...

参考:了解实例和类成员

关于volatile,如果您尝试另一种测试,将testValue变量移动到 class VolatileExample,如下所示:

 public class VolatileExample {
    private volatile int testValue = 1;
 ...
 }

然后,删除testVariablefromExampleThread并运行代码。它的输出应该和第一个一样。

volatile修饰符的含义:

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

参考:原子访问

于 2013-02-25T17:21:22.453 回答
0

您的最后一个问题“有人可以链接到一个很好的 volatile 示例,其中该变量未声明为静态的。” 为了清楚地理解这个概念,我举了一个例子。

    public class VolatileExample {

   // public static volatile int testValue = 1;
    public  int nonstatic=8;
    public static void main(String args[]) {
        VolatileExample volatileExample=new VolatileExample();
        new ExampleThread("Thread 1 ",volatileExample).start();
        new ExampleThread("Thread 2 ",volatileExample).start();
    }

}
class ExampleThread extends Thread {
VolatileExample volatileExample;
    public ExampleThread(String str,VolatileExample volatileExample){
        super(str);
        this.volatileExample=volatileExample;
    }
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                if (getName().compareTo("Thread 1 ") == 0)
                {
                   volatileExample.nonstatic++;
                    System.out.println( "Test Value T1: " + volatileExample.nonstatic);


                }
                if (getName().compareTo("Thread 2 ") == 0)
                {
                    System.out.println( "Test Value T2: " + volatileExample.nonstatic);
                    Thread.sleep(1);
                }
               // Thread.sleep(1000);
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }
}

现在请通过使 public int nonstatic=8; 来查看更改。易挥发的

以下将是当它变得易变时的输出,

测试值 T1:9 测试值 T1:10 测试值 T1:11 测试值 T2:11 测试值 T2:11 测试值 T2:11

进程以退出代码 0 结束

以下是非易失性的结果: 测试值 T1:9 测试值 T2:9 测试值 T1:10 测试值 T1:11 测试值 T2:11 测试值 T2:11

进程以退出代码 0 结束

观察:由于 Volatile keyWord 使线程直接将其写入主内存,因此线程更快地返回到循环中,因此当它变为 volatile 时,线程 1 更快地完成了任务,甚至在线程 2 开始之前就回到了循环中。

另一方面,使其成为非易失性,使线程更新其 locl 缓存,然后将其报告给主内存......所以在这个循环中,线程 2 进入了竞争。

请注意,我在 VolatileExample 类中有“非静态”变量,而不是在 ExampleThread 中来演示这一点。

您的另一个问题是:为什么线程 2 没有读取更新的值?如果必须将其设为 static ,那么 volatile 有什么用?

好吧,如果你让它 testvalue 非静态,那么它只不过是 ExampleThread 类的两个不同对象 Thread1 和 Thread2 处理他们自己的变量 testvalue.. 所以 volatile 并不重要..

于 2015-11-10T04:56:59.163 回答