| | |
| | | import publicway.QueryData; |
| | | import xitongshezhi.SystemDebugDialog; |
| | | |
| | | import java.util.Iterator; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | |
| | | * 用于定时向所有卡槽发送查询指令 |
| | | * 支持暂停和恢复功能,检查串口连接状态 |
| | | * 新增:不同卡槽状态使用不同查询频率 |
| | | * 优化:内存管理和长时间运行稳定性 |
| | | */ |
| | | public class lunxun { |
| | | private static volatile boolean isRunning = false; |
| | |
| | | private static Thread pollingThread; |
| | | private static int pollingInterval = 100; // 默认轮询间隔 |
| | | public static boolean sendChaxunzhiling=true;//是否向串口发送查询指令 |
| | | |
| | | public static boolean isSendChaxunzhiling() { |
| | | return sendChaxunzhiling; |
| | | } |
| | | |
| | | public static void setSendChaxunzhiling(boolean sendChaxunzhiling) { |
| | | lunxun.sendChaxunzhiling = sendChaxunzhiling; |
| | | } |
| | | |
| | | // 卡槽相关常量 |
| | | private static final int MIN_SLOT = 1; |
| | |
| | | // 性能优化:查询指令缓存 |
| | | private static final Map<Integer, String> queryCommandCache = new ConcurrentHashMap<>(); |
| | | |
| | | // 内存优化:缓存大小限制和清理机制 |
| | | private static final int MAX_CACHE_SIZE = 100; |
| | | private static final long CACHE_CLEANUP_INTERVAL = 60000; // 1分钟清理一次 |
| | | private static long lastCleanupTime = 0; |
| | | |
| | | // 错误日志限流机制 |
| | | private static final Map<String, Long> lastErrorLogTime = new ConcurrentHashMap<>(); |
| | | private static final long ERROR_LOG_INTERVAL = 5000; // 相同错误5秒内只记录一次 |
| | | |
| | | // 调试模式控制 |
| | | public static boolean DEBUG_ENABLED = false; |
| | | |
| | |
| | | private static final int NO_CARD_QUERY_INTERVAL = 100; // 无卡卡槽查询间隔:100ms |
| | | private static final int HAS_CARD_QUERY_INTERVAL = 10000; // 有卡卡槽查询间隔:10秒 |
| | | private static final Map<Integer, Long> lastQueryTimeMap = new ConcurrentHashMap<>(); // 记录每个卡槽的最后查询时间 |
| | | |
| | | public static boolean isSendChaxunzhiling() { |
| | | return sendChaxunzhiling; |
| | | } |
| | | |
| | | public static void setSendChaxunzhiling(boolean sendChaxunzhiling) { |
| | | lunxun.sendChaxunzhiling = sendChaxunzhiling; |
| | | } |
| | | |
| | | /** |
| | | * 检查串口连接状态 - 优化版本,添加重试机制 |
| | |
| | | } |
| | | serialConnected = true; |
| | | } else { |
| | | Errlog.logOperation("串口连接失败 - 串口未打开"); |
| | | logErrorWithRateLimit("serial_connection_failed", "串口连接失败 - 串口未打开"); |
| | | serialConnected = false; |
| | | } |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("串口连接检查异常: " + e.getMessage()); |
| | | logErrorWithRateLimit("serial_connection_exception", "串口连接检查异常: " + e.getMessage()); |
| | | serialConnected = false; |
| | | } |
| | | |
| | |
| | | |
| | | // 启动前严格检查串口连接 |
| | | if (!checkSerialConnectionWithRetry()) { |
| | | Errlog.logOperation("串口未连接,无法启动轮询查询"); |
| | | logErrorWithRateLimit("start_polling_serial_failed", "串口未连接,无法启动轮询查询"); |
| | | return false; |
| | | } |
| | | |
| | | // 启动前先清理一次内存 |
| | | performCleanup(); |
| | | |
| | | // 从配置中获取轮询间隔 |
| | | loadPollingIntervalFromConfig(); |
| | |
| | | pollingThread.start(); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("启动轮询查询线程时发生异常: " + e.getMessage()); |
| | | logErrorWithRateLimit("start_polling_thread_exception", "启动轮询查询线程时发生异常: " + e.getMessage()); |
| | | isRunning = false; |
| | | shouldStop.set(true); |
| | | return false; |
| | |
| | | pollingThread.join(3000); // 等待3秒 |
| | | // 检查线程是否还在运行 |
| | | if (pollingThread.isAlive()) { |
| | | Errlog.logOperation("轮询线程未在3秒内停止,标记为守护线程并忽略"); |
| | | logErrorWithRateLimit("polling_thread_stop_timeout", "轮询线程未在3秒内停止,标记为守护线程并忽略"); |
| | | // 不强制停止,而是确保它是守护线程 |
| | | pollingThread.setDaemon(true); |
| | | } |
| | | } catch (InterruptedException e) { |
| | | Errlog.logOperation("停止轮询查询时被中断: " + e.getMessage()); |
| | | logErrorWithRateLimit("stop_polling_interrupted", "停止轮询查询时被中断: " + e.getMessage()); |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("停止轮询线程时发生异常: " + e.getMessage()); |
| | | logErrorWithRateLimit("stop_polling_exception", "停止轮询线程时发生异常: " + e.getMessage()); |
| | | } finally { |
| | | pollingThread = null; |
| | | } |
| | |
| | | |
| | | // 恢复前检查串口连接 |
| | | if (!checkSerialConnectionWithRetry()) { |
| | | Errlog.logOperation("串口未连接,无法恢复轮询查询"); |
| | | logErrorWithRateLimit("resume_polling_serial_failed", "串口未连接,无法恢复轮询查询"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | */ |
| | | public static void setPollingInterval(int interval) { |
| | | if (interval < 10) { |
| | | Errlog.logOperation("轮询间隔不能小于10ms"); |
| | | logErrorWithRateLimit("polling_interval_too_small", "轮询间隔不能小于10ms"); |
| | | return; |
| | | } |
| | | |
| | |
| | | //System.out.println("配置系统未初始化,使用默认轮询间隔: " + pollingInterval + "ms"); |
| | | } |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("加载轮询间隔配置失败: " + e.getMessage()); |
| | | logErrorWithRateLimit("load_polling_interval_failed", "加载轮询间隔配置失败: " + e.getMessage()); |
| | | //System.out.println("使用默认轮询间隔: " + pollingInterval + "ms"); |
| | | } |
| | | } |
| | |
| | | /** |
| | | * 轮询任务内部类 - 优化版本 |
| | | * 支持不同状态卡槽的不同查询频率 |
| | | * 优化:内存管理和重用对象 |
| | | */ |
| | | |
| | | private static class PollingTask implements Runnable { |
| | | private int currentIndex = 0; // 当前索引,用于遍历slotArray |
| | | private int consecutiveFailures = 0; // 连续失败次数 |
| | | private static final int MAX_CONSECUTIVE_FAILURES = 5; // 最大连续失败次数 |
| | | private final StringBuilder debugBuilder = new StringBuilder(100); // 重用 StringBuilder |
| | | |
| | | @Override |
| | | public void run() { |
| | |
| | | |
| | | // 定期检查串口连接状态(每10次循环检查一次) |
| | | if (currentIndex % 10 == 0 && !checkSerialConnectionWithRetry()) { |
| | | Errlog.logOperation("串口连接断开,暂停轮询"); |
| | | logErrorWithRateLimit("serial_disconnected", "串口连接断开,暂停轮询"); |
| | | pausePolling(); |
| | | continue; |
| | | } |
| | | |
| | | // 定期清理缓存(每100次循环清理一次) |
| | | if (currentIndex % 100 == 0) { |
| | | cleanupOldCache(); |
| | | } |
| | | |
| | | // 获取卡槽数组 |
| | | Fkj[] slotArray = SlotManager.getSlotArray(); |
| | | if (slotArray == null || slotArray.length == 0) { |
| | | Errlog.logOperation("卡槽数组未初始化"); |
| | | logErrorWithRateLimit("slot_array_not_initialized", "卡槽数组未初始化"); |
| | | Thread.sleep(pollingInterval); |
| | | continue; |
| | | } |
| | |
| | | } else { |
| | | status = "无卡"; |
| | | } |
| | | if (DEBUG_ENABLED) { |
| | | StringBuilder debugMsg = new StringBuilder(50); |
| | | debugMsg.append("Slot ").append(slotNumber) |
| | | |
| | | // 使用重用的 StringBuilder 构建调试信息 |
| | | debugBuilder.setLength(0); |
| | | debugBuilder.append("Slot ").append(slotNumber) |
| | | .append(" (").append(status).append(") 查询成功,间隔: ") |
| | | .append(queryInterval).append("ms\n"); |
| | | SystemDebugDialog.appendAsciiData(debugMsg.toString()); |
| | | } |
| | | SystemDebugDialog.appendAsciiData(debugBuilder.toString()); |
| | | } |
| | | } else { |
| | | consecutiveFailures++; |
| | | if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) { |
| | | Errlog.logOperation("lunxun连续失败次数过多,暂停轮询"); |
| | | logErrorWithRateLimit("consecutive_failures", "lunxun连续失败次数过多,暂停轮询"); |
| | | pausePolling(); |
| | | break; |
| | | } |
| | |
| | | Thread.currentThread().interrupt(); |
| | | break; |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("轮询查询过程中发生异常: " + e.getMessage()); |
| | | logErrorWithRateLimit("polling_exception", "轮询查询过程中发生异常: " + e.getMessage()); |
| | | consecutiveFailures++; |
| | | |
| | | // 发生异常时等待一段时间再继续 |
| | |
| | | return false; |
| | | } |
| | | } else { |
| | | Errlog.logOperation("生成的查询指令为空,卡槽: " + slotNumber); |
| | | logErrorWithRateLimit("empty_query_command", "生成的查询指令为空,卡槽: " + slotNumber); |
| | | return false; |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("发送查询指令到卡槽 " + slotNumber + " 时发生异常: " + e.getMessage()); |
| | | logErrorWithRateLimit("send_query_exception", "发送查询指令到卡槽 " + slotNumber + " 时发生异常: " + e.getMessage()); |
| | | // 发生异常时更新串口连接状态 |
| | | serialConnected = false; |
| | | return false; |
| | |
| | | */ |
| | | public static boolean sendImmediateQuery(int slotNumber) { |
| | | if (slotNumber < MIN_SLOT || slotNumber > MAX_SLOT) { |
| | | Errlog.logOperation("卡槽编号必须在" + MIN_SLOT + "-" + MAX_SLOT + "之间"); |
| | | logErrorWithRateLimit("invalid_slot_number", "卡槽编号必须在" + MIN_SLOT + "-" + MAX_SLOT + "之间"); |
| | | return false; |
| | | } |
| | | |
| | | // 检查串口连接 |
| | | if (!checkSerialConnectionWithRetry()) { |
| | | Errlog.logOperation("串口未连接,无法发送查询指令"); |
| | | logErrorWithRateLimit("immediate_query_serial_failed", "串口未连接,无法发送查询指令"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | } |
| | | return true; |
| | | } else { |
| | | Errlog.logOperation("立即查询失败 - 发送指令到卡槽 " + slotNumber + " 失败"); |
| | | logErrorWithRateLimit("immediate_query_send_failed", "立即查询失败 - 发送指令到卡槽 " + slotNumber + " 失败"); |
| | | return false; |
| | | } |
| | | } else { |
| | | Errlog.logOperation("立即查询失败 - 生成的查询指令为空,卡槽: " + slotNumber); |
| | | logErrorWithRateLimit("immediate_query_empty_command", "立即查询失败 - 生成的查询指令为空,卡槽: " + slotNumber); |
| | | return false; |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Errlog.logOperation("立即查询卡槽 " + slotNumber + " 时发生异常: " + e.getMessage()); |
| | | logErrorWithRateLimit("immediate_query_exception", "立即查询卡槽 " + slotNumber + " 时发生异常: " + e.getMessage()); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | public static int sendImmediateQueryToAll() { |
| | | // 检查串口连接 |
| | | if (!checkSerialConnectionWithRetry()) { |
| | | Errlog.logOperation("串口未连接,无法发送批量查询指令"); |
| | | logErrorWithRateLimit("batch_query_serial_failed", "串口未连接,无法发送批量查询指令"); |
| | | return 0; |
| | | } |
| | | |
| | |
| | | if (connected) { |
| | | // //System.out.println("串口连接状态已设置为: 已连接"); |
| | | } else { |
| | | Errlog.logOperation("串口连接状态已设置为: 未连接"); |
| | | logErrorWithRateLimit("serial_disconnected_external", "串口连接状态已设置为: 未连接"); |
| | | // 如果串口断开且轮询正在运行,自动暂停轮询 |
| | | if (isRunning && !isPaused) { |
| | | pausePolling(); |
| | |
| | | } |
| | | } |
| | | |
| | | return String.format("轮询状态: %s, 串口: %s, 间隔: %dms, 指令缓存: %d, 卡槽范围: %d-%d, 无卡: %d(100ms), 有卡: %d(10s)", |
| | | return String.format("轮询状态: %s, 串口: %s, 间隔: %dms, 指令缓存: %d, 卡槽范围: %d-%d, 无卡: %d(100ms), 有卡: %d(10s)\n%s", |
| | | status, serialStatus, pollingInterval, cacheSize, MIN_SLOT, MAX_SLOT, |
| | | noCardCount, hasCardCount); |
| | | noCardCount, hasCardCount, getMemoryStatus()); |
| | | } |
| | | |
| | | /** |
| | |
| | | if (isPaused) { |
| | | // 恢复前检查串口连接 |
| | | if (!checkSerialConnectionWithRetry()) { |
| | | Errlog.logOperation("串口未连接,无法恢复轮询查询"); |
| | | logErrorWithRateLimit("external_resume_serial_failed", "串口未连接,无法恢复轮询查询"); |
| | | return false; |
| | | } |
| | | |
| | |
| | | */ |
| | | public static void setNoCardQueryInterval(int interval) { |
| | | if (interval < 10) { |
| | | Errlog.logOperation("无卡卡槽查询间隔不能小于10ms"); |
| | | logErrorWithRateLimit("no_card_interval_too_small", "无卡卡槽查询间隔不能小于10ms"); |
| | | return; |
| | | } |
| | | // 注意:这里只是设置常量,实际运行时需要重新启动轮询才能生效 |
| | |
| | | */ |
| | | public static void setHasCardQueryInterval(int interval) { |
| | | if (interval < 1000) { |
| | | Errlog.logOperation("有卡卡槽查询间隔不能小于1000ms"); |
| | | logErrorWithRateLimit("has_card_interval_too_small", "有卡卡槽查询间隔不能小于1000ms"); |
| | | return; |
| | | } |
| | | // 注意:这里只是设置常量,实际运行时需要重新启动轮询才能生效 |
| | |
| | | public static void setDEBUG_ENABLED(boolean dEBUG_ENABLED) { |
| | | DEBUG_ENABLED = dEBUG_ENABLED; |
| | | } |
| | | |
| | | // ==================== 新增内存优化方法 ==================== |
| | | |
| | | /** |
| | | * 清理旧缓存 - 防止内存无限增长 |
| | | */ |
| | | private static void cleanupOldCache() { |
| | | long currentTime = System.currentTimeMillis(); |
| | | if (currentTime - lastCleanupTime < CACHE_CLEANUP_INTERVAL) { |
| | | return; |
| | | } |
| | | |
| | | lastCleanupTime = currentTime; |
| | | |
| | | // 清理长时间未使用的查询时间记录 |
| | | long cleanupThreshold = currentTime - 300000; // 5分钟未使用 |
| | | lastQueryTimeMap.entrySet().removeIf(entry -> |
| | | currentTime - entry.getValue() > cleanupThreshold |
| | | ); |
| | | |
| | | // 限制查询指令缓存大小 |
| | | if (queryCommandCache.size() > MAX_CACHE_SIZE) { |
| | | Iterator<Map.Entry<Integer, String>> iterator = queryCommandCache.entrySet().iterator(); |
| | | int itemsToRemove = queryCommandCache.size() - MAX_CACHE_SIZE; |
| | | for (int i = 0; i < itemsToRemove && iterator.hasNext(); i++) { |
| | | iterator.next(); |
| | | iterator.remove(); |
| | | } |
| | | } |
| | | |
| | | // 清理错误日志限流记录 |
| | | lastErrorLogTime.entrySet().removeIf(entry -> |
| | | currentTime - entry.getValue() > 300000 // 5分钟 |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 限流错误日志 - 防止大量重复日志占用内存 |
| | | */ |
| | | private static void logErrorWithRateLimit(String errorKey, String message) { |
| | | long currentTime = System.currentTimeMillis(); |
| | | Long lastTime = lastErrorLogTime.get(errorKey); |
| | | |
| | | if (lastTime == null || currentTime - lastTime > ERROR_LOG_INTERVAL) { |
| | | Errlog.logOperation(message); |
| | | lastErrorLogTime.put(errorKey, currentTime); |
| | | |
| | | // 清理过期的错误记录 |
| | | if (lastErrorLogTime.size() > 50) { |
| | | lastErrorLogTime.entrySet().removeIf(entry -> |
| | | currentTime - entry.getValue() > 300000 // 5分钟 |
| | | ); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取内存状态信息 |
| | | */ |
| | | public static String getMemoryStatus() { |
| | | Runtime runtime = Runtime.getRuntime(); |
| | | long totalMemory = runtime.totalMemory(); |
| | | long freeMemory = runtime.freeMemory(); |
| | | long usedMemory = totalMemory - freeMemory; |
| | | long maxMemory = runtime.maxMemory(); |
| | | |
| | | return String.format("内存使用: %.2fMB/%.2fMB (最大: %.2fMB), 缓存: 时间记录=%d, 指令缓存=%d, 错误记录=%d", |
| | | usedMemory / (1024.0 * 1024.0), |
| | | totalMemory / (1024.0 * 1024.0), |
| | | maxMemory / (1024.0 * 1024.0), |
| | | lastQueryTimeMap.size(), |
| | | queryCommandCache.size(), |
| | | lastErrorLogTime.size()); |
| | | } |
| | | |
| | | /** |
| | | * 手动触发内存清理 |
| | | */ |
| | | public static void performCleanup() { |
| | | // 清理查询时间记录中长时间未查询的卡槽 |
| | | long cleanupThreshold = System.currentTimeMillis() - 3600000; // 1小时 |
| | | lastQueryTimeMap.entrySet().removeIf(entry -> |
| | | entry.getValue() < cleanupThreshold |
| | | ); |
| | | |
| | | // 清空查询指令缓存 |
| | | queryCommandCache.clear(); |
| | | |
| | | // 清空错误日志限流记录 |
| | | lastErrorLogTime.clear(); |
| | | |
| | | // 建议系统进行垃圾回收(但不强制) |
| | | System.gc(); |
| | | |
| | | if (DEBUG_ENABLED) { |
| | | SystemDebugDialog.appendAsciiData("执行内存清理完成\n"); |
| | | } |
| | | } |
| | | } |