Как сделать поочередное выполнение методов?
Я, через Lock и Condition хотел сделать так, чтобы потоки выполнялись по очереди. Сначала 1-й, потом 2-й, потом опять 1-й, 2-й и так далее.
Я пытался сделать следующим образом и в целом как бы понятно почему это не работает, но не понятно как изменить это, чтобы работало.
class SomeClass {
static Lock lock = new ReentrantLock(true);
static Condition condition = lock.newCondition();
static Condition condition1 = lock.newCondition();
public void firstMethod() throws InterruptedException, BrokenBarrierException {
while (true) {
lock.lock();
condition.await();
System.out.println("firstMethod");
System.out.println("firstMethod 1");
System.out.println("firstMethod 2");
System.out.println("secondMethod 3");
condition1.signal();
lock.unlock();
}
}
public void secondMethod() throws InterruptedException, BrokenBarrierException {
while (true) {
lock.lock();
System.err.println("secondMethod");
System.err.println("secondMethod 1");
System.err.println("secondMethod 2");
System.err.println("secondMethod 3");
condition.signal();
condition1.await();
lock.unlock();
}
}
}
public class LockExample {
public static void main(String[] args) throws InterruptedException {
SomeClass sm = new SomeClass();
Thread th = new Thread(() -> {
try {
sm.firstMethod();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread th2 = new Thread(() -> {
try {
sm.secondMethod();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
th.start();
th2.start();
}
}
Ответы (2 шт):
Тут есть несколько проблем.
Первая, это опечатка в firstMethod. Он выдает себя за второй поток тут:
System.out.println("firstMethod 2");
System.out.println("secondMethod 3");
Следующая проблема - это использования двух разных потоков вывода System.out и System.err. В зависимости от того, как вы запускаете вашу программу, где-то будет происходить слияние этих потоков вывода в один. Это может делать IDE если вывод идет в одну консоль, или если запускаете из терминала, то терминал. При этом там может быть буферизация и вывод в финальное место (после слияния) будет не в том порядке, в котором потоки выполнения писали в потоки вывода.
Чтоб устранить эту проблему, нужно использовать один поток вывода для обоих потоков выполнения.
И наконец, собственно проблема с многопоточностью и использованием Condition. Эта реализация не работает, потому что возможна такая последовательность исполнения:
- оба потока заходят в цикл
secondMethodзахватывает локsecondMethodпишет консольsecondMethodпосылает сигналcondition.signal()
Проблема в том, что firstMethod в это время не ждал в методе condition.await и сигнал от второго потока будет пропущен. Теперь два потока будут вечно ждать: firstMethod на condition.wait, а secondMethod на condition1.wait и у нас получился deadlock.
Решить это можно так, чтоб поток не продолжал выполнение, пока второй не дойдет до определенной точки. Это можно сделать с помощью CyclicBarrier:
class SomeClass {
static CyclicBarrier secondCanRun = new CyclicBarrier(2);
static CyclicBarrier firstCanRun = new CyclicBarrier(2);
public void firstMethod() throws InterruptedException, BrokenBarrierException {
while (true) {
firstCanRun.await();
System.out.println("firstMethod");
System.out.println("firstMethod 1");
System.out.println("firstMethod 2");
System.out.println("firstMethod 3");
secondCanRun.await();
}
}
public void secondMethod() throws InterruptedException, BrokenBarrierException {
firstCanRun.await();
while (true) {
secondCanRun.await();
System.out.println("secondMethod");
System.out.println("secondMethod 1");
System.out.println("secondMethod 2");
System.out.println("secondMethod 3");
firstCanRun.await();
}
}
}
Тут в точках firstCanRun.await() и secondCanRun.await() потоки будут ждать друг друга.
Еще один способ, но встроенными инструментами java:
class SomeClass2 {
static boolean run = true;
public void firstMethod() throws InterruptedException, BrokenBarrierException {
while (true) {
synchronized (this) {
while (!run) {
wait();
}
System.out.println("first");
run = false;
notify();
}
}
}
public void secondMethod() throws InterruptedException, BrokenBarrierException {
while (true) {
synchronized (this) {
while (run) {
wait();
}
System.out.println("second");
run = true;
notify();
}
}
}
}