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

如何确保Logger类实例可被多线程安全使用?

如何确保Logger类的线程安全使用

原Logger类的核心问题在于:StringBuilder是非线程安全的,多线程并发调用log()时,多次append操作会交叉执行,导致日志内容错乱;同时getContents()在并发读取时可能拿到不完整的中间状态数据。下面给出几种实用的解决方案:

方案一:使用synchronized同步方法

这是最直接的实现方式,通过给log()和getContents()添加synchronized修饰,保证同一时间只有一个线程能执行这些方法,确保操作的原子性和数据可见性:

public class Logger {
    private StringBuilder contents = new StringBuilder();

    public synchronized void log(String message) {
        contents.append(System.currentTimeMillis());
        contents.append(": ");
        contents.append(Thread.currentThread().getName());
        contents.append(message);
        contents.append("\n");
    }

    public synchronized String getContents() {
        return contents.toString();
    }
}

方案二:替换StringBuilder为StringBuffer并优化日志拼接

StringBuffer的所有方法都内置了同步,但直接替换后,log()内的多次append仍可能被打断(比如线程A写完时间戳后,线程B插入写入自己的日志)。优化方式是先拼接好完整的日志条目,再一次性调用append,保证单条日志的原子性:

public class Logger {
    private StringBuffer contents = new StringBuffer();

    public void log(String message) {
        // 先拼接完整日志条目,再执行一次append
        String logEntry = System.currentTimeMillis() + ": " 
            + Thread.currentThread().getName() + message + "\n";
        contents.append(logEntry);
    }

    public String getContents() {
        return contents.toString();
    }
}

方案三:使用显式锁ReentrantLock

如果需要更灵活的锁控制(比如公平锁、支持中断等),可以用ReentrantLock替代synchronized,注意必须在finally块中释放锁,避免异常导致锁泄漏:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Logger {
    private StringBuilder contents = new StringBuilder();
    private final Lock lock = new ReentrantLock(); // 可传入true创建公平锁

    public void log(String message) {
        lock.lock();
        try {
            contents.append(System.currentTimeMillis());
            contents.append(": ");
            contents.append(Thread.currentThread().getName());
            contents.append(message);
            contents.append("\n");
        } finally {
            lock.unlock();
        }
    }

    public String getContents() {
        lock.lock();
        try {
            return contents.toString();
        } finally {
            lock.unlock();
        }
    }
}

方案四:ThreadLocal+线程本地日志(高并发场景)

如果是高并发场景,同步锁会带来性能瓶颈,可以用ThreadLocal让每个线程维护自己的日志缓冲区,最后再合并所有线程的日志,避免锁竞争:

import java.util.HashMap;
import java.util.Map;

public class Logger {
    private final ThreadLocal<StringBuilder> threadLocalBuilder = ThreadLocal.withInitial(StringBuilder::new);
    private final Map<Thread, StringBuilder> threadLogs = new HashMap<>();

    public void log(String message) {
        StringBuilder builder = threadLocalBuilder.get();
        builder.append(System.currentTimeMillis());
        builder.append(": ");
        builder.append(Thread.currentThread().getName());
        builder.append(message);
        builder.append("\n");
        threadLogs.put(Thread.currentThread(), builder);
    }

    public synchronized String getContents() {
        StringBuilder fullContents = new StringBuilder();
        for (StringBuilder log : threadLogs.values()) {
            fullContents.append(log);
        }
        return fullContents.toString();
    }

    // 线程结束时调用,清理ThreadLocal避免内存泄漏
    public void cleanup() {
        threadLocalBuilder.remove();
        threadLogs.remove(Thread.currentThread());
    }
}

方案选择建议

  • 简单低并发场景:优先用synchronized方法,实现简单且可靠;
  • 追求代码简洁:用方案二的优化版,减少同步范围;
  • 需要灵活锁逻辑:用ReentrantLock;
  • 高并发高性能需求:用ThreadLocal方案,适合批量获取日志的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 11:50:22