You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#自定义日志类实现问题:多线程日志队列与调用确认

嘿,我明白你想自己撸一个日志类的想法——不想依赖第三方库,还得搞定多线程多类调用下的日志入队和展示,确实得把线程安全这块抠细了。从你给的代码片段和描述来看,核心问题大概率出在多线程下的队列访问安全和LogWork方法的调用逻辑冗余上,咱们一步步捋清楚:

首先,补全你可能的代码逻辑(猜你是写到锁那里断了)

你提到的private static reado...应该是ReentrantLock吧?很多人实现自定义日志类时会用锁来保证单例和队列安全,但如果锁的用法不对,或者队列本身不是线程安全的,就会出现需要重复确认LogWork调用的问题。

核心问题分析
  • 单例的线程不安全:如果你的mInstance初始化没做线程安全处理,多线程下可能会创建多个Logger实例,导致日志队列分散,LogWork调用混乱。
  • 普通Queue的并发风险:如果用的是LinkedList这类非线程安全的Queue,多线程同时入队时会出现元素丢失、队列结构损坏的情况,这时候你可能会反复检查LogWork是否正确触发。
  • LogWork调用逻辑不合理:如果每次入队日志都手动调用LogWork,可能会导致重复执行,或者在多线程下触发多次,你不得不反复确认是否调用正确。
解决方案:线程安全的日志类实现

下面是一个修正后的完整实现,解决上述问题:

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 定义日志级别枚举
enum LogLevel {
    VERBOSE, DEBUG, INFO, WARN, ERROR
}

// 日志条目类,封装日志的所有信息
class LogEntry {
    private LogLevel level;
    private String message;
    private String className;
    private long timestamp;

    public LogEntry(LogLevel level, String message, String className) {
        this.level = level;
        this.message = message;
        this.className = className;
        this.timestamp = System.currentTimeMillis();
    }

    // getter方法
    public LogLevel getLevel() { return level; }
    public String getMessage() { return message; }
    public String getClassName() { return className; }
    public long getTimestamp() { return timestamp; }
}

public class Logger {
    // 枚举单例,天生线程安全,避免双重检查锁定的坑
    private enum SingletonHolder {
        INSTANCE;
        private final Logger logger = new Logger();
    }

    private final LogLevel mLogLevel;
    // 用ConcurrentLinkedQueue,天生线程安全,不需要额外加锁处理入队出队
    private final ConcurrentLinkedQueue<LogEntry> mLogQueue;
    // 单线程线程池,保证日志顺序输出,同时处理后台消费
    private final ExecutorService mLogExecutor;

    private Logger() {
        // 默认日志级别设为INFO,可根据需求调整
        mLogLevel = LogLevel.INFO;
        mLogQueue = new ConcurrentLinkedQueue<>();
        // 创建后台守护线程,随应用退出自动终止
        mLogExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "Logger-Worker-Thread");
            t.setDaemon(true);
            return t;
        });
        // 启动日志消费逻辑
        startLogConsumer();
    }

    public static Logger getInstance() {
        return SingletonHolder.INSTANCE.logger;
    }

    // 对外暴露的日志入队API
    public void log(LogLevel level, String message, String className) {
        if (level.ordinal() < mLogLevel.ordinal()) {
            return; // 低于当前日志级别,直接忽略
        }
        LogEntry entry = new LogEntry(level, message, className);
        mLogQueue.offer(entry);
        // 这里不需要手动调用LogWork,后台线程会自动处理
    }

    // 快捷日志方法,简化调用
    public void d(String message, String className) {
        log(LogLevel.DEBUG, message, className);
    }

    public void i(String message, String className) {
        log(LogLevel.INFO, message, className);
    }

    public void e(String message, String className) {
        log(LogLevel.ERROR, message, className);
    }

    // 后台日志消费逻辑(替代你的LogWork)
    private void startLogConsumer() {
        mLogExecutor.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                LogEntry entry = mLogQueue.poll();
                if (entry != null) {
                    // 这里是你的日志展示逻辑,比如打印到控制台、更新UI等
                    processLogEntry(entry);
                } else {
                    try {
                        // 队列为空时休眠,避免空轮询浪费CPU
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        });
    }

    // 实际处理日志的方法
    private void processLogEntry(LogEntry entry) {
        // 示例:打印到控制台,你可以改成展示给用户的UI逻辑
        String logStr = String.format("[%d] [%s] [%s]: %s",
                entry.getTimestamp(),
                entry.getLevel().name(),
                entry.getClassName(),
                entry.getMessage());
        System.out.println(logStr);
        // 如果是Android应用,可通过Handler.post更新UI;如果是桌面应用,可更新日志面板
    }

    // 应用退出时调用,关闭线程池
    public void shutdown() {
        mLogExecutor.shutdownNow();
    }
}
关键改进点说明
  • 线程安全的单例:用枚举实现单例,比双重检查锁定更可靠,完全避免多线程下的单例实例重复创建问题。
  • ConcurrentLinkedQueue:这个队列是线程安全的,offer()和poll()方法都是原子操作,不需要额外加锁,解决了多线程入队的安全问题。
  • 后台消费线程:把LogWork(也就是processLogEntry)的调用交给后台单线程处理,不需要每次入队都手动触发,从根本上解决了“需要两次确认LogWork是否正确调用”的问题——后台线程会自动轮询队列,有日志就处理,没有就休眠。
  • 日志级别过滤:提前过滤低于当前级别的日志,减少不必要的入队操作。
使用示例

在你的其他类中,直接调用即可,多线程下也完全安全:

// 在主线程的类中
Logger.getInstance().i("用户登录成功", MainActivity.class.getSimpleName());

// 在子线程中
new Thread(() -> {
    Logger.getInstance().d("子线程执行任务", "BackgroundTask");
}).start();

这样改造后,你就不用再反复确认LogWork的调用了——后台线程会稳定地处理所有入队的日志,而且多线程下的日志顺序也能保证(因为用了单线程线程池)。

内容的提问来源于stack exchange,提问作者YogoWafel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:48:55