Алгоритм Петерсона на Java?

есть пример реализации алгоритма Петерсона для взаимного исключения в Java?

5 ответов


никто здесь не предоставил правильную / безопасную реализацию этого алгоритма в Java. Я не уверен, как должно работать решение Джона W, так как у него отсутствуют части (а именно объявления ThreadLocals и объяснение того, что должно быть в его массиве-primitive booleans пока нет get() и set()).

Глава 17 спецификации языка Java объясняет модель памяти Java. Особый интерес представляет 17.4.5, который описывает происходит-перед порядок. Об этом довольно легко думать в пределах одной нити. Рассмотрим фрагмент:

int x, y, z, w;
x = 0;
y = 5;

z = x;
w = y;

все согласятся, что в конце этого фрагмента, как x и z равны 0 и как y и w равны 5. Игнорируя декларации, мы имеем здесь шесть действий:

  1. пишите на x
  2. запись в y
  3. чтение x
  4. пишите на z
  5. чтение y
  6. пишите на w

поскольку все они появляются в одном потоке, JLS говорит, что эти чтения и записи гарантированно показывают этот порядок: каждое действие n выше (потому что действия находятся в одном потоке) имеет отношение happens-before со всеми действиями m, m > n.

но как насчет разных потоков? для обычных обращений к полю нет никаких отношений, установленных между потоками. это означает, что поток A может увеличить общую переменную, а поток B может прочитать эту переменную, но не видеть новое значение. При выполнении программы в JVM распространение записи потока A может быть переупорядочено после чтения потока B.

фактически, поток A может записать в переменную x, а затем к переменной y, устанавливая связь между этими двумя действиями в потоке A. Но поток B может читать x и y и это законно для B, чтобы получить новое значение y до новое значение x появляется. Спецификация говорит:

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

как это исправить? Для нормального доступа к полю,volatile ключевое слово достаточно:

A запись в переменную volatile (§8.3.1.4) V синхронизирует со всеми последующие чтения в любой теме (где последующее определяется в соответствии к порядку синхронизации).

синхронизируется-с сильнее условие, чем происходит-раньше, и поскольку происходит-раньше является транзитивным, если поток A хочет, чтобы поток B видел его записи в x и y, ему просто нужно написать изменчивую переменную z после написания x и y. Поток B необходимо прочитать из z перед чтением x и y и он будет гарантированно видеть новые значения x и y.

в решении Габриэля мы видим эту закономерность: запись происходит в in, который не был бы видимый для других потоков, но затем запись происходит в turn, поэтому другие потоки гарантированно видят обе записи, пока они читают turn первый.

к сожалению, условие цикла while обратно: чтобы гарантировать, что поток не видит устаревшие данные для in, цикл while должен считываться из turn первый:

    // ...
    while (turn == other() && in[other()]) {
        // ...

С этим исправлением в виду, большая часть остальной части решения в порядке: в критическом разделе мы не заботимся о застарелости данных потому что мы в критической секции! Единственный другой недостаток приходит в конце: Runnable наборы in[id] к новому значению и выходу. Будет ли другой поток гарантированно видеть новое значение in[id]? Спецификация говорит нет:

окончательное действие в потоке T1 синхронизирует-с любым действием в другой поток T2, который обнаруживает, что T1 прекратил. Т2 может выполнить позвонив Т1.isAlive () или Т1.присоединяться.)(

Итак, как мы исправим это? Просто добавьте еще одну запись в turn в конце метода:

    // ...
    in[id] = false;
    turn = other();
}
// ...

так как мы переупорядочены цикл while, другой поток будет гарантированно увидеть новые ложные ценности in[id] потому что писать in[id] случается-перед тем, как писать в turn случается-перед чтением из turn случается-перед чтением из in[id].

