Проблема параллелизма потока даже внутри одной команды? - PullRequest
2 голосов
/ 01 февраля 2012

Я немного удивлен тем, что я получу, если скомпилирую и запусту следующую (ужасную несинхронизированную) программу Java SE.

public class ThreadRace {

    // this is the main class.

    public static void main(String[] args) {

        TestRunnable tr=new TestRunnable(); // tr is a Runnable.
        Thread one=new Thread(tr,"thread_one");
        Thread two=new Thread(tr,"thread_two");

    one.start();
    two.start(); // starting two threads both with associated object tr.
    }
}

class TestRunnable implements Runnable {
    int counter=0; // Both threads can see this counter.

    public void run() {
    for(int x=0;x<1000;x++) {
        counter++;
    }
        // We can't get here until we've added one to counter 1000 times.
        // Can we??

    System.out.println("This is thread "+
      Thread.currentThread().getName()+" and the counter is "+counter);
    }
}

Если я запускаю «java ThreadRace» в командной строке, то вот моя интерпретация о том, что происходит. Две новые темы созданы и запущены. Темы имеют тот же Runnable экземпляр объекта tr, и поэтому они видят тот же tr.counter. Оба новых потока добавляют один к этому счетчику 1000 раз, а затем выводят значение счетчика.

Если я запускаю это много и много раз, то обычно получаю вывод вида

This is thread thread_one and the counter is 1000
This is thread thread_two and the counter is 2000

и иногда я получаю вывод вида

This is thread thread_one and the counter is 1204
This is thread thread_two and the counter is 2000

Обратите внимание, что в последнем случае произошло то, что thread_one завершил добавление одного к счетчику 1000 раз, но thread_two начал добавлять один уже, прежде чем thread_one распечатал значение счетчика. В частности, этот вывод для меня все еще понятен.

Однако, очень редко я получаю что-то вроде

This is thread thread_one and the counter is 1723
This is thread thread_two and the counter is 1723

Насколько я понимаю, это "не может произойти". Единственный путь System.out.println() линия может быть достигнут в любом потоке, если поток закончил считать до 1000. Так что я не беспокоюсь, если один из потоков сообщает счетчик как некоторые случайное число от 1000 до 2000, но я не вижу, как оба потока могут дойти до линии System.out.println() (подразумевая, что оба цикла завершены, не так ли?) и счетчик не будет 2000 к тому времени, когда будет напечатано второе утверждение.

То, что происходит, что оба потока как-то пытаются сделать counter++ точно в то же время, а один перезаписывает другой? То есть нить может быть даже прерывается даже в середине при выполнении одного оператора ?

Ответы [ 2 ]

6 голосов
/ 01 февраля 2012

Оператор "++" не является атомарным - это не происходит за один непрерывный цикл.Подумайте об этом так:

1. Fetch the old value
2. Add one to it
3. Store the new value back

Итак, представьте, что вы получите следующую последовательность:

Thread A: Step 1
Thread B: Step 1
Thread A: Step 2
Thread B: Step 2
Thread A: Step 3
Thread B: Step 3

Оба потока считают, что они увеличили переменную, но ее значение увеличилось только на единицу.!Вторая операция «возврата» эффективно отменяет результат первой.

Теперь, по правде говоря, когда вы добавляете несколько уровней кеша, на самом деле могут происходить куда более странные вещи;но это простое объяснение для понимания.Вы можете устранить подобные проблемы, синхронизируя доступ к переменной: либо весь метод run(), либо внутри цикла с использованием синхронизированного блока.Как предполагает Джон, вы также можете использовать некоторые из более изящных инструментов в java.util.concurrent.atomic.

4 голосов
/ 01 февраля 2012

Это абсолютно может произойти.counter++ не является атомарной операцией.Рассмотрим это как:

int tmp = counter;
tmp++;
counter = tmp;

Теперь представьте, что два потока одновременно выполняют этот код во времени:

  • Оба читают счетчик
  • Оба увеличивают свою локальную копию(От 0 до 1)
  • Оба пишут 1 в counter

Именно из-за этого существует java.util.concurrent.atomic.Измените свой код на:

class TestRunnable implements Runnable {
    private final AtomicInteger counter = new AtomicInteger();

    public void run() {
      for(int x=0;x<1000;x++) {
        counter.incrementAndGet();
      }

      System.out.println("This is thread "+
        Thread.currentThread().getName()+" and the counter is " + counter.get());
   }
}

Этот код безопасен.

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