Как продемонстрировать проблемы видимости многопоточности Java?

Если к переменным в Java получают доступ от нескольких потоков, нужно гарантировать, что они безопасно публикуются. Это обычно означает использовать synchronizedили volatile.

У меня есть впечатление, что некоторые мои коллеги не относятся к этой проблеме серьезно, так как они "никогда не слышали о volatile прежде и их программы работали в течение многих лет".

Таким образом, мой вопрос:

Может кто-то предоставлять программу/отрывок Java в качестве примера, которая надежно показывает проблемы видимости данных.

Я думаю, запуская программу и видя неожиданный NPE или утрачиваю новизну, значение переменной помогло бы больше, чем просто теоретические объяснения, которые не могут быть продемонстрированы.

Большое спасибо за Вашу справку!

Обновление: Только к акценту точка снова. Я считал Java Concurreny на практике и знаю примеры, которые теоретически имеют проблемы видимости. То, что я ищу, является способом на самом деле продемонстрировать их. Я не уверен, что это на самом деле возможно, но возможно существует jvm конфигурация или что-то подобное, которое позволяет ее.

30
задан Joe23 7 May 2010 в 08:57
поделиться

5 ответов

Изменив пример здесь путем удаления операций, я пришел к примеру, который постоянно не работает в моей среде (поток никогда не перестает выполняться).

// Java environment:
// java version "1.6.0_0"
// OpenJDK Runtime Environment (IcedTea6 1.6.1) (6b16-1.6.1-3ubuntu3)
// OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)
public class Test2 extends Thread {
    boolean keepRunning = true;
    public static void main(String[] args) throws InterruptedException {
        Test2 t = new Test2();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }
    public void run() {
        while (keepRunning) 
        {}
    }
}

Обратите внимание, что проблемы такого типа сильно зависят от компилятора/runtime/системы. В частности, компилятор может решить добавить инструкции для чтения переменной из памяти, даже если она не является волатильной - так что код будет работать, - vm и jit могут оптимизировать чтение из памяти и использовать только регистры, и даже процессор может изменить порядок инструкций - это не повлияет на данный случай, но в других многопоточных случаях это может повлиять на воспринимаемое состояние других потоков, если изменяется более одной переменной.

21
ответ дан 28 November 2019 в 00:16
поделиться

Заставьте их прочитать книгу Java Concurrency in Practice гуру параллелизма Java Брайана Гетца. Эта книга - обязательное чтение для любого, кому приходится писать серьезное параллельное программное обеспечение на Java!

Конечно, говорить "Я никогда не слышал о volatile, а мои программы работают годами" - это тупой аргумент от незнания.

1
ответ дан 28 November 2019 в 00:16
поделиться

Может ли кто-нибудь предоставить пример программы / фрагмента Java, который надежно показывает проблемы с видимостью данных.

Нет, не существует надежного примера, который показывает проблемы с видимостью данных.

Причина в том, что любое допустимое выполнение программы с volatile также является допустимым выполнением той же программы без volatile . (Однако обратное, очевидно, неверно!)

7
ответ дан 28 November 2019 в 00:16
поделиться

Наиболее распространенным примером, подчеркивающим важность использования volatile, является пример while (keepRunning) :

public class Test extends Thread {

    boolean keepRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }

    public void run() {
        while (keepRunning) 
            System.out.println(System.currentTimeMillis() + ": " + keepRunning);
    }
}

Поскольку keepRunning ] может (допустимо) храниться в кэше потока, выполняющего цикл while, эта программа может выводить «истина» для keepRunning еще долгое время после того, как для keepRunning установлено значение false.

Однако обратите внимание на , что не существует надежного способа выявления условий гонки. (См. Мой другой ответ.) Этот пример может раскрыть его при определенных обстоятельствах на определенных комбинациях оборудования / операционной системы / jvm.

6
ответ дан 28 November 2019 в 00:16
поделиться

У меня есть для вас фрагмент кода:

package test;

public class LoopTest {

 private boolean done = false;

 /**
  * @param args
  */
 public void start() {
  System.out.println(System.getProperty("java.vm.name"));
  System.out.println(System.getProperty("java.version"));
  for (int i = 0; i < 100; i++) {
   startNewThread();
  }

  try {
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  done = true;
  System.out.println("forcing end");
 }

 private void startNewThread() {
  new Thread(new Runnable() {

   public void run() {
    long i = 0;
    while(!done) {
     i++;
     if(i % 100L == 0) {
      System.out.println("still working " + i);
     }
    }
    System.out.println("ending " + i);
   }

  }).start();
 }

 public static void main(String[] args) {
  new LoopTest().start();
 }

}

Этот пример, запущенный в режиме сервера JVM, сгенерировал следующие выходные данные на моей машине:

..
..
ending 14100
still working 14800
ending 14800
still working 26500
ending 26500
still working 18200
ending 18200
still working 9400
ending 9400
still working 1300
ending 1300
still working 59500
ending 59500
still working 1700
still working 75400
ending 75400
still working 33500
ending 33500
still working 36100
ending 36100
still working 121000
ending 121000
still working 3000
ending 3000
ending 1700
still working 5900
ending 5900
still working 7800
ending 7800
still working 7800
ending 7800
still working 6800
ending 6800
still working 5300
ending 5300
still working 9100
still working 10600
ending 10600
still working 9600
ending 9600
still working 10000
ending 10000
ending 9100
still working 1700
ending 1700
..
..

Посмотрите на " Завершающие операторы # ": все они имеют число, кратное 100, что, я думаю, маловероятно. Моя интерпретация заключается в том, что существует проблема видимости, из-за которой потоки все еще читают done == false, хотя он уже был обновлен до true. После вызова синхронизированного метода System.out.println () с оператором "still working #" потоки считывают обновленное значение для done и завершают работу.

Или кто-нибудь видит ошибку в моем коде / интерпретации?

1
ответ дан 28 November 2019 в 00:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: