调用自身时 EJB Singleton WRITE 锁不被尊重
EJB Singleton WRITE lock not honored when calling self
我已将此 EJB 部署到 Glassfish 4 (Java EE 7):
import javax.ejb.EJB;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
@Singleton
@Lock(LockType.WRITE) // WRITE is default, but here for emphasis
public class SingletonBean {
@EJB
SingletonBean self;
@Schedule(second="*/3", minute="*", hour="*")
public void test_automatic_timer() throws InterruptedException {
System.out.println("test_automatic_timer()");
self.test();
}
public void test() {
System.out.println("test()");
}
}
我预计这会陷入僵局。当定时器调用test_automatic_timer
时,它获得了WRITE锁。对 self.test()
的调用应该永远等待锁被释放,但在日志中我有:
Info: test_automatic_timer()
Info: test()
Info: test_automatic_timer()
Info: test()
为什么这不像我预期的那样工作?
p.s。这不完全是学术性的。我想进行 self
调用以便获得 a new container-managed transaction,但我想确保我首先更好地理解同步。
确认史蒂夫的回答
史蒂夫的回答是正确的。为了确认这一点,我记录了线程 ID,发现它们是相同的。我也想确认锁是否起作用,所以我重写了方法体如下:
@Schedule(second="*/3", minute="*", hour="*")
public void test_automatic_timer() throws InterruptedException {
System.out.println("test_automatic_timer()");
System.out.println(Thread.currentThread());
self.test();
Thread.sleep(10_000);
}
@Asynchronous
public void test() {
System.out.println("test()");
System.out.println(Thread.currentThread());
}
如果这按预期工作,对 test
的异步调用将被阻塞十秒钟,直到 test_automatic_timer
完成。如果不行,test
会立即执行。日志显示:
Info: test_automatic_timer()
Info: Thread[__ejb-thread-pool11,5,main]
// 10 second pause...
Info: test()
Info: Thread[__ejb-thread-pool12,5,main]
所以它有效。
这段代码没有死锁,因为对 test()
的调用是在同一个(计时器执行)线程中进行的。
也就是说,我认为至少在像这样自我注入单例时的某些行为可能是未定义的。如果引入引用 self
的 @PostConstruct
方法会发生什么?
您的 bean 名称用词不当,因为 @Singleton
bean 是有状态的。
无论如何,这样做可能更安全:
@Singleton
@Lock(LockType.WRITE) // WRITE is default, but here for emphasis
public class SingletonBean {
@Resource
private SessionContext sessionContext;
@Schedule(second="*/3", minute="*", hour="*")
public void test_automatic_timer() throws InterruptedException {
System.out.println("test_automatic_timer()");
sessionContext.getBusinessObject(StatelessSessionBean.class).test();
}
@Transactional(REQUIRES_NEW)
public void test() {
System.out.println("test()");
}
}
这确实是使用新事务自调用 EJB 的方法。
我已将此 EJB 部署到 Glassfish 4 (Java EE 7):
import javax.ejb.EJB;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
@Singleton
@Lock(LockType.WRITE) // WRITE is default, but here for emphasis
public class SingletonBean {
@EJB
SingletonBean self;
@Schedule(second="*/3", minute="*", hour="*")
public void test_automatic_timer() throws InterruptedException {
System.out.println("test_automatic_timer()");
self.test();
}
public void test() {
System.out.println("test()");
}
}
我预计这会陷入僵局。当定时器调用test_automatic_timer
时,它获得了WRITE锁。对 self.test()
的调用应该永远等待锁被释放,但在日志中我有:
Info: test_automatic_timer()
Info: test()
Info: test_automatic_timer()
Info: test()
为什么这不像我预期的那样工作?
p.s。这不完全是学术性的。我想进行 self
调用以便获得 a new container-managed transaction,但我想确保我首先更好地理解同步。
确认史蒂夫的回答
史蒂夫的回答是正确的。为了确认这一点,我记录了线程 ID,发现它们是相同的。我也想确认锁是否起作用,所以我重写了方法体如下:
@Schedule(second="*/3", minute="*", hour="*")
public void test_automatic_timer() throws InterruptedException {
System.out.println("test_automatic_timer()");
System.out.println(Thread.currentThread());
self.test();
Thread.sleep(10_000);
}
@Asynchronous
public void test() {
System.out.println("test()");
System.out.println(Thread.currentThread());
}
如果这按预期工作,对 test
的异步调用将被阻塞十秒钟,直到 test_automatic_timer
完成。如果不行,test
会立即执行。日志显示:
Info: test_automatic_timer()
Info: Thread[__ejb-thread-pool11,5,main]
// 10 second pause...
Info: test()
Info: Thread[__ejb-thread-pool12,5,main]
所以它有效。
这段代码没有死锁,因为对 test()
的调用是在同一个(计时器执行)线程中进行的。
也就是说,我认为至少在像这样自我注入单例时的某些行为可能是未定义的。如果引入引用 self
的 @PostConstruct
方法会发生什么?
您的 bean 名称用词不当,因为 @Singleton
bean 是有状态的。
无论如何,这样做可能更安全:
@Singleton
@Lock(LockType.WRITE) // WRITE is default, but here for emphasis
public class SingletonBean {
@Resource
private SessionContext sessionContext;
@Schedule(second="*/3", minute="*", hour="*")
public void test_automatic_timer() throws InterruptedException {
System.out.println("test_automatic_timer()");
sessionContext.getBusinessObject(StatelessSessionBean.class).test();
}
@Transactional(REQUIRES_NEW)
public void test() {
System.out.println("test()");
}
}
这确实是使用新事务自调用 EJB 的方法。