У меня есть следующий код, где zero (), even (), odd () должны вызываться тремя отдельными потоками и вызываться в такой последовательности: zero () - odd () - zero ( ) - четное (), пока n не будет достигнуто.
public class ZeroEvenOdd {
private int n;
private volatile int start = 1;
private Lock lock = new ReentrantLock();
private Condition zero = lock.newCondition();
private Condition even = lock.newCondition();
private Condition odd = lock.newCondition();
public ZeroEvenOdd(int n) {
this.n = n;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
while (start <= n) {
printNumber.accept(0);
if (start % 2 == 0) {
even.signal();
} else {
odd.signal();
}
zero.await();
System.out.println("zero awake");
}
odd.signal();
even.signal();
} finally {
lock.unlock();
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
while (start <= n) {
if (start % 2 != 0) {
even.await();
} else {
printNumber.accept(start++);
zero.signal();
}
}
} finally {
lock.unlock();
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
while (start <= n) {
if (start % 2 == 0) {
odd.await();
} else {
printNumber.accept(start++);
zero.signal();
}
}
} finally {
lock.unlock();
}
}
}
А вот тестовый пример:
public static void main(String[] args) {
IntConsumer consumer = new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
};
ZeroEvenOdd zeroEvenOdd = new ZeroEvenOdd(4);
Thread thread1 = new Thread(() -> {
try {
zeroEvenOdd.zero(consumer);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
zeroEvenOdd.odd(consumer);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread3 = new Thread(() -> {
try {
zeroEvenOdd.even(consumer);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread3.start();
}
В идеале, когда n = 5, он должен печатать 0102030405, однако при запуске тестового примера иногда я печатаю 012030405, что означает, что когда start == 2, нулевое условие не было пробуждено, но вместо этого условие даже пробуждалось. Как это случилось? Если я немного изменил код, добавив отдельный флаг, тогда проблема будет решена:
public class ZeroEvenOdd {
private int n;
private volatile int start = 1;
private volatile int who;
private Lock lock = new ReentrantLock();
private Condition zero = lock.newCondition();
private Condition even = lock.newCondition();
private Condition odd = lock.newCondition();
public ZeroEvenOdd(int n) {
this.n = n;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
while (start <= n) {
if (who!=0) {
zero.await();
}
printNumber.accept(0);
if (start % 2 == 0) {
who=2;
even.signal();
} else {
who=1;
odd.signal();
}
zero.await();
}
odd.signal();
even.signal();
} finally {
lock.unlock();
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
while (start <= n) {
if (who!=2) {
even.await();
} else {
printNumber.accept(start++);
who=0;
zero.signal();
}
}
} finally {
lock.unlock();
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
while (start <= n) {
if (who!=1) {
odd.await();
} else {
printNumber.accept(start++);
who=0;
zero.signal();
}
}
} finally {
lock.unlock();
}
}
}
Я просто не вижу механики, лежащей в основе этого явления. Пожалуйста, помогите мне прояснить эту проблему.