само собой разумеется, без метрики Т комментариев, Этот метод хрупкий, и кто-то может прийти и изменить что-то и тонко нарушить правильность. Просто объявив массив volatile недостаточно хорошо: как объяснено в этой теме Билл пью (один из привести исследователей для модели памяти Java), объявляя массив volatile делает обновления массива ссылка видимым для других потоков. Обновления элементов массива не обязательно видны (следовательно, все петли, которые мы просто должны были перепрыгнуть, используя другой volatile переменной охранять доступ к элементам массива).

если вы хотите, чтобы ваш код был четким и кратким, сохраните его таким, как он есть, и измените in быть AtomicIntegerArray (используйте 0 для false, 1 для true; нет AtomicBooleanArray). Этот класс действует как массив, элементами которого являются все volatile, и поэтому разрешит все наши проблемы славно. Кроме того, вы можете просто объявить две изменчивые переменные,boolean in0 и boolean in1 и обновите их вместо использования логического матрица.


Если у вас нет какой-то конкретной потребности в агоризме Петерсона (что было бы странно при работе на языке высокого уровня, таком как Java), я предлагаю вам взглянуть на средства синхронизации, встроенные в язык.

например, вы можете найти эту главу книги на " условиях гонки и Взаимное исключение " в Java полезно:http://java.sun.com/developer/Books/performance2/chap3.pdf

в particlar:

Java обеспечивает встроенная поддержка ожидание этого "изменения состояния" через понятие условия. Состояние это немного неправильное название, однако, потому что это полностью зависит от пользователя действительно ли условие произошло. Кроме того, условие не обязательно быть конкретно истинным или ложный. Чтобы использовать условия, необходимо познакомьтесь с тремя ключевыми методами класса объектов:

• wait (): это метод используется для ожидания условия. Он вызван когда замок в настоящее время проводится для конкретного (shared) объект.

* notify(): этот метод используется для уведомления одного потока, что состояние (возможно) изменилось. Опять же, этот метод вызывается, когда замок в настоящее время проводится на конкретный объект. Только один поток можно пробудить в результате этот призыв.

• notifyAll(): этот метод используется для уведомления нескольких потоков что условие (возможно) измененный. Все запущенные потоки на время этот метод называется будет уведомления.


Я не мог найти его в интернете сам, поэтому я решил попробовать написать его:


public class Peterson implements Runnable {

    private static boolean[] in = { false, false };
    private static volatile int turn = -1;

    public static void main(String[] args) {
        new Thread(new Peterson(0), "Thread - 0").start();
        new Thread(new Peterson(1), "Thread - 1").start();
    }

    private final int id;

    public Peterson(int i) {
        id = i;
    }

    private int other() {
        return id == 0 ? 1 : 0;
    }

    @Override
    public void run() {
        in[id] = true;
        turn = other();
        while (in[other()] && turn == other()) {
            System.out.println("[" + id + "] - Waiting...");
        }
        System.out.println("[" + id + "] - Working ("
                + ((!in[other()]) ? "other done" : "my turn") + ")");
        in[id] = false;
    }
}

Не стесняйтесь комментировать, это будет понятно:)


вы действительно должны проверить книгу Искусство многопроцессорного программирования. Он очень подробно описывает различные реализации блокировки (как спин, так и блокировку). Он также просматривает различные другие типы параллельных алгоритмов (например, список пропусков). Вот фрагмент из его книги об алгоритме блокировки Петерсона

Class Peterson implements Lock{
   private final boolean []flag = new boolean[2];
   private volatile int victim;

   public void lock(){
        int i = ThreadID.get(); // ThreadID is a ThreadLocal field
        int j= 1 - i;
        flag[i] = true;    // I'm Interested
        victim = i;        // You go first
        while(flag[j] && victim == i){}; //wait
   }
   public void unlock(){
       int i = ThreadID.get();
       flag[i] = false;      //Not interested anymore
   }
 }

хотя это не paterson algo, классы AtomicBoolean и Atomic* используют подход lockless, busy loops для обновления общих данных. Они могут соответствовать вашим требованиям.