【问题标题】:Volatile keyword usage in JavaJava中可变关键字的使用
【发布时间】:2015-07-13 20:11:27
【问题描述】:

我无法理解,如果我的变量既是易失的又是静态的,那么为什么线程没有反映输出中的公共共享值 最后几行的输出是: 线程正在运行 4998线程-0 线程正在运行 4999线程-0 线程正在运行 4899线程-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();
    }
    }

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    您不能对 volatile 变量使用像 i++ 这样的复合(多步)操作。

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

    【讨论】:

    • 复合是什么意思?可以请您修改需要更改的部分吗?
    • 复合操作涉及多个步骤。 Volatile 可以确保赋值和检索操作有效,但i++ 由检索、增量和赋值操作组成,总共是 3 个。您需要同步以确保其他线程不会“介于两者之间”。
    • 或者在计算机科学语言中++不是原子操作。
    • @ThomasJungblut 是的,复合操作而不是原子操作。
    • 您不能对 volatile 变量使用 i++ 之类的复合(多步)操作。 这根本不是真的。当然你可以做到。 OP 确实 做到了。我认为您的意思是,创建变量volatile 不会神奇地使使用变量线程的代码安全。
    【解决方案2】:

    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);” i 的值为 3476。 i+name 被评估为“3476Thread-1”,但此时 Thread-1 执行停止,而 Thread-0 获得了它的时间片。 Thread-0 一直执行直到完成。然后 Thread-1 再次执行。 i+name 被评估为“3476Thread-1”和调用 println 之前,我们将其保留。调用现已完成并打印,因此您会在最后看到“3476Thread-1”。 i 已由 Thread-0 增加到 5000,但这不会改变 i+name 的评估结果,该评估是在所有这些增加之前完成的。

    问题在于 i++ 和 i+name 是不同的指令,线程执行可以在它们之间暂停和恢复。为确保获得连续输出,您需要确保 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+name 之间暂停,它将位于由 synchronized(lock) 控制的临界区中。当线程 0 开始执行时,它将到达同步(锁定)指令,并且必须停止执行,直到线程 1 恢复并退出该块。因为JLS 确保:

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

    【讨论】:

      【解决方案3】:

      这是我学习 volatile 关键字用法的代码 sn-p:

      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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • Volatile 确保您读取正确的当前值,但如果两个线程读取相同的值,那么它的冲突点。
        猜你喜欢
        • 2012-12-09
        • 2016-09-06
        • 1970-01-01
        • 2013-10-23
        • 1970-01-01
        • 1970-01-01
        • 2019-06-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多