Java中的易失性关键字用法

时间:2015-06-30 06:04:09

标签: java multithreading

我无法理解如果我的变量既是易失性的又是静态的,那么为什么线程不反映输出中的公共共享值 最后几行的输出是: 线程正在运行 4998Thread-0 线程正在运行 4999Thread-0 线程正在运行 4899Thread-1 线程正在运行

public class Test implements Runnable{
  volatile static int  i=0;
    @Override
    public void run() {
        for(;i<5000;i++)
        {   try {
            Thread t = Thread.currentThread();
      String name = t.getName();
      //  Thread.sleep(10);
                System.out.println(i+name);
            } catch (Exception ex) {
               ex.printStackTrace();
            }
        System.out.println("Thread is running");
    }}
    public static void main(String[] args) {
    Test t=new Test();
    Thread t1=new Thread(t);
    Thread t2=new Thread(t);
    t1.start();
      // t.run();
       t2.start();
    }
    }

4 个答案:

答案 0 :(得分:2)

您不能在易变量上使用i++之类的复合(多步)操作。

您可以让两个线程检索该值,增加它并将其写回,从而导致一个丢失的增量(如您在示例中所示)。

答案 1 :(得分:0)

Volatile确保变量的更改对其他线程可见。但无法确保同步

从程序的一次执行中我得到了这个输出:

Thread is running
3474Thread-1
Thread is running
3475Thread-0
Thread is running
3477Thread-0
(.... many lines where i goes from 3478 to 4998, with Thread-0 always being the one running...)
Thread is running
4999Thread-0
Thread is running
3476Thread-1
Thread is running

这是因为线程会获得运行处理器时间的片段,并且可以在任何时候暂停和恢复它们的执行。 这里Thread-1正在执行“System.out.println(i + name);”行我的值为3476.i + name被评估为“3476Thread-1”,但就在此之后,Thread-1执行停止,而Thread-0则获得其时间片。 Thread-0执行直到完成。然后Thread-1再次执行。我们已将 名称评估为“3476Thread-1”并且在调用println之前保留了。呼叫现在已完成并打印,因此您可以在此结束时看到“3476Thread-1”。我已经被Thread-0增加到5000,但是这并没有改变i + name的评估结果,这个结果是之前所有这些增加。

问题是i ++和i + name是不同的指令,线程执行可以暂停并在它们之间恢复。为了确保获得一个secuential输出,您需要确保i ++和i + name之间没有中断。也就是说,您需要使该组指令成为原子。

public class Test implements Runnable{
    static Object lock = new Object();

  volatile static int  i=0;
    @Override
    public void run() {
        for(;;)
        {
            try {
                Thread t = Thread.currentThread();
                String name = t.getName();
                synchronized( lock )
                {
                    if ( i>=5000 )
                        break;
                    i++;
                    System.out.println(i+name);
                }
                //  Thread.sleep(10);
            } catch (Exception ex) {
               ex.printStackTrace();
            System.out.println("Thread is running");
            }
    }   }
    public static void main(String[] args) {
    Test t=new Test();
    Thread t1=new Thread(t);
    Thread t2=new Thread(t);
    t1.start();
      // t.run();
       t2.start();
    }
}

在该程序中,如果Thread-1在i ++和i +名称之间暂停,它将位于由synchronized(lock)控制的临界区内。当Thread-0执行时,它将到达同步(锁定)指令,并且必须停止执行,直到Thread-1恢复并离开该块。因为JLS确保:

  

synchronized语句(第14.19节)计算对象的引用;   然后它尝试在该对象的监视器上执行锁定操作   在锁定操作成功之前不会继续进行   完成。锁定动作执行后,身体的   执行synchronized语句。如果身体的执行永远   完成,无论是正常还是突然,解锁动作都是   在同一台显示器上自动执行。

答案 2 :(得分:0)

这是一段代码片段,我从中学习了volatile关键字:

import java.util.concurrent.TimeUnit;

public class VolatileDemo {
    private volatile static boolean stop; //remove volatile keyword to see the difference
    private static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread otherThread = new Thread(new Runnable() {
            public void run() {
                while (!stop)
                    i++;
            }
        });
        otherThread.start();
        TimeUnit.SECONDS.sleep(1);
        stop = true;// main thread stops here and should stop otherThread as well
    }
}

如果你想观察volatile的作用,尝试删除它然后按照执行,这应该是你运行这两个版本后显而易见的,但基本上这里的关键字阻止java编译器假设停止条件永远不会改变,它会每次评估条件时都要读取。干净,不是吗?

通过查看代码,问题不在于使用volatile关键字,问题是表达式i++不是原子的。它实际上是3步操作:

1) fetch value of i;
2) increment i;
3) save new value of i

当多线程发挥作用时,这些可能会或可能不会与其他线程的指令混合使用。 所以执行也可能看起来像这样:

1) T1: fetch i;
2) T2: fetch i;
3) T1: increment i;
4) T2: increment i;
5) T1: save i;
6) T2: save i;

如果i0,您认为您将2作为输出,而不是1

同步,这在没有过度使用和深思熟虑的情况下非常简单。

建议阅读synchronization

答案 3 :(得分:0)

如果两个线程都在读取和写入共享变量,那么使用volatile关键字是不够的。在这种情况下,您需要使用同步来保证变量的读取和写入是原子的。

因此,在修改i值时需要使用同步。