Java线程同步:为何需单独锁log而非仅用synchronized方法?
关于Account类同步方式的疑问:单独锁log是否必要?
问题背景
我实现了如下Java代码的Account类:
public class Account { private static List<Double> log = new ArrayList<Double>(); private double balance; public Account(){balance = 0.0;} public synchronized void deposit(double val){ balance = balance + val; log.add(val); } public synchronized void withdraw(double val){ balance = balance - val; log.add(val); } }
我认为这个同步方式是正确的,因为synchronized方法保证了对log的操作是原子性的,还通过如下测试验证:
Account a = new Account(); Thread t = new Thread(() -> a.deposit(30)); Thread j = new Thread(() -> a.withdraw(20)); t.start(); j.start(); sleep(300); System.out.println(Account.getLog());
但后来看到另一种实现方案,在方法内部单独对log对象加锁执行add操作:
class Account { private static List<Double> log = new ArrayList<Double>(); private double balance; public Account() { balance = 0.0; } public synchronized void deposit(double val) { balance = balance + val; synchronized (log) { log.add(val); } } public synchronized void withdraw(double val) { balance = balance - val; synchronized (log) { log.add(-val); } } }
我产生了疑问:为何需要单独锁log,仅用同步方法是否足够?
解答
仅用synchronized方法的局限性
你的测试能正常运行,是因为只用到了一个Account实例——此时两个线程竞争的是同一个Account实例锁,自然不会同时操作log。但这种方案存在严重的线程安全隐患:
log是静态变量,属于Account类的共享资源,而非单个实例。如果程序中创建了多个Account实例,不同实例的synchronized方法持有的是各自的实例锁,彼此不互斥。比如线程1操作a1.deposit,线程2操作a2.withdraw,两个线程会同时进入同步方法,并行执行log.add,而ArrayList本身不是线程安全的,这会导致并发修改异常或者日志数据混乱。
单独锁log的必要性
单独对log对象加锁,本质是把锁的粒度和共享资源绑定:
- 不管有多少个
Account实例,所有线程操作log时都会竞争同一个锁(log对象本身),确保对log的所有修改都是串行执行的,从根本上保证了这个静态共享资源的线程安全,不受实例数量的影响。
额外补充
另外,原代码的withdraw方法中log.add(val)是错误的——取款应该记录负数金额,第二种方案的log.add(-val)才是正确的日志记录逻辑。
内容的提问来源于stack exchange,提问作者Luca Pedersoli
相关产品推荐
相关产品推荐

