Почему ключевое слово volatile не работает должным образом в коде Java?

#java #volatile

#java #volatile

Вопрос:

Я изучаю знания параллелизма в Java. Что касается ключевого слова volatile, оно должно сделать переменную видимой в разных потоках. Но в моем демонстрационном коде, похоже, оно работает не так, как ожидалось. Метод run() в классе, который реализует Runnable , никогда не остановится.

 public class VisibilityDemo {

  public static void main(String[] args) throws InterruptedException {
    TimeConsumingTask timeConsumingTask = new TimeConsumingTask();
    Thread thread = new Thread(new TimeConsumingTask());
    thread.start();
    Thread.sleep(3000);
    timeConsumingTask.cancel();
  }
}

class TimeConsumingTask implements Runnable {
  private volatile boolean toCancel = false;

  @Override
  public void run() {
    while (! toCancel) {
      System.out.println("executing...");
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    if (toCancel) {
      System.out.println("Task was canceled.");
    } else {
      System.out.println("Task done.");
    }
  }

  public void cancel() {
    toCancel = true;
    System.out.println(this   " canceled.");
  }
}
 

Комментарии:

1. timeConsumingTask никогда не запускается.

2. «Что касается ключевого слова volatile, оно должно делать переменную видимой в разных потоках».: Нет, volatile означает, что эта переменная считывается не из кэша, а из основной памяти.

3. @gkhaos — Это сложнее, чем это.

4. @StephenC действительно, переменные, характеризуемые изменчивостью, не являются целью оптимизации компилятора (что-нибудь еще?). Я хотел сказать, что «volatile» ничего не говорит о доступности (например, частной или общедоступной).

Ответ №1:

В вашем основном методе у вас есть два экземпляра вашей задачи:

   public static void main(String[] args) throws InterruptedException {
    TimeConsumingTask timeConsumingTask = new TimeConsumingTask(); //<-- one
    Thread thread = new Thread(new TimeConsumingTask()); //<-- two
    thread.start();
    Thread.sleep(3000);
    timeConsumingTask.cancel(); //<-- cancel() on first 
  }
}
 

Вы передаете одно Thread из них конструктору, а затем вызываете cancel другое. Вам нужно вызвать cancel экземпляр, переданный Thread , например, так:

   public static void main(String[] args) throws InterruptedException {
    TimeConsumingTask timeConsumingTask = new TimeConsumingTask(); 
    Thread thread = new Thread(timeConsumingTask); //<-- difference here
    thread.start();
    Thread.sleep(3000);
    timeConsumingTask.cancel();
  }
}