Тупик - PullRequest
       21

Тупик

7 голосов
/ 17 февраля 2010

У меня 2 темы. Одна нить печатает нечетные числа, а вторая нить печатает четные числа. Теперь я должен выполнить потоки поочередно, чтобы я мог вывести 1,2,3,4,5,6, .....

Я написал программу для этого, и это приводит к тупику. Может кто-нибудь объяснить, в чем проблема с кодом и как его исправить?

class BooleanObject {
boolean flag;
BooleanObject(boolean flag) {
    this.flag = flag;
}
}
class EvenThread extends Thread {
Object lock;
BooleanObject flagObj;
EvenThread(Object o, BooleanObject flag) {
    lock = o;
    this.flagObj = flag;
}
public void run() {
    for (int i=2;i<100;i+=2) {
        synchronized(lock) {
            if (flagObj.flag == false) {
                flagObj.flag = true;
                lock.notify();
            }
            else {
                try {
                    while (flagObj.flag == true) {
                        lock.wait();
                    }
                }
                catch (InterruptedException e) {

                }
            }
            System.out.println(i);
        }
    }
}
}

class OddThread extends Thread {
Object lock;
BooleanObject flagObj;
OddThread(Object o, BooleanObject flag) {
    lock = o;
    this.flagObj = flag;
}
public void run() {
    for (int i=1;i<100;i+=2) {
        synchronized(lock) {
            if (flagObj.flag == true) {
                flagObj.flag = false;
                lock.notify();
            }

            else {
                try {
                    while(flagObj.flag == false) {
                        lock.wait();
                    }
                }
                catch (InterruptedException e) {

                }
            }
            System.out.println(i);
        }
    }
}
}

public class EvenOddThreads {
public static void main(String[] args) {
    Object obj = new Object();
    BooleanObject flagObj = new BooleanObject(true);
    EvenThread et = new EvenThread(obj,flagObj);
    OddThread ot = new OddThread(obj,flagObj);

    et.setName("even thread");
    ot.setName("odd thread");

    et.start();
    ot.start();
}
}

Ответы [ 3 ]

9 голосов
/ 17 февраля 2010

Проблема с автобоксом. Когда вы изменяете flag с истинного на ложное или наоборот, вы фактически получаете совершенно новый Boolean объект. То есть эта строка:

flag = false;

Эквивалентно:

flag = new Boolean(false);

Как только это происходит, ваши два потока ссылаются на два разных объекта Boolean, поэтому их флаги оказываются несинхронизированными, и ни один из потоков не может дать сигнал другому проснуться. Когда OddThread изменяет флаг, EvenThread все еще имеет старый объект флага, поэтому он не видит новое значение.

Поскольку объект Boolean является неизменным, вам необходимо изменить свой флаг, чтобы использовать какой-либо другой изменяемый объект, который может изменять значения на месте, не создавая новые объекты. То есть, или оба класса ссылаются на общую (возможно, глобальную) переменную.

Как @erickson предлагает вам использовать AtomicBoolean, который является изменяемым. Еще один хитрый способ сделать это - изменить flag на:

boolean[] flag = new boolean[1];

А затем используйте flag[0] везде. Тогда оба потока смогут изменять flag[0], всегда ссылаясь на один и тот же boolean[] объект массива. У вас не будет проблемы с автобоксом.

...

Кроме того, неплохо бы обернуть любой вызов wait() в цикл. wait() может подвергаться ложным пробуждениям, когда вызов возвращается, хотя на самом деле никто не звонил notify(). Чтобы обойти это, вы всегда должны проверять состояние охраны после пробуждения, чтобы убедиться, что пробуждение не является ложным.

while (flag == true) {
    lock.wait();
}

Обновление

Я внес изменения на основе ваших предложений выше; но я не получаю ожидаемый результат. Я вставлю модифицированный код выше. Вот результат, который я получаю 1 2 4 3 5 6 8 7 9 10 11 13 12 15 17 14 ....

Когда вы в конечном итоге ждете, когда вы проснулись, вы не переключаете flag и не уведомляете другой поток. Я советую немного реорганизовать ваш код, чтобы он выглядел как «подожди; напечатай; уведомь». Что-то вроде:

synchronized (lock) {
    while (flagObj.flag == false) {
        lock.wait();
    }

    System.out.println(i);

    flagObj.flag = false;
    lock.notify();
}
4 голосов
/ 17 февраля 2010

Проблема не в автобоксировании. То же самое произошло бы, даже если бы повсюду использовались boolean примитивы.

Это проблема масштаба. Каждый экземпляр потока имеет свой собственный элемент flag, и они совершенно разные. Когда вы назначаете новое значение в одном потоке, другой поток не видит его.

Чтобы сделать эту работу по назначению, создайте оболочку mutable boolean (AtomicBoolean сделает эту работу, хотя вы не будете использовать ее свойства параллелизма в этом приложении) и передать эту обертку в каждую нить. Каждый поток будет мутировать этот единственный объект, а не назначать новый объект своей собственной переменной.

0 голосов
/ 17 февраля 2010

У вас тут две проблемы.

1) Первый - это

if (flag == true) {
    flag = false;
    lock.notify();
}

Вы передаете ссылку флага в конструктор, но затем изменяете локальный flag каждого потока, что не повлияет на значение другого потока.

Попробуйте что-то вроде

class Monitor
{
    public static volatile boolean flag;
}

А затем используйте Monitor.flag в каждом потоке.

2) Вторая проблема (после исправления первого) состоит в том, что каждый поток должен иметь этот

synchronized(lock)
{
    lock.notify();
}

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

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