【问题标题】:Why volatile is not working properly为什么 volatile 无法正常工作
【发布时间】:2012-07-26 13:37:18
【问题描述】:

今天我正在使用TimerTask 创建一个超时作业,但遇到了一个新问题,我有一个static volatile boolean 变量flag。我的理解是,一旦此变量的值发生更改,所有正在运行的thread 都会通知它。但是当我运行这个程序时,我得到了以下输出,这是不可接受的。

O/P:

--------------
--------------
DD
BB
Exiting process..
CC

我的期望是我的最后一次打印应该是Exiting process.. 为什么会出现这种奇怪的行为?

我的代码是:

public class TimeOutSort {

    static volatile boolean flag = false;

    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {

                flag = true;
                System.out.println("Exiting process..");
                // System.exit(0);
            }
        }, 10 * 200);

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag)
                    System.out.println("BB");

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag)
                    System.out.println("CC");

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag)
                    System.out.println("DD");

            }
        }).start();
    }

}

编辑:我怎样才能做到这一点?

【问题讨论】:

  • 因为线程执行顺序没有保证!!
  • 您是说volatile 不起作用还是您没有正确使用它? ;)
  • 我不是在谈论排序,我只是好奇我的标志一旦变为真,为什么 while 循环中的语句会被执行?
  • @PeterLawrey,可能是,我能够找出问题所在
  • 在编辑得到回答后,您不应该真正修改问题的实质。你要求解释你观察到的行为,你得到了很好的答案;移动球门柱是不公平的。

标签: java multithreading static volatile


【解决方案1】:

volatile 几乎意味着每次线程访问一个变量时,它必须确保使用每个线程可见的版本(即没有每个线程的缓存)。

不会flag 设置为true 后强制CC 打印线程真正开始运行立即。完全有可能(尤其是在单核机器上)一个线程设置标志并且在CC打印线程甚至有机会运行之前打印消息。

另外:请注意,打印到System.out 涉及获取锁(在println() 调用中的某个位置),这可以修改测试代码的多线程行为。

【讨论】:

    【解决方案2】:

    线程可以按任意顺序执行代码

    thread BB: while (!flag) // as flag is false
    thread Main:   flag = true;
    thread Main:   System.out.println("Exiting process..");
    thread BB:     System.out.println("BB");
    

    我的期望是我的最后一次打印应该是 Exiting process..

    线程被设计为并发和独立运行。如果这始终是最后一条语句,那将是令人惊讶的,因为您在设置标志时无法确定每个线程的位置。

    【讨论】:

    • @amicngh 正确同步。在这种情况下,让TimerTask 等到其他人使用join() 完成。我建议您也点击an operating systems textbook,这就是这个主题往往被涵盖的地方。
    • 但是它们会按顺序执行。
    • @amicngh 嗯?不,我指的是一般意义上的同步,即“知道你在使用线程时到底在做什么”,而不是 Java 为此选择的关键字所暗示的“互斥”。
    【解决方案3】:

    打印“CC”的线程碰巧没有收到任何 CPU 时间,直到您的打印“退出进程...”的线程打印了那个。这是预期的行为。

    【讨论】:

    • 我怎样才能实现这种行为?
    【解决方案4】:

    它不是 volatile 不工作(如果不是,你的一些线程就不会停止)。它与不同线程中指令的执行顺序有关,这是随机的(取决于 OS 调度),除非您在中间步骤显式同步循环。

    【讨论】:

    • 随机性仍然存在,与同步无关。同步保证一次只有一个线程,但不保证线程A先执行然后线程B等,
    • @thinksteep 我指的是一般意义上的“同步”——使用闩锁、屏障或任何其他允许一个线程控制另一个线程执行的机制,而不是简单的互斥(使用“同步”块在循环内),正如您正确指出的那样,这并不能解决这里的排序问题。
    【解决方案5】:

    要为您得到的解释添加替代措辞:在您的示例输出中,打印"CC" 的线程在while (!flag)System.out.println() 行“之间”暂停(某处) .这意味着在它唤醒之后,println() 会执行 before 标志的下一次检查。 (它也不会因为你改变标志值而被唤醒,而是因为其他一些线程阻塞或用完它的时间片。)

    【讨论】:

      【解决方案6】:

      我没有测试它,但你可以这样实现它

      public class TimeOutSort {
      
      static volatile boolean flag = false;
      
      public static void main(String[] args) {
      
          Timer timer = new Timer();
          timer.schedule(new TimerTask() {
      
              @Override
              public void run() {
      
                  synchronized(flag){
                       flag = true;
                       notifyAll();
                  }
      
      
              }
          }, 10 * 200);
      
          new Thread(new Runnable() {
      
              @Override
              public void run() {
      
                  synchronized(flag){
                      if(!flag)
                      {
                          wait();
                      }
                      System.out.println("BB");
      
                  }
      
      
              }
          }).start();
      
          new Thread(new Runnable() {
      
              @Override
              public void run() {
      
                    synchronized(flag){
                      if(!flag)
                      {
                          wait();
                      }
                      System.out.println("CC");
      
                  }
              }
          }).start();
      
          new Thread(new Runnable() {
      
              @Override
              public void run() {
      
                    synchronized(flag){
                      if(!flag)
                      {
                          wait();
                      }
                      System.out.println("DD");
      
                  }
      
      
              }
          }).start();
      }
      

      }

      【讨论】:

        猜你喜欢
        • 2011-07-24
        • 2017-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-03
        • 2023-03-22
        • 1970-01-01
        相关资源
        最近更新 更多