持有多把锁时为何不应调用wait方法?
先来看你提供的这段代码,它确实存在同时持有两把锁的情况:
public synchronized void methodOne(List<String> profileNames, ParameterAttributes parameterAttributes) throws InterruptedException { if (profileNames != null && !profileNames.isEmpty()) { profileNames.forEach(profileName -> System.out.println(profileName)); } synchronized(lockObj) { lockObj.wait(); } }
这里的synchronized方法会先获取当前类实例的锁,之后又在内部代码块获取了lockObj的锁,接着调用了lockObj.wait()。这种操作会带来几个严重的问题:
不必要的长期阻塞,引发性能或死锁风险
调用lockObj.wait()时,当前线程只会释放lockObj这一把锁,但不会释放当前实例的锁!这就意味着,所有想要访问该类其他synchronized方法(或者同步当前实例的代码块)的线程,都会被持续阻塞,直到这个线程被唤醒并完全执行完methodOne方法。如果wait()的唤醒条件迟迟不满足(比如其他线程忘记调用notify/notifyAll,或者业务上需要等待很长时间),当前实例的锁会被长时间霸占,直接拖垮程序的并发能力,甚至可能引发连锁阻塞,间接导致死锁。锁逻辑混乱,降低代码可维护性
持有多锁时调用wait,会让锁的释放逻辑变得非常不直观。后续维护代码的开发者很难第一时间意识到:这个方法同时拿了两把锁,而wait只释放了其中一把。这种隐藏的逻辑很容易引发后续bug,比如新增同步逻辑时没考虑锁的持有情况,进一步加剧并发问题。违背并发编程的最小持有原则
并发编程里有个核心原则:只在必要的时间段持有必要的锁。你这段代码里,遍历profileNames的逻辑(如果确实需要锁的话)只需要当前实例的锁,而wait逻辑只需要lockObj的锁。把两个逻辑放在同一个持有多锁的方法里,本身就扩大了锁的持有范围,再加上wait导致的锁不释放,完全违背了最小持有原则,会大幅提升并发冲突的概率。
举个实际的例子:如果这个类还有另一个synchronized方法methodTwo(),当线程A在methodOne里进入wait状态后,线程B想调用methodTwo()就会被一直卡住——哪怕线程B根本不需要操作lockObj,这完全是毫无意义的阻塞。
正确的做法应该是拆分锁的持有范围,让每个同步块只持有完成对应逻辑所需的锁,调用wait时确保只持有当前wait对象的那一把锁,避免其他锁被无端霸占。
内容的提问来源于stack exchange,提问作者Arghya Pal

