У меня есть 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();
}
}
Проблема заключается в автоматическом блокировании. Если вы измените флаг
с true на false или наоборот, вы фактически получаете совершенно новый объект Boolean
. То есть эта строка:
flag = false;
Эквивалентна:
flag = new Boolean(false);
Как только это произойдет, ваши два потока будут ссылаться на два разных Объекты Boolean
, поэтому их флаги оказываются несинхронизированными, и ни один поток не может сигнализировать другому о пробуждении.Когда OddThread
изменяет флаг EvenThread
все еще имеет старый объект флага, поэтому он не видит новое значение.
Поскольку объект Boolean
неизменяем, вам необходимо изменить свой флаг, чтобы использовать какой-либо другой изменяемый объект, который может изменять значения на месте без создания новых объектов. Это или оба класса ссылаются на общую (возможно, глобальную) переменную.
Как предлагает @erickson, вы можете использовать AtomicBoolean
, который является изменяемым. Другой сложный способ сделать это - изменить флаг
на:
boolean[] flag = new boolean[1];
И затем везде использовать флаг [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 ....
Когда вы в конечном итоге ждете, когда вы просыпаетесь, вы не переключаете флаг
и уведомить другой поток. Я советую немного реорганизовать ваш код, чтобы он выглядел как «подождите; напечатайте; уведомите».Примерно так:
synchronized (lock) {
while (flagObj.flag == false) {
lock.wait();
}
System.out.println(i);
flagObj.flag = false;
lock.notify();
}
Проблема не в автобоксе. То же самое произошло бы, даже если бы логические
примитивы использовались повсюду.
Это проблема масштаба. Каждый экземпляр потока имеет свой собственный флаг
член, и они полностью различны. Когда вы назначаете новое значение в одном потоке, другой поток его не видит.
Чтобы эта работа работала, как задумано, создайте изменяемую логическую
оболочку ( AtomicBoolean
выполнит эту работу, хотя вы не будете использовать ее свойства параллелизма в этом приложении) и передайте эту оболочку каждому потоку. Каждый поток будет изменять этот единственный объект, а не назначать новый объект своей собственной переменной.
На самом деле у вас здесь две проблемы.
1) Первый - this
if (flag == true) {
flag = false;
lock.notify();
}
Вы передаете ссылку на флаг конструктору, но затем меняете локальный флаг
каждого потока, что не повлияет на значение другого потока.
Попробуйте что-нибудь вроде
class Monitor
{
public static volatile boolean flag;
}
А затем используйте Monitor.flag
в каждом потоке.
2) Вторая проблема (после исправления 1-го) заключается в том, что каждый поток должен иметь this
synchronized(lock)
{
lock.notify();
}
в конце после цикла, потому что в противном случае один поток будет wait (), а другой поток уже выполнен.