CompletableFuture以非阻塞的方式执行任务,并在任务完成后进行结果处理

CompletableFuture以非阻塞的方式执行任务,并在任务完成后进行结果处理

采用CompletableFuture实现异步并行检查,相比传统的线程池+Future组合,它能更简洁地实现"等待所有任务完成+汇总结果"的逻辑,代码更优雅且易于维护。

二、核心代码实现

下面是完整的端口检查工具类实现,包含单个端口检查和多端口并发检查两个核心方法:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 并发端口检查工具类
 */
public class PortChecker {
    // 自定义线程池:根据端口检查的特性,使用固定大小线程池
    private static final Executor executor = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors() * 2,
            r -> {
                Thread thread = new Thread(r);
                thread.setName("port-checker-thread-" + thread.getId());
                thread.setDaemon(true); // 守护线程,避免影响主程序退出
                return thread;
            }
    );

    /**
     * 并发检查多个端口是否开放
     *
     * @param ip    要检查的IP地址
     * @param ports 要检查的端口列表,逗号分隔(如"80,8080,443")
     * @return 如果所有端口都无法连接,则返回true;如果任何一个端口可以连接,则返回false
     * @throws BusinessException 当存在开放端口时抛出业务异常
     */
    public boolean validatePorts(String ip, String ports) {
        // 1. 解析端口列表
        String[] portArray = ports.split(",");
        if (portArray.length == 0) {
            throw new IllegalArgumentException("端口列表不能为空");
        }

        // 2. 为每个端口创建异步检查任务
        List<CompletableFuture<Boolean>> futures = Arrays.stream(portArray)
                .map(portStr -> {
                    try {
                        int port = Integer.parseInt(portStr.trim());
                        // 验证端口有效性
                        if (port < 1 || port > 65535) {
                            throw new IllegalArgumentException("无效端口号: " + port);
                        }
                        return checkPort(ip, port);
                    } catch (NumberFormatException e) {
                        throw new IllegalArgumentException("端口格式错误: " + portStr, e);
                    }
                })
                .collect(Collectors.toList());

        // 3. 等待所有异步任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        // 4. 汇总所有任务结果
        List<Boolean> results = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        // 5. 判断结果:只要有一个端口开放(返回false),则校验失败
        if (results.contains(Boolean.FALSE)) {
            throw new BusinessException(StrUtil.format("IP:{} 存在开放端口,校验失败", ip));
        }

        return true;
    }

    /**
     * 检查单个端口是否开放
     *
     * @param ip   IP地址
     * @param port 端口号
     * @return 端口未开放或连接超时返回true;端口开放返回false
     */
    private CompletableFuture<Boolean> checkPort(String ip, int port) {
        return CompletableFuture.supplyAsync(() -> {
            try (Socket socket = new Socket()) {
                // 连接超时设置为1秒(根据实际需求调整)
                socket.connect(new InetSocketAddress(ip, port), 1000);
                // 连接成功:端口开放,返回false
                System.out.printf("IP:%s 端口:%d 开放%n", ip, port);
                return false;
            } catch (IOException e) {
                // IO异常:端口未开放或连接超时,返回true
                System.out.printf("IP:%s 端口:%d 未开放(原因:%s)%n", ip, port, e.getMessage());
                return true;
            } catch (Exception e) {
                // 其他异常:视为校验通过,返回true
                System.err.printf("IP:%s 端口:%d 检查异常:%s%n", ip, port, e.getMessage());
                return true;
            }
        }, executor);
    }
}

// 业务异常类定义
class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}

// 字符串工具类(简化版,实际项目可使用Hutool等工具库)
class StrUtil {
    public static String format(String template, Object... params) {
        return String.format(template, params);
    }
}

三、代码解析:核心技术点详解

1. 并发任务创建:CompletableFuture.supplyAsync

return CompletableFuture.supplyAsync(() -> {
    // 端口检查逻辑
}, executor);
  • supplyAsync用于创建有返回值的异步任务,第一个参数是Supplier函数式接口,封装端口检查的核心逻辑
  • 第二个参数指定自定义线程池,避免使用默认的ForkJoinPool,更好地控制资源占用
  • 每个端口检查任务独立运行,实现真正的并行处理

2. 单个端口检查逻辑

端口检查的核心逻辑通过Socket连接实现:

try (Socket socket = new Socket()) {
    socket.connect(new InetSocketAddress(ip, port), 1000);
    return false; // 连接成功:端口开放
} catch (IOException e) {
    return true;  // 连接失败:端口未开放
}
  • 使用try-with-resources语法自动关闭Socket资源,避免资源泄漏
  • socket.connect()方法指定超时时间(1秒),防止长时间阻塞
  • 异常处理设计:
    • IO异常(如Connection refused、超时)视为端口未开放
    • 其他异常(如InterruptedException)也视为校验通过(可根据业务调整)

3. 多任务协调:CompletableFuture.allOf

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
  • allOf接收多个CompletableFuture,返回一个新的CompletableFuture
  • 新的CompletableFuture会在所有输入任务完成后才完成
  • join()方法阻塞等待所有任务完成(与get()类似,但抛出未检查异常)

4. 结果汇总与判断

List<Boolean> results = futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList());

if (results.contains(Boolean.FALSE)) {
    throw new BusinessException("存在开放端口,校验失败");
}
  • 调用每个Future的join()方法获取结果(此时所有任务已完成,不会阻塞)
  • 结果判断逻辑:只要有一个端口开放(返回false),则整体校验失败
  • 通过业务异常传递失败信息,便于上层处理

5. 性能对比:并发vs串行

假设检查10个端口,每个端口检查耗时1秒:

  • 串行检查:总耗时约10秒(10个任务依次执行)
  • 并发检查:总耗时约1秒(10个任务并行执行)

并发方式在多端口检查场景下性能优势明显,端口数量越多,优势越显著。

五、使用示例

public class PortCheckerDemo {
    public static void main(String[] args) {
        PortChecker checker = new PortChecker();
        String ip = "127.0.0.1";
        String ports = "80,8080,443,3306";
        
        try {
            boolean result = checker.validatePorts(ip, ports);
            if (result) {
                System.out.println("所有端口均未开放,校验通过");
            }
        } catch (BusinessException e) {
            System.err.println("校验失败:" + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.err.println("参数错误:" + e.getMessage());
        }
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值