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时,它会获得写锁。对self.test()的调用应该永远等待锁被释放,但在日志中我有:

Info:   test_automatic_timer()
Info:   test()
Info:   test_automatic_timer()
Info:   test()

为什么这没有按照我预期的方式工作?

附言这并不完全是学术性的。我想执行self调用,以便可以获取新的容器管理的事务,但我想确保首先更好地了解同步。


确认史蒂夫的答案

史蒂夫的回答是正确的。为了确认这一点,我记录了线程 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方法会发生什么?

你的豆名用词不当,因为@Singleton豆子是有状态的。

无论如何,做这样的事情可能更安全:

@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 的方法。

最新更新