使用synchronized关键字的Spring @Transactional不起作用

时间:2017-01-20 16:09:02

标签: java spring synchronized transactional

让我们说我有一个类似这样的方法的java类(只是一个例子)

@Transactional
public synchronized void onRequest(Request request) {

    if (request.shouldAddBook()) {
        if (database.getByName(request.getBook().getName()) == null) {
            database.add(request.getBook());
        } else {
            throw new Exception("Cannot add book - book already exist");
        }
    } else if (request.shouldRemoveBook()) {
        if (database.getByName(request.getBook().getName()) != null) {
            removeBook();
        } else {
            throw new Exception("Cannot remove book - book doesn't exist");
        }
    }
}

假设这本书被删除,然后重新添加了一个新作者或其他小改动,所以这个方法可能会从另一个系统快速调用两次,首先删除Book,然后再添加相同的Book(与一些新的细节)。

为了解决这个问题,我们可以尝试(像我一样)添加上面的@Transactional代码,然后同步'同步'当@Transactional不起作用时。但奇怪的是,它在第​​二次通话中失败了

"无法添加书本已存在"。

我花了很多时间试图解决这个问题,所以我想我会分享答案。

4 个答案:

答案 0 :(得分:9)

当删除并立即添加一本书时,如果我们没有“@Transactional”或“synchronized”,我们将从这个线程执行开始:

T1:| -----删除书籍----->

T2:| -------添加书------->

synchronized关键字可确保方法一次只能由一个线程运行。这意味着执行变为:

T1:| -----删除书-----> T2:| --------添加书------>

@Transactional注释是一个方面,它的作用是在类周围创建代理 java类,添加一些代码(开始交易 )在方法调用之前,调用该方法,然后调用其他一些代码(提交事务)。所以第一个线程现在看起来像这样:

T1:| - 春天开始交易 - | -----删除书----- | - 春天提交交易--->

或更短:T1:| -B- | -R- | -C - >

和第二个线程是这样的:

T2:| - 春天开始交易 - | -------添加书------- | - 春天提交交易--->

T2:| -B- | -A- | -C - >

请注意,@Transactional注释仅锁定同时修改数据库中的同一实体。由于我们正在添加一个不同的实体(但具有相同的书名),因此它没有多大好处。但它仍然不应该是正确的吗?

这里是有趣的部分:

Spring添加 的事务代码不是synchronized方法的一部分 ,因此T2线程实际上可以在“commit”代码运行完毕之前启动其方法,在完成第一个方法调用之后。像这样:

T1:| -B- | -R- | -C-- | - >

T2:| -B ------ | -A- | -C - >

因此。当“add”方法读取数据库时,删除代码已经运行,但不是提交代码,所以它仍然在数据库中找到对象并抛出错误。几毫秒之后,它将从数据库中消失。

删除@Transactional注释会使synchronized关键字按预期工作,尽管这不是其他人提到的好解决方案。删除synchronized并修复@Transactional注释是一种更好的解决方案。

答案 1 :(得分:4)

您需要设置事务隔离级别以防止数据库中的脏读,而不必担心线程安全。

@Transactional(isolation = Isolation.SERIALIZABLE)
public void onRequest(Request request) {

    if (request.shouldAddBook()) {
        if (database.getByName(request.getBook().getName()) == null) {
            database.add(request.getBook());
        } else {
            throw new Exception("Cannot add book - book already exist");
        }
    } else if (request.shouldRemoveBook()) {
        if (database.getByName(request.getBook().getName()) != null) {
            removeBook();
        } else {
            throw new Exception("Cannot remove book - book doesn't exist");
        }
    }
}

以下是对事务传播和隔离的出色解释。

  

Spring @Transactional - isolation, propagation

答案 2 :(得分:1)

应该在@Transaction方法之前使用'synchronized'。 否则,在多线程时,对象已解锁,但未提交事务。

答案 3 :(得分:0)

我将“同步”添加到 onRequest 的调用方方法并将事务的隔离更改为 READ_UNCOMMITTED。这可以解决问题。但我很好奇为什么只将“同步”移动到调用者方法不起作用。因为这样做,方法执行过程如下:持有同步锁,持有事务锁,释放事务锁,释放同步锁。然而,第四步似乎发生在第三步之前。所以修改事务隔离是必要的。有人知道原因吗?

相关问题