В данной статье я бы хотел обсудить с уважаемым сообществом методы синхронизации потоков в Java. Чтобы не увязать в теории, попробуем рассмотреть синхронизацию потоков в задаче преобразования асинхронных методов в синхронные.
Дано
- JavaSE 6+
- библиотека с асинхронным методом
void A.asyncMethod(Callback callback);
- метод, который нужно переопределить, и вернуть из него результат
@Override
Object overridenMethod() {
return syncMethod();
}
Задача
Преобразовать асинхронный вызов метода в синхронный с возвращением результата.
Решение
Создать метод-обертку для преобразования асинхронного вызова в синхронный.
Метод 1 — блокировка на объекте
Object syncMethod() {
final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(1);
try {
A.asyncMethod(new Callback() {
@Override
public void onFinish(Object o) {
try {
queue.put(o);
} catch (InterruptedException e) {}
synchronized (queue) {
queue.notify();
}
}
});
if (queue.size() == 0) {
synchronized (queue) {
queue.wait();
}
}
return queue.poll();
} catch (InterruptedException e) {
return null;
}
}
Создаем блокирующую очередь для возвращения результата. Вызываем асинхронный метод и ждем его выполнения, если у нас еще нет результата в очереди. По завершению выполнения метода, запихиваем объект в очередь и оповещаем ожидающий поток о том что можно продолжить исполнение. Возвращаем объект из очереди. Блокировка проводится на объекте очереди.
Метод 2 — с помощью CountDownLatch
Object syncMethod() {
final CountDownLatch done = new CountDownLatch(1);
final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(1);
try {
A.asyncMethod(new Callback() {
@Override
public void onFinish(Object o) {
try {
queue.put(o);
} catch (InterruptedException e) {}
done.countDown();
}
});
done.await();
return queue.poll();
} catch (InterruptedException e) {
return null;
}
}
Вместо блокировки на объекте используем объект класса CountDownLatch. При его создании задаем начальное значение счетчика 1. После вызова метода ожидаем пока значение счетчика будет 0. По завершению асинхронного метода понижаем счетчик, он становится 0, главная нить просыпается и мы возвращаем объект из очереди.
Метод 3 — с помощью CyclicBarrier
Object syncMethod() {
final CyclicBarrier barrier = new CyclicBarrier(2);
final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(1);
try {
A.asyncMethod(new Callback() {
@Override
public void onFinish(Object o) {
try {
queue.put(o);
} catch (InterruptedException e) {}
try {
barrier.await();
} catch (InterruptedException e) {}
catch (BrokenBarrierException e) {}
}
});
barrier.await();
return queue.poll();
} catch (InterruptedException e) {
return null;
} catch (BrokenBarrierException e) {
return null;
}
}
При создании экземпляра класса CyclicBarrier указывается количество нитей, которые одновременно могут пройти этот барьер. После вызова асинхронного метода ждем второй нити, которая вызовет barrier.await()
. По завершению метода, нить вызвавшая тот же метод у барьера, становится второй, барьер достигает своего ограничения и восстанавливает выполнение всех приостановенных нитей.
Итого
В ходе данной статьи мы узнали, на примере практической задачи преобразования асинхронных методов в синхронные, средства для синхронизации потоков в Java 6.
Дополнения, здравая критика и обсуждение недостатков/достоинств этих методов приветствуются.
Автор: WToll