Тупик синхронизации цикла - PullRequest
5 голосов
/ 22 января 2011

У меня есть следующие классы в Java

public class Counter {
    private int value;

    public Counter(int value) {
        this.value = value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public void decrement() {
        this.value--;
    }
    public int getValue() {
        return this.value;
    }
}

public class Cell extends Thread {

    private Object sync;
    private Counter counter;

    public Cell(Object sync, Counter counter) {
        this.sync = sync;
        this.counter = counter;
    }

    public void run() {
        for (int r=0; r<Simulation.ROUND_NUM; r++) {

            // do something

            synchronized(counter) {
                counter.decrement();
                counter.notifyAll();
            }
            synchronized(sync) {
                try {
                    sync.wait();
                }
                catch (Exception ex) {}
            }

        }
    }
}

public class Simulation extends Thread {

    public static final int THREAD_NUM = 5;
    public static final int ROUND_NUM = 5;

    public Object sync = new Object();
    private Counter counter = new Counter(THREAD_NUM);

    public void run() {

        for (int i=0; i<THREAD_NUM; i++) {
            Cell c = new Cell(sync,counter);
            c.start();
        }

        for (int i=0; i<ROUND_NUM; i++) {
            synchronized(counter) {
                while(counter.getValue() != 0) {
                    try {
                        counter.wait();
                    }
                    catch (Exception ex) {}
                }
                counter.setValue(THREAD_NUM);
            }

            synchronized(sync) {
                sync.notifyAll();
            }
        }
    }
}

Цель состоит в том, чтобы предотвратить выполнение следующей итерации цикла в каждом потоке ячейки, пока каждый поток ячейки не будет выполнен на каждой итерации.Мое решение иногда приводит к тупику.Я не могу понять почему.Пожалуйста, помогите

Ответы [ 5 ]

5 голосов
/ 22 января 2011

Прежде всего вы можете использовать класс AtomicInteger вместо созданного вами класса Counter.Класс AtomicInteger является поточно-ориентированным, поэтому вы можете использовать атомарные действия, такие как decmentAndGet и incrementAndGet .

Для достижения функциональности ожидания завершения каждого из потоков Cell можно использовать CountDownLatch , как упомянуто в предыдущем комментарии, или даже параллельные объекты, такие как CyclicBarriers остановить выполнение, пока все потоки Ячеек не соединятся на барьере.Через некоторые из этих параллельных объектов легче управлять несколькими потоками.Использование простой синхронизации также работает, обычно требуется больше кода и размышлений, чтобы все работало хорошо.

3 голосов
/ 22 января 2011

В вашем коде, похоже, нет гарантии, что при выполнении sync.notifyAll() все потоки Cell дойдут до sync.wait().Это относится к последнему потоку Cell (пятому в вашем примере), который должен захватить блокировку на sync, чтобы дождаться ее.Но поток Simulation также пытается сделать то же самое, не убедившись, что все ждут.Это состояние гонки заставляет симуляцию иногда захватывать блокировку до того, как последняя ячейка сможет сделать то же самое и ждать.

Поскольку эта последняя ячейка не ждет, она не получает уведомления, поэтому вся вещь застревает.Вы можете проверить это, добавив System.out.println () в качестве первой строки в каждом блоке synchronized (sync) и написав соответственно «ожидание синхронизации» и «уведомление синхронизации».Когда вы уведомите об этом, вы увидите, что только 4 потока ждут синхронизации.

Чтобы все ожидали, когда симулятор уведомит вас, вложите два синхронизированных блока в Cell#run():

public class Counter {
    private int value;

    public Counter(int value) {
        this.value = value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public void decrement() {
        this.value--;
    }

    public int getValue() {
        return this.value;
    }

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

class Cell extends Thread {

    private Object sync;
    private Counter counter;

    public Cell(Object sync, Counter counter) {
        this.sync = sync;
        this.counter = counter;
    }

    public void run() {
        for (int r = 0; r < Simulation.ROUND_NUM; r++) {

            // do something

            synchronized (sync) {
                synchronized (counter) {
                    counter.decrement();
                    counter.notifyAll();
                }
                try {
                    sync.wait();
                } catch (Exception ignored) {}
            }


        }
    }
}

class Simulation extends Thread {

    public static final int THREAD_NUM = 900;
    public static final int ROUND_NUM = 30;

    public Object sync = new Object();
    private Counter counter = new Counter(THREAD_NUM);

    public void run() {

        for (int i = 0; i < THREAD_NUM; i++) {
            Cell c = new Cell(sync, counter);
            c.start();
        }

        for (int i = 0; i < ROUND_NUM; i++) {
            synchronized (counter) {
                while (counter.getValue() != 0) {
                    try {
                        counter.wait();
                    } catch (Exception ex) {
                    }
                }
                counter.setValue(THREAD_NUM);
            }

            synchronized (sync) {
                sync.notifyAll();
            }
        }
    }
}
2 голосов
/ 22 января 2011

Ваш код может зайти в тупик, потому что вы не даете никаких гарантий того, что потоки Cell будут фактически находиться в блоке wait () в момент возникновения notifyAll. Ниже приведена последовательность событий, которые могут вызвать эту проблему:

  1. Симуляция запускает все потоки и блокирует ожидание значения 0.
  2. Каждый поток в последовательности вызывает декремент, затем counter.notifyAll и затем теряет свой временной интервал
  3. Основной поток был уведомлен, просыпается, обнаруживает, что счетчик равен 0, вызывает sync.notifyAll, возвращается к началу и ждет бесконечно.
  4. Каждому потоку в последовательности дается временной интервал, он переходит в ожидание () и ожидает бесконечно.
0 голосов
/ 22 января 2011

Это не тупик.Ваш основной поток может пропустить уведомление на счетчике и застрять в counter.wait () после того, как он перечитается в 0. Используйте инструмент Jstack JDK, чтобы проанализировать, что потоки делают в такой ситуации.

0 голосов
/ 22 января 2011

Прекрасный пример! Это не тупик, так как по определению это может произойти, только когда поток удерживает более одной блокировки одновременно, а другой пытается получить одинаковые блокировки в другом порядке.
Я подозреваю, что проблема здесь вызвана ложными пробуждениями, происходящими в объектах Cell (если ложное пробуждение произошло в объекте Simulation, это не будет иметь никакого эффекта, так как wait () вызывается в цикле, который вызывает ожидание подлежит повторному вводу).
Ложное пробуждение в ячейке приведет к дополнительному уменьшению. Это, в свою очередь, заставит пройти тест while(counter.getValue() != 0).
Измените это условие на while(counter.getValue() >= 0), и «тупики» исчезнут. Пожалуйста, дайте нам знать, если это работает.

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