иллюстрирует изменчивость: является ли этот код потокобезопасным? - PullRequest
3 голосов
/ 21 октября 2011

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

Но я не очень привык использовать volatile.Идея следующего кода состоит в том, чтобы вызвать бесконечный цикл, если volatile опущен, и быть полностью поточно-ориентированным, если присутствует volatile.Является ли следующий код потокобезопасным?У вас есть какой-нибудь другой реалистичный и короткий пример кода, который использует volatile и без него даст явно неправильный результат?

Вот код:

public class VolatileTest implements Runnable {

    private int count;
    private volatile boolean stopped;

    @Override
    public void run() {
        while (!stopped) {
            count++;
        }
        System.out.println("Count 1 = " + count);
    }

    public void stopCounting() {
        stopped = true;
    }

    public int getCount() {
        if (!stopped) {
            throw new IllegalStateException("not stopped yet.");
        }
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileTest vt = new VolatileTest();
        Thread t = new Thread(vt);
        t.start();
        Thread.sleep(1000L);
        vt.stopCounting();
        System.out.println("Count 2 = " + vt.getCount());
    }
}

Ответы [ 6 ]

10 голосов
/ 21 октября 2011

Виктор прав, есть проблемы с вашим кодом: атомарность и видимость.

Вот мое издание:

    private int count;
    private volatile boolean stop;
    private volatile boolean stopped;

    @Override
    public void run() {
        while (!stop) {
            count++; // the work
        }
        stopped = true;
        System.out.println("Count 1 = " + count);
    }

    public void stopCounting() {
        stop = true;
        while(!stopped)
           ; //busy wait; ok in this example
    }

    public int getCount() {
        if (!stopped) {
            throw new IllegalStateException("not stopped yet.");
        }
        return count;
    }

}

Если поток отмечает, что stopped==true, это гарантирует, чторабота завершается, и результат видимА происходит до действия Б;записи в A видны B.

1 голос
/ 28 марта 2014

Упрощение примера @Elf, где другой поток никогда не получит значение, которое было обновлено другим потоком.Удаление System.out.println, поскольку внутри println и out есть синхронизированный код, является статическим, что помогает другому потоку получить последнее значение переменной-флага.

public class VolatileExample implements Runnable {
   public static boolean flag = true; 


  public void run() {
     while (flag);
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new VolatileExample());
    thread.start();
    Thread.sleep(1000L);
    flag = false;
    thread.join();
  }
}
1 голос
/ 21 октября 2011

Мне всегда было трудно убедительно проиллюстрировать проблемы параллелизма: хорошо, хорошо, все хорошо о случается до и прочем, но почему это должно волновать? Есть ли реальная проблема? Есть много плохо написанных, плохо синхронизированных программ, и они все еще работают большую часть времени.

Раньше я находил курорт в " работах" большую часть времени VS работает", но, честно говоря, это слабый подход. Так что мне нужен был пример, который сделал бы разницу очевидной - и, желательно, болезненной.

Итак, вот версия, которая действительно показывает разницу:

public class VolatileExample implements Runnable {
    public static boolean flag = true; // do not try this at home

    public void run() {
        long i = 0;
        while (flag) {
            if (i++ % 10000000000L == 0)
                System.out.println("Waiting  " + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new VolatileExample());
        thread.start();
        Thread.sleep(10000L);
        flag = false;
        long start = System.currentTimeMillis();
        System.out.println("stopping " + start);
        thread.join();
        long end = System.currentTimeMillis();
        System.out.println("stopped  " + end);
        System.out.println("Delay: " + ((end - start) / 1000L));
    }
}

Простой прогон показывает:

Waiting  1319229217263
stopping 1319229227263
Waiting  1319229242728
stopped  1319229242728
Delay: 15

То есть требуется более десяти секунд (15 здесь) для работающего потока, чтобы заметить, что было любое изменение.

С volatile вы получаете:

Waiting  1319229288280
stopping 1319229298281
stopped  1319229298281
Delay: 0

то есть, выход (почти) немедленно. Разрешение currentTimeMillis составляет около 10 мс, поэтому разница составляет более 1000 раз.

Обратите внимание, что это была версия Apple JK (ранее) Sun с опцией -server. Было добавлено 10-секундное ожидание, чтобы JIT-компилятор узнал, что цикл достаточно горячий, и оптимизировал его.

Надеюсь, это поможет.

0 голосов
/ 28 июля 2012

Неверный код, с которым мы не можем принять x = 1 также, если y уже равен 2:

Class Reordering {
  int x = 0, y = 0;
  public void writer() {
    x = 1;
    y = 2;
  }

  public void reader() {
    int r1 = y;
    int r2 = x;
  }
}

Пример использования изменяемого ключевого слова:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

Источник: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

0 голосов
/ 21 октября 2011

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

0 голосов
/ 21 октября 2011

ОБНОВЛЕНИЕ Мой ответ неправильный, см. Ответ от неопровержимого.


Это не поточно-ориентированный, поскольку доступ к countне есть только один поток писателя.Если существует другой поток записи, значение count станет несовместимым с количеством обновлений.

Видимость значения count для основного потока обеспечивается проверкой stopped volatile внутри getCount метода,Это то, что называется piggybacking on synchronization в Concurrency in practice книге.

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