在 Java 中,要在主线程中获取异步请求的结果,可以使用多种方式。以下是几种常见的实现方法:
使用Future和ExecutorService
这是 Java 并发包中提供的标准方式,通过Future来获取异步任务的结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MainThreadAsync {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
// 提交异步任务
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
// 模拟异步请求(比如网络请求)
Thread.sleep(2000); // 模拟耗时操作
System.out.println("异步任务执行完毕");
return "异步请求返回的数据";
}
});
// 主线程可以做其他事情
System.out.println("主线程正在处理其他任务...");
// 阻塞等待异步结果(get()方法会阻塞直到结果返回)
String result = future.get();
System.out.println("获取到异步结果: " + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
executor.shutdown();
}
}
}
结果
主线程正在处理其他任务...
异步任务执行完毕
获取到异步结果: 异步请求返回的数据
使用CompletableFuture(Java 8 + 推荐)
CompletableFuture提供了更灵活的异步编程方式,支持链式调用和非阻塞回调。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
// 发起异步请求
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
// 模拟异步请求
Thread.sleep(2000);
System.out.println("异步请求完成");
return "异步请求返回的数据";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
// 主线程可以做其他事情
System.out.println("主线程正在处理其他任务...");
try {
// 阻塞获取结果
String result = future.get();
System.out.println("获取到异步结果: " + result);
// 或者使用非阻塞的回调方式
future.thenAccept(result1 -> {
System.out.println("回调方式获取到异步结果: " + result1);
});
// 等待异步任务完成 这里必须等待,否则主线程执行完毕,程序结束,但是异步线程没有执行完成。
future.join();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
主线程正在处理其他任务...
异步请求完成
获取到异步结果: 异步请求返回的数据
回调方式获取到异步结果: 异步请求返回的数据
使用回调接口(传统方式)
自定义回调接口,当异步任务完成时主动通知主线程。
import java.util.concurrent.CountDownLatch;
// 定义回调接口
interface Callback {
void onSuccess(String result);
void onFailure(Exception e);
}
public class AsyncResultInMainThread {
// 用于存储异步结果
private static String asyncResult;
// 用于存储可能发生的异常
private static Exception asyncException;
public static void main(String[] args) {
// 创建CountDownLatch,用于主线程等待异步结果
CountDownLatch latch = new CountDownLatch(1);
System.out.println("主线程:发起异步请求");
// 发起异步请求
asyncRequest(new Callback() {
@Override
public void onSuccess(String result) {
// 这个方法在异步线程中执行,仅用于存储结果
asyncResult = result;
latch.countDown(); // 通知主线程结果已准备好
}
@Override
public void onFailure(Exception e) {
// 存储异常信息
asyncException = e;
latch.countDown(); // 通知主线程发生异常
}
});
// 主线程可以先处理其他不需要异步结果的任务
System.out.println("主线程:处理其他任务...");
try {
// 主线程等待异步结果,最多等待5秒
latch.await();
// 以下代码在主线程中执行
if (asyncException != null) {
System.out.println("主线程:异步请求发生错误");
asyncException.printStackTrace();
} else {
System.out.println("主线程:获取到异步结果 - " + asyncResult);
// 在这里可以安全地在主线程中使用异步结果
processResultInMainThread(asyncResult);
}
} catch (InterruptedException e) {
System.out.println("主线程:等待被中断");
Thread.currentThread().interrupt();
}
System.out.println("主线程:任务完成");
}
// 在主线程中处理结果的方法
private static void processResultInMainThread(String result) {
System.out.println("主线程处理结果:" + result.toUpperCase());
}
// 模拟异步请求方法
private static void asyncRequest(Callback callback) {
new Thread(() -> {
try {
System.out.println("异步线程:执行网络请求...");
// 模拟网络请求耗时
Thread.sleep(2000);
// 模拟返回结果
String result = "来自服务器的响应数据";
System.out.println("异步线程:请求完成,准备通知主线程");
// 调用回调,通知结果(此时仍在异步线程中)
callback.onSuccess(result);
} catch (InterruptedException e) {
callback.onFailure(new Exception("请求被中断", e));
}
}).start();
}
}
主线程:发起异步请求
主线程:处理其他任务...
异步线程:执行网络请求...
异步线程:请求完成,准备通知主线程
主线程:获取到异步结果 - 来自服务器的响应数据
主线程处理结果:来自服务器的响应数据
主线程:任务完成
第三种方式是否线程安全
Java 内存模型(JMM)中的可见性问题。
问题:Java 中,每个线程可能会将共享变量(如asyncResult和asyncException)的副本保存到自己的工作内存中,而不是直接操作主内存中的变量:
异步线程更新了asyncResult的值,但可能只更新了自己工作内存中的副本,尚未同步到主内存。
主线程可能仍在读取自己工作内存中的旧值,导致无法看到最新结果。
在前面的代码中,CountDownLatch的countDown()和await()方法会建立线程间的 happens-before 关系,这保证了:
异步线程在countDown()之前的所有操作(包括对asyncResult的赋值)
一定对执行await()之后的主线程可见
这是因为CountDownLatch内部使用了同步机制(如AbstractQueuedSynchronizer),会触发内存屏障(Memory Barrier),强制线程将工作内存中的数据刷新到主内存,或从主内存重新加载数据。
选择建议:
如果使用 Java 8 及以上版本,推荐使用CompletableFuture,它功能强大且代码简洁
Future方式适合简单场景,但功能相对有限
回调接口方式更适合传统项目或需要自定义通知机制的场景
2800

被折叠的 条评论
为什么被折叠?



