Как продемонстрировать проблемы с многопоточностью в Java? - PullRequest
29 голосов
/ 07 мая 2010

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

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

Итак, мой вопрос:

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

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

Большое спасибо за вашу помощь!

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

Ответы [ 7 ]

21 голосов
/ 07 мая 2010

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

// 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) 
        {}
    }
}

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

7 голосов
/ 07 мая 2010

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

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

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

5 голосов
/ 07 мая 2010

Наиболее распространенным примером, подчеркивающим важность использования 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, эта программа может вывести «true» в течение keepRunning через много времени после того, как keepRunning установлено в false.

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

1 голос
/ 25 июня 2010

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

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 () с оператором «все еще работающий #» потоки считывают обновленное значение для done и завершают работу.

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

1 голос
/ 07 мая 2010

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

Конечно, говоря: «Я никогда не слышал о volatile, и мои программы работали годами» - это глупый аргумент от невежества .

0 голосов
/ 16 декабря 2015

Расширение кода @ David (конфигурация Java6 64bit, Eclipse Juno SR2):

public class NoVisibility_Demonstration extends Thread {
    boolean keepRunning = true;
    public static void main(String[] args) throws InterruptedException {
        NoVisibility_Demonstration t = new NoVisibility_Demonstration();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }
    public void run() {
        int x = 10;
        while (keepRunning) 
        {
            //System.out.println("If you uncomment this line, the code will work without the visibility issue");
            x++;

        }
        System.out.println("x:"+x);
    }
}

Используя этот пример, вы можете показать оба сценария. Когда вы раскомментируете строку в цикле while в run (), проблема видимости устраняется. Причина в том, что операторы println () используют синхронизацию. Подробное обсуждение ЗДЕСЬ

0 голосов
/ 28 августа 2013
public class NoVisibility {

    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ReaderThread().start();
        number = 42;
        Thread.sleep(20000);
        ready = true;
    }
}

Выполните вызов Thread.sleep() на 20 секунд, что произойдет, если JIT сработает в течение этих 20 секунд, и это оптимизирует проверку и кеширует значение или вообще удалит условие.И поэтому код не будет виден.

Чтобы этого не произошло, вы ДОЛЖНЫ использовать volatile.

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