EJB单例同一实例内方法调用的锁机制及代码场景疑问
这个问题问到了EJB单例锁机制里一个很容易踩坑的点,我来给你详细拆解下:
首先直接给你结论:当单例实例内部调用自身其他方法时,不会重新应用目标方法的@Lock注解,而是直接继承当前调用上下文的锁。放到你的代码里,doSomethingReadButCanDoWrite()方法持有的是READ锁,所以内部调用doSomethingWrite()时,用的还是READ锁,而不是方法上标注的WRITE锁。
为什么会这样?
EJB容器是通过方法拦截器来管理单例锁的——只有当外部客户端直接调用单例方法时,容器的拦截器才会介入,检查方法上的@Lock注解,然后分配对应的锁(READ共享锁/WRITE独占锁)。
但如果是单例自己内部的方法调用(也就是同一个对象里的方法互相调用),这个调用不会经过容器的拦截器,相当于普通的Java对象方法调用,所以目标方法上的@Lock注解会被完全忽略,直接沿用当前线程已经持有的锁。
你的代码会有什么问题?
你的doSomethingWrite()方法标注了WRITE锁,本意是希望这个方法执行时独占单例,避免并发写冲突。但如果是从READ锁的方法内部调用它,实际用的是READ共享锁——这意味着多个线程可能同时进入doSomethingWrite(),直接破坏了WRITE锁的独占性,很容易引发数据库数据不一致的问题。
怎么解决?
如果需要在READ锁方法里执行WRITE操作,你需要通过容器代理来调用自己,让调用重新经过容器的拦截器,从而触发目标方法的锁检查。最常用的方式是通过SessionContext获取自己的业务代理:
@Singleton public class MySingleton { @Resource private SessionContext sessionContext; @Lock(LockType.WRITE) public void doSomethingWrite(){ // 数据库写操作 } @Lock(LockType.READ) public void doSomethingRead(){ // 数据库读操作 } @Lock(LockType.READ) public void doSomethingReadButCanDoWrite(){ if(somethingDoesNotExistInDB()){ // 获取自身的代理实例,通过代理调用方法 MySingleton selfProxy = sessionContext.getBusinessObject(MySingleton.class); selfProxy.doSomethingWrite(); // 这里会触发WRITE锁的检查和获取 } doSomethingRead(); } }
这样调用时,容器会拦截代理的方法调用,检查doSomethingWrite()的WRITE锁注解。此时当前线程持有READ锁,需要等待所有持有READ锁的线程释放后,才能获取WRITE锁(符合EJB锁的互斥规则:WRITE锁与所有READ/WRITE锁互斥)。
额外提醒
不止是锁,单例内部直接调用方法还会绕开容器的其他管理特性,比如事务上下文、自定义拦截器、安全检查等。所以只要你需要容器来管理这些特性,都应该通过代理来调用,而不是直接内部调用。
内容的提问来源于stack exchange,提问作者Makhz

