本文详解 java 多线程环境下对静态共享集合(如 static list)的同步策略,指出仅使用 synchronized 方法无法保证线程安全,并通过对比分析、代码示例与关键注意事项,阐明必须显式锁定共享对象本身的原因。
在 Java 并发编程中,synchronized 是最基础且常用的同步机制,但其作用范围和锁对象的选择极易被误解。以 Account 类为例,初看之下,将 deposit() 和 withdraw() 声明为 synchronized 方法似乎已确保了线程安全——毕竟所有对 balance 和 log 的修改都被“串行化”了。然而,这种理解只在锁对象与被保护资源严格对应时才成立;而本例中,锁对象(this 实例)与被保护资源(static List
public synchronized void deposit(...) 等价于:
public void deposit(double val) {
synchronized (this) { // ← 锁的是当前 Account 实例!
balance = balance + val;
log.add(val); // ← 但 log 是 static,被所有实例共享!
}
}这意味着:
这正是原方案不安全的根本原因:方法级同步保护的是实例状态(balance),而非跨实例共享的静态资源(log)。
要保护静态 log,必须让所有访问它的代码都竞争同一把锁。最佳实践是直接以 log 对象自身为锁:
public class Account {
private static final List log = new ArrayList<>(); // ✅ final 保证引用不可变
private double balance;
public Account() { balance = 0.0; }
public synchronized void deposit(double val) {
balance += val;
synchronized (log) { // ✅ 所有线程均竞争 log 这一共享锁
log.add(val);
}
}
public synchronized void withdraw(double val) {
balance -= val;
synchronized (log) {
log.add(-val); // 注意:原题 withdraw 日志应记录负值,体现资金流出
}
}
// 安全的静态访问器(可选)
public static List getLog() {
synchronized (log) {
return new ArrayList<>(log); // ✅ 返回副本,避免外部直接修改
}
}
} ? 关键点说明:log 声明为 final:防止意外重新赋值导致锁失效(如 log = new ArrayList() 后,新旧引用锁对象不一致);双重锁嵌套可行:synchronized(this) 保护 balance,synchronized(log) 保护 log,互不干扰;避免锁 Class 对象(如 synchronized(Account.class)):虽能保护静态资源,但粒度过大,易造成不必要的线程阻塞。
对于日志等追加场景,推荐直接选用 java.util.concurrent 包中的线程安全集合,语义更清晰、性能通常更优:
private static final Listlog = Collections.synchronizedList(new ArrayList<>()); // 或更现代的选择(JDK 14+): // private static final List log = new CopyOnWriteArrayList<>();
此时 log.add() 本身已线程安全,无需额外 synchronized(log) 块(但注意:Collections.synchronizedList 的迭代仍需手动同步)。
文中代码还存在一个严重设计缺陷:使用 double 表示货币金额。浮点数精度误差会导致不可接受的财务偏差(例如 0.1 + 0.2 != 0.3)。生产环境必须改用 BigDecimal 或整数(以分为单位):
private BigDecimal balance = BigDecimal.ZERO;
public void deposit(BigDecimal val) {
balance = balance.add(val);
synchronized (log) {
log.add(val.doubleValue()); // 若日志仅作调试,可保留 double;否则也应用 BigDecimal
}
}正确的同步不是“加锁越多越好”,而是“锁得恰到好处”——精准匹配临界资源与锁粒度,方为高并发程序的稳健基石。