为何《并发编程实战》Listing10.3中identityHashCode死锁安全实现有效?
一、如何保证不同线程中对应Account的identityHashCode大小关系一致?
首先明确:System.identityHashCode(Object)返回的是对象的内存身份哈希值,仅对同一个对象实例,所有线程调用该方法返回的结果固定;但对于两个equals相等但实例不同的Account对象(如akk1Thread1=new Account(1)和akk1Thread2=new Account(1)),它们是完全独立的对象,identityHashCode可能完全不同,此时无法保证不同线程中对应业务实体的哈希值大小关系一致。
这种场景下,依赖identityHashCode的锁排序逻辑会直接失效:线程A中myAccount(akk1Thread1)的哈希值可能大于yourAccount(akk2Thread1),而线程B中yourAccount(akk1Thread2)的哈希值可能小于myAccount(akk2Thread2),导致两个线程的锁获取顺序完全相反,死锁风险依然存在。
正确的解决方式是基于Account的业务唯一标识(如账号ID)进行排序,而非对象本身的身份哈希。比如每个Account都有一个id字段,通过比较id的大小来决定锁的获取顺序:
public void transferMoney(Account from, Account to, int amount) throws InterruptedException { Account first = from.id < to.id ? from : to; Account second = from.id < to.id ? to : from; synchronized (first) { synchronized (second) { from.debit(amount); to.credit(amount); } } }
这样无论线程拿到的是哪个实例,只要是同一个业务账号(id相同),排序逻辑就完全一致,锁的获取顺序不会混乱。
二、该示例是否仅在应用存在全局Account缓存时才有效?
是的,Listing10.3的锁排序方案**必须依赖“同一个业务账号对应唯一的Account对象实例”**的前提,也就是应用存在全局缓存(比如将Account实例存入全局Map,通过账号ID获取)或者对象池。
只有在这种场景下,线程A和线程B操作的同一个业务账号对应的是同一个Account实例,它们的System.identityHashCode值完全相同,锁的排序逻辑才能保证一致,避免死锁。如果没有全局缓存,每个线程自行创建Account实例,哪怕equals相等,只要是不同对象,identityHashCode就可能不同,锁排序逻辑会直接失效,死锁依然可能发生。
内容的提问来源于stack exchange,提问作者Belenot

