如何确保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
相关产品推荐
相关产品推荐

