thread43 - Executor

本文介绍 Java 中 Executor 接口的基本概念及其简单实现。Executor 是 Java 线程池中最顶层的接口,用于执行 Runnable 任务。通过示例展示如何自定义一个简单的 Executor 实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.neutron.t23;

import java.util.concurrent.Executor;

/**
 * 线程池接口
 * 初步了解Executor接口,java线程池中最顶级的接口
 */
public class T231Executor implements Executor {

    // 会执行任务,任务如何实现,则需要自定义
    @Override
    public void execute(Runnable command) {
        // new Thread(command).run();
        command.run();
    }

    public static void main(String[] args) {
        new T231Executor().execute(() -> {
            System.out.println("hello executor");
        });
    }
}

being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper sj-client-check - *~*~*~ Previous channel ManagedChannelImpl{logId=30, target=127.0.0.1:17888} was garbage collected without being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper sj-client-check - *~*~*~ Previous channel ManagedChannelImpl{logId=63, target=127.0.0.1:17888} was garbage collected without being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper sj-client-check - *~*~*~ Previous channel ManagedChannelImpl{logId=162, target=127.0.0.1:17888} was garbage collected without being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper sj-client-check - *~*~*~ Previous channel ManagedChannelImpl{logId=51, target=127.0.0.1:17888} was garbage collected without being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper sj-client-check - *~*~*~ Previous channel ManagedChannelImpl{logId=41, target=127.0.0.1:17888} was garbage collected without being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper sj-client-check - *~*~*~ Previous channel ManagedChannelImpl{logId=73, target=127.0.0.1:17888} was garbage collected without being shut down! ~*~*~* Make sure to call shutdown()/shutdownNow() [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-19 - heartbeat check requestId:[19] message:[UNAVAILABLE: io exception] [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-20 - heartbeat check requestId:[20] message:[UNAVAILABLE: io exception] [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-21 - heartbeat check requestId:[21] message:[UNAVAILABLE: io exception] [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-23 - heartbeat check requestId:[23] message:[UNAVAILABLE: io exception] [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-24 - heartbeat check requestId:[24] message:[UNAVAILABLE: io exception] [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-25 - heartbeat check requestId:[25] message:[UNAVAILABLE: io exception] [ERROR] com.aizuda.snailjob.client.common.handler.ClientRegister snail-job-grpc-client-executor-127.0.0.1-26 - heartbeat check requestId:[26] message:[UNAVAILABLE: io exception] [INFO] de.codecentric.boot.admin.client.registration.ApplicationRegistrator registrationTask1 - Application registered itself as 24c2b44fbc5b [INFO] com.aizuda.snailjob.client.common.init.SnailJobCloseListener SpringApplicationShutdownHook - snail-job client about to shutdown v1.5.0 [INFO] com.aizuda.snailjob.client.common.log.report.AbstractLogReport SpringApplicationShutdownHook - AsyncReport Log about to shutdown [INFO] com.aizuda.snailjob.client.common.log.report.AbstractLogReport SpringApplicationShutdownHook - AsyncReport Log has been shutdown [INFO] com.aizuda.snailjob.client.common.init.SnailJobCloseListener SpringApplicationShutdownHook - snail-job client closed successfully v1.5.0 [INFO] io.undertow SpringApplicationShutdownHook - stopping server: Undertow - 2.3.18.Final
最新发布
08-08
<think>根据提供的日志,主要问题是出现了大量的`ManagedChannelOrphanWrapper`错误,提示gRPC的`ManagedChannel`实例在垃圾回收之前没有被正确关闭(没有调用`shutdown()`或`shutdownNow()`)。此外,还有心跳检查失败(`UNAVAILABLE: io exception`)的问题。 ### 问题分析 1. **`ManagedChannel`未正确关闭**: - gRPC的`ManagedChannel`在使用后必须显式关闭,否则会导致资源泄漏(如线程、连接等)。 - 错误日志表明,多个`ManagedChannel`实例在被垃圾回收时还未关闭,说明代码中创建了`ManagedChannel`,但没有在不再需要时调用关闭方法。 2. **心跳检查失败**: - 心跳检查失败可能是由于网络问题、服务端不可用或连接泄漏导致连接资源耗尽。 - 由于`ManagedChannel`未关闭,可能会导致连接泄漏,进而使后续的心跳请求无法建立连接。 ### 解决方案 1. **确保每个`ManagedChannel`在使用后关闭**: - 使用`try-with-resources`(如果实现了`AutoCloseable`)或显式调用`shutdown()`/`shutdownNow()`。 - 注意:`ManagedChannel`实现了`AutoCloseable`(在gRPC 1.10.0及以上版本),所以可以使用try-with-resources。 2. **重构代码,避免创建过多的`ManagedChannel`**: - 考虑复用`ManagedChannel`实例,因为创建和销毁`ManagedChannel`开销较大。通常,一个目标地址(如`127.0.0.1:17888`)应该共享一个`ManagedChannel`实例(使用连接池或单例模式)。 ### 示例代码 #### 创建并复用ManagedChannel ```java import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class ChannelManager { private static final Map<String, ManagedChannel> channelMap = new ConcurrentHashMap<>(); public static ManagedChannel getChannel(String target) { return channelMap.computeIfAbsent(target, key -> ManagedChannelBuilder.forTarget(key) .usePlaintext() // 如果使用明文,生产环境应使用TLS .build() ); } public static void shutdownAll() { channelMap.forEach((target, channel) -> { shutdownChannel(channel); }); channelMap.clear(); } private static void shutdownChannel(ManagedChannel channel) { if (channel != null && !channel.isShutdown()) { try { channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); channel.shutdownNow(); } } } } ``` #### 使用ManagedChannel ```java try { ManagedChannel channel = ChannelManager.getChannel("127.0.0.1:17888"); // 使用channel创建stub并调用gRPC服务 // ... } finally { // 注意:这里不能关闭channel,因为它是复用的。应该在应用关闭时统一关闭。 } ``` #### 应用关闭时关闭所有Channel 在Spring Boot应用中,可以注册一个关闭钩子: ```java import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; @Component public class ApplicationShutdownListener implements ApplicationListener<ContextClosedEvent> { @Override public void onApplicationEvent(ContextClosedEvent event) { ChannelManager.shutdownAll(); } } ``` ### 解决心跳检查失败 - 确保服务端可用。 - 检查网络连接是否正常。 - 确保客户端正确复用`ManagedChannel`,避免频繁创建和销毁连接。 ### 总结 1. **问题根源**:未正确关闭`ManagedChannel`导致资源泄漏和连接耗尽。 2. **解决方案**: - 复用`ManagedChannel`(使用连接池或单例)。 - 在应用关闭时显式关闭所有`ManagedChannel`。 - 避免为每个请求创建新的`ManagedChannel`。 ### 注意事项 - 使用连接池时,注意设置合理的连接数上限,避免资源耗尽。 - 生产环境应使用TLS加密连接(使用`usePlaintext()`仅用于测试)。 §§相关问题§§: 1. 如何监控gRPC连接泄漏? 2. gRPC的`ManagedChannel`和`ManagedServerTransport`有什么区别? 3. 在gRPC中,如何实现负载均衡? 4. 如何配置gRPC的心跳和超时时间?</think>### 问题分析:gRPC 通道未正确关闭导致资源泄漏 从日志中可以看出,核心问题是 **gRPC 的 `ManagedChannel` 实例被垃圾回收时未被正确关闭**,导致以下问题: ``` [ERROR] io.grpc.internal.ManagedChannelOrphanWrapper - *~*~*~ Previous channel ManagedChannelImpl{...} was garbage collected without being shut down! Make sure to call shutdown()/shutdownNow() ``` ### 问题原因 1. **资源泄漏**: - `ManagedChannel` 持有网络连接、线程池等资源 - 未显式关闭会导致: * 文件描述符泄漏 * 线程泄漏(每个通道约 2-3 线程) * 内存泄漏(内部缓冲区未释放) 2. **GC 无法替代资源释放**: - 虽然 JVM 会回收对象内存,但底层网络资源需显式释放 - 垃圾回收器不负责释放非内存资源 3. **连接池管理缺陷**: - 日志中显示多个不同 ID 的通道(logId=30/63/162...) - 表明存在通道被创建后未复用的情况 ### 解决方案 #### 1. 确保正确关闭通道(基础修复) ```java import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; public class GrpcClient { private ManagedChannel channel; public void init() { channel = ManagedChannelBuilder.forAddress("127.0.0.1", 17888) .usePlaintext() .build(); } // 必须显式关闭通道 public void shutdown() { if (channel != null && !channel.isShutdown()) { channel.shutdown(); // 优雅关闭 try { if (!channel.awaitTermination(5, TimeUnit.SECONDS)) { channel.shutdownNow(); // 强制关闭 } } catch (InterruptedException e) { channel.shutdownNow(); Thread.currentThread().interrupt(); } } } } ``` #### 2. 使用连接池管理通道(推荐方案) ```java import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class ChannelPool { private static final Map<String, ManagedChannel> pool = new ConcurrentHashMap<>(); // 获取通道(复用已有连接) public static ManagedChannel getChannel(String host, int port) { String key = host + ":" + port; return pool.computeIfAbsent(key, k -> ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build() ); } // 应用关闭时清理所有通道 public static void shutdownAll() { pool.forEach((key, channel) -> { if (!channel.isShutdown()) { channel.shutdown(); try { if (!channel.awaitTermination(3, TimeUnit.SECONDS)) { channel.shutdownNow(); } } catch (InterruptedException e) { channel.shutdownNow(); Thread.currentThread().interrupt(); } } }); pool.clear(); } } ``` #### 3. Spring Boot 集成方案(通过 Shutdown Hook) ```java import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Component; @Component public class GrpcShutdownListener implements ApplicationListener<ContextClosedEvent> { @Override public void onApplicationEvent(ContextClosedEvent event) { ChannelPool.shutdownAll(); // 清理所有通道 } } ``` #### 4. 心跳连接优化(解决 UNAVAILABLE 错误) ```java ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", 17888) .usePlaintext() // 开启心跳检测 .keepAliveTime(30, TimeUnit.SECONDS) // 每30秒发送心跳 .keepAliveTimeout(10, TimeUnit.SECONDS) // 等待响应超时 .build(); ``` ### 关键原理说明 1. **通道生命周期管理**: - 创建 → 使用 → 显式关闭 - 关闭流程: ```mermaid graph LR A[调用 shutdown] --> B[拒绝新请求] B --> C[完成进行中请求] C --> D[释放底层资源] D --> E[关闭线程池] ``` 2. **连接池优势**: - 避免频繁创建/销毁通道(TCP 连接成本高) - 减少 TIME_WAIT 状态连接 - 控制最大连接数(防止资源耗尽) 3. **心跳机制**: - 保持长连接活性 - 及时检测断连(早于 TCP keepalive) - 自动重连(gRPC 内置) ### 生产环境建议 1. **监控指标**: ```java // 添加监控 channel = ManagedChannelBuilder.forAddress(...) .intercept(new MonitoringClientInterceptor(registry)) .build(); ``` 2. **优雅关闭流程**: ```java Runtime.getRuntime().addShutdownHook(new Thread(() -> { channelPool.shutdownAll(); // 等待处理中的请求完成 Thread.sleep(2000); })); ``` 3. **配置参考**: ```properties # application.properties grpc.pool.max-size=10 grpc.pool.max-idle=5 grpc.keepalive.time=30s grpc.keepalive.timeout=5s ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值