❓场景及设计

我们面临一个场景:需要读取某个数据,如果操作超时,则返回特定数据。这种场景在分布式系统、网络请求、数据库查询等中非常常见。

我们可以通过多种方式实现,包括使用Future、CompletableFuture、线程池配合超时控制,或者使用CountDownLatch等同步工具。下面介绍几种常见的实现方式。

设计要点:

  1. 我们需要在另一个线程中执行读取数据的任务。
  2. 主线程需要等待这个任务完成,但最多等待指定的时间。
  3. 如果超时,则中断任务(如果可能)并返回特定数据。

注意:中断任务不一定能立即停止任务,这取决于任务是否响应中断。因此,我们通常还会设置一个超时时间,避免无限期等待。

📊 方案对比

方案 优点 缺点 适用场景
Future + 超时 简单直接,Java原生支持 线程中断不保证立即停止 简单的同步调用场景
CompletableFuture(推荐) 功能丰富,组合性强 代码相对复杂 复杂的异步编排
Guava的ListenableFuture 功能完善,API友好 需要额外依赖 已使用Guava的项目
Spring的@Async + @Timeout 声明式,简洁优雅 需要Spring环境 Spring应用
手动线程管理 完全控制,理解原理 代码复杂易出错 学习理解原理
响应式 非阻塞,资源高效 学习曲线较陡 高并发I/O密集型

🎯 方案一:Future + 超时控制(最常用)

public class TimeoutDataReader {
    private final ExecutorService executor = Executors.newCachedThreadPool();
    private final String defaultValue = "DEFAULT_VALUE";
    private final long timeout = 3000; // 3秒超时

    public String readDataWithTimeout(String key) {
        Future<String> future = executor.submit(() -> readDataFromSource(key));
        
        try {
            return future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            future.cancel(true); // 重要:取消任务避免资源浪费
            return defaultValue;
        } catch (Exception e) {
            return defaultValue; // 其他异常也返回默认值
        }
    }
    
    private String readDataFromSource(String key) {
        // 模拟耗时的数据读取
        try {
            Thread.sleep(5000); // 模拟5秒耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return defaultValue;
        }
        return "Actual Data for " + key;
    }
}

✔️ 方案二:CompletableFuture(推荐 需要Java 8+)

public class CompletableTimeoutReader {
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private final String defaultValue = "DEFAULT_VALUE";

    public CompletableFuture<String> readDataAsync(String key, long timeoutMs) {
        CompletableFuture<String> dataFuture = CompletableFuture.supplyAsync(() -> readDataFromSource(key));
        CompletableFuture<String> timeoutFuture = new CompletableFuture<>();
        
        // 设置超时任务
        scheduler.schedule(() -> 
            timeoutFuture.complete(defaultValue), timeoutMs, TimeUnit.MILLISECONDS);
        
        // 任一完成即返回
        return dataFuture.applyToEither(timeoutFuture, Function.identity())
                        .exceptionally(ex -> defaultValue);
    }
    
    // 使用示例
    public String getDataWithTimeout(String key) {
        try {
            return readDataAsync(key, 3000).join();
        } catch (Exception e) {
            return defaultValue;
        }
    }
}

🔧 方案三:Guava的ListenableFuture(需Guava依赖)

public class GuavaTimeoutReader {
    private final ListeningExecutorService executor = 
        MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
    private final String defaultValue = "DEFAULT_VALUE";

    public String readDataWithTimeout(String key, long timeout, TimeUnit unit) {
        ListenableFuture<String> future = executor.submit(() -> readDataFromSource(key));
        
        try {
            // 添加超时回调
            Futures.addCallback(future, new FutureCallback<String>() {
                @Override
                public void onSuccess(String result) {
                    // 正常处理
                }
                @Override
                public void onFailure(Throwable t) {
                    // 异常处理
                }
            }, executor);
            
            return Futures.withTimeout(future, timeout, unit, executor)
                         .get(); // 这里应该用回调,简化示例用get
        } catch (TimeoutException e) {
            return defaultValue;
        } catch (Exception e) {
            return defaultValue;
        }
    }
}

🚀 方案四:Spring的@Async + @Timeout(Spring环境)

@Service
public class SpringTimeoutService {
    private final String defaultValue = "DEFAULT_VALUE";
    
    @Async
    @Timeout(duration = 3, unit = ChronoUnit.SECONDS) // Spring 6.0+
    public CompletableFuture<String> readDataAsync(String key) {
        return CompletableFuture.completedFuture(readDataFromSource(key));
    }
    
    // 使用方式
    public String getDataWithSpringTimeout(String key) {
        try {
            return readDataAsync(key)
                    .orTimeout(3000, TimeUnit.MILLISECONDS)
                    .join();
        } catch (Exception e) {
            return defaultValue;
        }
    }
}

💡 方案五:手动线程管理(测试代码, 助于理解原理)

public class ManualTimeoutReader {
    private final String defaultValue = "DEFAULT_VALUE";
    
    public String readDataManualTimeout(String key, long timeoutMs) {
        final String[] result = new String[1];
        final AtomicBoolean completed = new AtomicBoolean(false);
        final CountDownLatch latch = new CountDownLatch(1);
        
        Thread workerThread = new Thread(() -> {
            try {
                result[0] = readDataFromSource(key);
                completed.set(true);
            } finally {
                latch.countDown();
            }
        });
        
        workerThread.start();
        
        try {
            if (latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
                return result[0];
            } else {
                workerThread.interrupt(); // 尝试中断
                return defaultValue;
            }
        } catch (InterruptedException e) {
            workerThread.interrupt();
            return defaultValue;
        }
    }
}

🎪 方案六:响应式编程(WebFlux/Reactor)

@Slf4j
public class ReactiveTimeoutReader {
    private final String defaultValue = "DEFAULT_VALUE";
    
    public Mono<String> readDataReactive(String key, Duration timeout) {
        return Mono.fromCallable(() -> readDataFromSource(key))
                  .timeout(timeout)
                  .onErrorResume(throwable -> {
                      if (throwable instanceof TimeoutException) {
                          log.warn("读取数据超时,返回默认值");
                      } else {
                          log.error("读取数据异常", throwable);
                      }
                      return Mono.just(defaultValue);
                  })
                  .subscribeOn(Schedulers.boundedElastic());
    }
    
    // 使用示例
    public String getDataBlocking(String key) {
        return readDataReactive(key, Duration.ofSeconds(3))
                       .block(); // 生产环境避免block
    }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