张世豪
3 小时以前 d22349714c8d199c02f336f90fba841ef8f5cd39
src/chushihua/lunxun.java
@@ -7,6 +7,7 @@
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;
@@ -16,6 +17,7 @@
 * 用于定时向所有卡槽发送查询指令
 * 支持暂停和恢复功能,检查串口连接状态
 * 新增:不同卡槽状态使用不同查询频率
 * 优化:内存管理和长时间运行稳定性
 */
public class lunxun {
   private static volatile boolean isRunning = false;
@@ -24,14 +26,6 @@
   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;
@@ -44,6 +38,15 @@
   // 性能优化:查询指令缓存
   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;
@@ -52,6 +55,14 @@
   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;
   }
   /**
    * 检查串口连接状态 - 优化版本,添加重试机制
@@ -76,11 +87,11 @@
            }
            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;
      }
@@ -117,9 +128,12 @@
      // 启动前严格检查串口连接
      if (!checkSerialConnectionWithRetry()) {
         Errlog.logOperation("串口未连接,无法启动轮询查询");
         logErrorWithRateLimit("start_polling_serial_failed", "串口未连接,无法启动轮询查询");
         return false;
      }
      // 启动前先清理一次内存
      performCleanup();
      // 从配置中获取轮询间隔
      loadPollingIntervalFromConfig();
@@ -137,7 +151,7 @@
         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;
@@ -174,15 +188,15 @@
            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;
         }
@@ -234,7 +248,7 @@
      // 恢复前检查串口连接
      if (!checkSerialConnectionWithRetry()) {
         Errlog.logOperation("串口未连接,无法恢复轮询查询");
         logErrorWithRateLimit("resume_polling_serial_failed", "串口未连接,无法恢复轮询查询");
         return false;
      }
@@ -276,7 +290,7 @@
    */
   public static void setPollingInterval(int interval) {
      if (interval < 10) {
         Errlog.logOperation("轮询间隔不能小于10ms");
         logErrorWithRateLimit("polling_interval_too_small", "轮询间隔不能小于10ms");
         return;
      }
@@ -328,7 +342,7 @@
            //System.out.println("配置系统未初始化,使用默认轮询间隔: " + pollingInterval + "ms");
         }
      } catch (Exception e) {
         Errlog.logOperation("加载轮询间隔配置失败: " + e.getMessage());
         logErrorWithRateLimit("load_polling_interval_failed", "加载轮询间隔配置失败: " + e.getMessage());
         //System.out.println("使用默认轮询间隔: " + pollingInterval + "ms");
      }
   }
@@ -351,12 +365,13 @@
   /**
    * 轮询任务内部类 - 优化版本
    * 支持不同状态卡槽的不同查询频率
    * 优化:内存管理和重用对象
    */
   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() {
@@ -365,7 +380,7 @@
         while (isRunning && !Thread.currentThread().isInterrupted() && !shouldStop.get()) {
            try {
               //                   System.out.println("查询中.....线程");
//               System.out.println("查询中.....线程");
               // 检查是否暂停
               if (isPaused) {
                  synchronized (lunxun.class) {
@@ -378,15 +393,20 @@
               // 定期检查串口连接状态(每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;
               }
@@ -427,14 +447,18 @@
                              } else {
                                 status = "无卡";
                              }
                              SystemDebugDialog.appendAsciiData(
                                    String.format("Slot %d (%s) 查询成功,间隔: %dms\n",
                                          slotNumber, status, queryInterval));
                              // 使用重用的 StringBuilder 构建调试信息
                              debugBuilder.setLength(0);
                              debugBuilder.append("Slot ").append(slotNumber)
                                         .append(" (").append(status).append(") 查询成功,间隔: ")
                                         .append(queryInterval).append("ms\n");
                              SystemDebugDialog.appendAsciiData(debugBuilder.toString());
                           }
                        } else {
                           consecutiveFailures++;
                           if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
                              Errlog.logOperation("lunxun连续失败次数过多,暂停轮询");
                              logErrorWithRateLimit("consecutive_failures", "lunxun连续失败次数过多,暂停轮询");
                              pausePolling();
                              break;
                           }
@@ -456,7 +480,7 @@
               Thread.currentThread().interrupt();
               break;
            } catch (Exception e) {
               Errlog.logOperation("轮询查询过程中发生异常: " + e.getMessage());
               logErrorWithRateLimit("polling_exception", "轮询查询过程中发生异常: " + e.getMessage());
               consecutiveFailures++;
               // 发生异常时等待一段时间再继续
@@ -480,7 +504,7 @@
         try {
            // 使用缓存的查询指令               
            String queryCommand = getCachedQueryCommand(slotNumber);
            // System.out.println("指令是:"+queryCommand);
//             System.out.println("指令是:"+queryCommand);
            if (DEBUG_ENABLED) {
               SystemDebugDialog.appendAsciiData("send to "+slotNumber+" queryCommand");
            }
@@ -504,12 +528,12 @@
                  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;
@@ -524,13 +548,13 @@
    */
   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;
      }
@@ -551,16 +575,16 @@
               }
               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;
      }
   }
@@ -572,7 +596,7 @@
   public static int sendImmediateQueryToAll() {
      // 检查串口连接
      if (!checkSerialConnectionWithRetry()) {
         Errlog.logOperation("串口未连接,无法发送批量查询指令");
         logErrorWithRateLimit("batch_query_serial_failed", "串口未连接,无法发送批量查询指令");
         return 0;
      }
@@ -625,7 +649,7 @@
      if (connected) {
         //            //System.out.println("串口连接状态已设置为: 已连接");
      } else {
         Errlog.logOperation("串口连接状态已设置为: 未连接");
         logErrorWithRateLimit("serial_disconnected_external", "串口连接状态已设置为: 未连接");
         // 如果串口断开且轮询正在运行,自动暂停轮询
         if (isRunning && !isPaused) {
            pausePolling();
@@ -664,9 +688,9 @@
         }
      }
      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());
   }
   /**
@@ -695,7 +719,7 @@
         if (isPaused) {
            // 恢复前检查串口连接
            if (!checkSerialConnectionWithRetry()) {
               Errlog.logOperation("串口未连接,无法恢复轮询查询");
               logErrorWithRateLimit("external_resume_serial_failed", "串口未连接,无法恢复轮询查询");
               return false;
            }
@@ -752,7 +776,7 @@
    */
   public static void setNoCardQueryInterval(int interval) {
      if (interval < 10) {
         Errlog.logOperation("无卡卡槽查询间隔不能小于10ms");
         logErrorWithRateLimit("no_card_interval_too_small", "无卡卡槽查询间隔不能小于10ms");
         return;
      }
      // 注意:这里只是设置常量,实际运行时需要重新启动轮询才能生效
@@ -765,7 +789,7 @@
    */
   public static void setHasCardQueryInterval(int interval) {
      if (interval < 1000) {
         Errlog.logOperation("有卡卡槽查询间隔不能小于1000ms");
         logErrorWithRateLimit("has_card_interval_too_small", "有卡卡槽查询间隔不能小于1000ms");
         return;
      }
      // 注意:这里只是设置常量,实际运行时需要重新启动轮询才能生效
@@ -779,4 +803,102 @@
   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");
      }
   }
}