有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

spring引导服务中的java线程

你好,有类似的问题,但找不到对我有帮助的答案。 在SpringBoot应用程序中,我有一个服务类来在帐户之间转账,在这里我使用细粒度锁定。如果线程属于同一个帐户,它们将被锁定

@Service
public class AccountServiceImpl implements AccountService {

static final HashMap<Long, ReentrantLock> locks = new HashMap<Long, ReentrantLock>();

    private ReentrantLock getLock(Long id) {
        synchronized (locks) {
            ReentrantLock lock = locks.get(id);
            if (lock == null) {
                lock = new ReentrantLock();
                locks.put(id, lock);
            }
            return lock;
        }
      }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public List<Transaction> transferMoney(Long sourceAccountId, Long targetAccountId, Currency currency, BigDecimal amount) {

        Lock lock = getLock(sourceAccountId);
        try {
            lock.lock();
       Balance balance = getBalance(sourceAccountId, currency);
                System.out.println("BALANCE BEFORE TRANSFER " + balance.getBalance()); 
        //createTransactions here using transactionService.create(transactions)
        accountRepository.refresh(accountRepository.findOne(sourceAccountId))  
        balance = getBalance(sourceAccountId, currency);
        System.out.println("BALANCE AFTER TRANSFER " + balance.getBalance()); 
        return transactions;
       }finally{
           lock.unlock()
        }


}

除了使用ApacheJMeter发送多个并行请求外,它的工作原理与预期基本相同。如果我发送多个请求,从账户转账100美元,并通过1000余额控制台输出如下:

BALANCE BEFORE TRANSFER 1000
BALANCE AFTER TRANSFER 900
BALANCE BEFORE TRANSFER 1000
BALANCE AFTER TRANSFER 900
BALANCE BEFORE TRANSFER 900
BALANCE AFTER TRANSFER 800
BALANCE BEFORE TRANSFER 800
BALANCE AFTER TRANSFER 700
BALANCE BEFORE TRANSFER 700
BALANCE AFTER TRANSFER 600
BALANCE BEFORE TRANSFER 700
BALANCE AFTER TRANSFER 600

因此,它大部分工作正常,但在某些点上,它没有得到更新的余额。到目前为止,我尝试了一切,传播和隔离。手动创建事务,并在线程解除锁定之前对其进行复制。似乎什么都不管用。在我使用之前

Propagation.REQUIRES_NEW

控制台输出始终为空

BALANCE BEFORE TRANSFER 1000
BALANCE AFTER TRANSFER 900

现在它有时起作用,有时不起作用。这并不一致

获取余额方法使用以下命令刷新帐户:

accountRepository.refresh()

和交易服务。createTransactions还使用传播进行注释。需要新的 有人能告诉我为什么这不起作用吗?至少用正确的方式引导我

多谢各位


编辑: 如果不清楚,则从数据库中读取足够的数据。使用springjpa


共 (2) 个答案

  1. # 1 楼答案

    正如GPI所回答和其他人所评论的,摆脱Java锁定,因为从长远来看,它的好处将抵消损失。通过使用-org.springframework.data.jpa.repository.Lock注释,spring数据jpa已经解决了这个问题

    只需使用-@Lock(LockModeType.PESSIMISTIC_WRITE)注释用于选择用于转账目的的帐户数据(帐户实体)的存储库方法。这将锁定所选数据,直到事务完成。我想,同一个存储库将用于从以及向帐户检索

    将转账金额应用于两个帐户,并在@Transactional服务方法中调用存储库上的save

    此外,建议也向存储库添加一个非锁定选择方法,以便在不需要锁定时(即用于非资金转账目的)可以使用该方法

    i have Service class to transfer money between accounts, where im using fine-grained locking. Threads gonna lock if they belong to the same account.

    使用这种方法,如果帐户相同,将自动发生锁定

  2. # 2 楼答案

    问题很可能是DB事务和java锁之间存在竞争

    另一个问题是,您只锁定两个帐户中的一个,但需要保护双方不受并发访问的影响。这将引入死锁的可能性

    DB/java锁竞争的情况是:

    1. HTTP请求命中您的控制器
    2. @事务启动数据库事务
    3. 你得到一个Java锁
    4. 您执行该操作时,尚未将任何内容刷新到DB
    5. 您释放了java锁,但您的Controller方法尚未返回,因此JPA事务不会刷新到DB
    6. 另一个请求进入,打开一个事务,并“看到世界”原样,即不刷新任何内容(例如步骤“0”)
    7. 无论现在发生什么,您都有两个事务,其中一个事务根据您的需要对世界有“错误”的看法

    现在想象一下,如果您的程序有多个实例(例如故障切换、负载共享),那么您的java锁甚至无法工作,这真是一场噩梦:-)

    做到这一点的“简单”方法(在这种复杂程度上)是“选择以更新”您的实体,这将防止选择的交叉。您甚至不需要java锁,SQL引擎将以本机方式提供锁(在提交第一个事务之前,不会返回第二个select)

    但是您仍然有死锁的风险(如果出现两个请求,一个是针对帐户a到B的请求,另一个是针对帐户B到C的请求,这可能导致B在两个事务中被锁定,您必须捕获并重播该案例,希望冲突能够在该时间点得到解决)

    您可以在:https://www.baeldung.com/jpa-pessimistic-locking处进行读取,以了解如何执行SELECT FOR UPDATE,这基本上需要像这样加载实体:

    entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);
    

    另一种可能是反转java锁和@transactionnal。这样,在处于独占模式之前,您将永远不会点击DB。但这将导致多个JVM中的多个程序实例无法共享锁的问题。如果这不是你的情况,那么这可能更简单

    在这两种情况下,您仍然必须在两侧锁定(DB或Java锁)并考虑死锁