Converting Asynchronous Calls to Synchronous in Java: Five Common Techniques
This article explains how to turn Java asynchronous invocations into synchronous ones by demonstrating five approaches—wait/notify, condition locks, Future, CountDownLatch, and CyclicBarrier—each illustrated with complete code examples and detailed execution flow.
Sunny first clarifies the difference between synchronous and asynchronous calls: a synchronous call blocks the caller until a result is returned, while an asynchronous call returns immediately and usually delivers the result via a callback.
The article then focuses on converting asynchronous Java calls to synchronous ones by blocking the calling thread until the result arrives. Five methods are presented.
0. Constructing an Asynchronous Call
A simple asynchronous model is built with an AsyncCall class that spawns a thread, sleeps for a random duration, and invokes demo.callback(res) . The BaseDemo abstract class holds an AsyncCall instance and defines an abstract callback method that concrete demos must implement.
public class AsyncCall {
private Random random = new Random(System.currentTimeMillis());
private ExecutorService tp = Executors.newSingleThreadExecutor();
// demo1,2,4,5 call method
public void call(BaseDemo demo) {
new Thread(() -> {
long res = random.nextInt(10);
try { Thread.sleep(res * 1000); } catch (InterruptedException e) { e.printStackTrace(); }
demo.callback(res);
}).start();
}
// demo3 call method
public Future
futureCall() {
return tp.submit(() -> {
long res = random.nextInt(10);
try { Thread.sleep(res * 1000); } catch (InterruptedException e) { e.printStackTrace(); }
return res;
});
}
public void shutdown() { tp.shutdown(); }
} public abstract class BaseDemo {
protected AsyncCall asyncCall = new AsyncCall();
public abstract void callback(long response);
public void call() {
System.out.println("发起调用");
asyncCall.call(this);
System.out.println("调用返回");
}
}1. Using wait and notify
The first technique employs the intrinsic lock of an object. The main thread calls demo1.lock.wait() after invoking demo1.call() , and the callback invokes lock.notifyAll() to wake the waiting thread.
public class Demo1 extends BaseDemo {
private final Object lock = new Object();
@Override
public void callback(long response) {
System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束");
synchronized (lock) { lock.notifyAll(); }
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
demo1.call();
synchronized (demo1.lock) {
try { demo1.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("主线程内容");
}
}2. Using a Condition Lock
The second method replaces the intrinsic lock with a ReentrantLock and a Condition . The main thread awaits the condition, while the callback signals it.
public class Demo2 extends BaseDemo {
private final Lock lock = new ReentrantLock();
private final Condition con = lock.newCondition();
@Override
public void callback(long response) {
System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束");
lock.lock();
try { con.signal(); } finally { lock.unlock(); }
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
demo2.call();
demo2.lock.lock();
try { demo2.con.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { demo2.lock.unlock(); }
System.out.println("主线程内容");
}
}3. Using Future
The third approach leverages Future . The asynchronous method returns a Future<Long> from a thread‑pool task; the main thread polls isDone() and then retrieves the result with get() .
public class Demo3 {
private AsyncCall asyncCall = new AsyncCall();
public Future
call() {
Future
future = asyncCall.futureCall();
asyncCall.shutdown();
return future;
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
System.out.println("发起调用");
Future
future = demo3.call();
System.out.println("返回结果");
while (!future.isDone() && !future.isCancelled());
try { System.out.println(future.get()); } catch (Exception e) { e.printStackTrace(); }
System.out.println("主线程内容");
}
}4. Using CountDownLatch
The fourth technique uses a CountDownLatch . The main thread calls await() , and the callback invokes countDown() to release the latch.
public class Demo4 extends BaseDemo {
private final CountDownLatch countDownLatch = new CountDownLatch(1);
@Override
public void callback(long response) {
System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束");
countDownLatch.countDown();
}
public static void main(String[] args) {
Demo4 demo4 = new Demo4();
demo4.call();
try { demo4.countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("主线程内容");
}
}5. Using CyclicBarrier
The final method employs a CyclicBarrier initialized with 2 parties (the async thread and the main thread). Both threads call await() and proceed once both have arrived.
public class Demo5 extends BaseDemo {
private CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
@Override
public void callback(long response) {
System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束");
try { cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); }
}
public static void main(String[] args) {
Demo5 demo5 = new Demo5();
demo5.call();
try { demo5.cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); }
System.out.println("主线程内容");
}
}All five methods share the same core idea: the calling thread blocks until the asynchronous task signals completion, either through wait/notify, condition variables, Future polling, latch countdown, or barrier synchronization.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.