
通过为共享的 `stringbuilder` 实例添加同步控制(如 `synchronized` 块),可保证多线程调用 `log()` 和 `getcontents()` 时的数据一致性与线程安全性。
在多线程应用中,日志记录器(Logger)常被多个线程并发调用,若不加保护,极易引发竞态条件(race condition):例如 StringBuilder.append() 非原子操作可能导致内容错乱、字符截断,甚至 String.toString() 返回不一致的中间状态。
原始代码中,contents 是非线程安全的可变对象,且未做任何同步,因此 log() 方法中的多步追加操作(时间戳、线程名、消息、换行符)可能被其他线程穿插执行,造成日志格式损坏或数据丢失。
✅ 正确做法是:以 contents 为锁对象,对所有读写操作加 synchronized 块,并将其声明为 final——这不仅语义上表明其不可变性,更确保了锁对象的稳定性(避免锁替换导致同步失效):
public class Logger {
private final StringBuilder contents = new StringBuilder();
public void log(String message) {
synchronized (contents) {
contents.append(System.currentTimeMillis());
contents.append('[');
contents.append(Thread.currentThread().getName());
contents.append("]: ");
contents.append(message);
contents.append("\n");
}
}
public String getContents() {
synchronized (contents) {
return contents.toString();
}
}
}⚠️ 注意事项:
- 不要使用 synchronized(this):若 Logger 实例被外部作为锁使用,可能引发死锁或意外同步干扰;
- 避免在同步块内执行耗时操作(如 I/O、网络调用),本例中仅字符串拼接,符合最佳实践;
- 若需更高吞吐量(如高并发日志场景),可考虑 java.util.concurrent 中的线程安全替代方案,例如 ConcurrentLinkedQueue
缓存日志条目 + 单独线程异步刷盘,但本例以简洁性和正确性为首要目标。
总结:线程安全的本质是受保护的临界区 + 稳定的锁对象。将 final StringBuilder 作为锁,配合细粒度同步,即可在保持代码清晰的同时,彻底解决并发写入问题。










