首页 > 解决方案 > 线程安全是这里的问题吗?

问题描述

上下文:我有一个服务类,它有一个公共foo方法(将从休息控制器调用),它又会调用私有fooHelper方法,传递一个列表对象,该对象在其他几个私有方法调用后填充fooHelper。这样做的原因是我想保持foo方法简短(请建议我创建 private 的方法是否错误fooHelper)。

问题:线程安全可能是这里的一个问题吗?如果多个线程foo同时访问方法(参考传递的列表对象)?我知道我可以使用不可修改列表,但我的问题是特定于当前方法是否存在线程安全问题的场景?

我用谷歌搜索并阅读了几篇文章,根据他们的说法,这不应该foo是创建的问题,new List因此没有线程将共享相同的列表。我的理解正确吗?

任何帮助将不胜感激,如果我无法清楚地提出我的问题,也请原谅。欢迎任何编辑或建议。

@RequiredArgsConstructor
@Service
public class A {


    @Override
    public List<RoundUpResponse> foo(String token) {

        final List<RoundUpResponse> roundUpResponseList = new ArrayList<>();
        final Accounts accounts = getAccounts(token);
        final List<Account> accountList = accounts.getAccounts();

        accountList.forEach(account -> fooHelper(token, roundUpResponseList, account));
        return roundUpResponseList;
    }

private void fooHelper(String token, List<RoundUpResponse> roundUpResponseList, Account account) {
        final FeedItems transactionFeed = getTransactionFeed(token, account); // private
        final SavingsGoal savingsGoal = getSavingsGoal(token, account); // private
        final Long spareChange = calculateSpareChange(transactionFeed, savingsGoal); // private
        final SavingsGoalTopUp savingsGoalTopUpPayload = createTopUpPayload(account, // privatespareChange);
        final String savingsGoalTransferId = topUpSavingsGoal(token, account, savingsGoal, savingsGoalTopUpPayload); // private
        roundUpResponseList.add(roundUpResponseService.createResponsePerAccount(account, savingsGoal.getName(), savingsGoalTopUpPayload, savingsGoalTransferId)); // private
    }

标签: javaspring-bootthread-safety

解决方案


代码可能是线程安全的,也可能不是线程安全的,具体取决于getAccounts()方法。这些方法应该以线程安全的方式返回其对象底层结构的防御性副本。然后你的代码将是线程安全的。另一方面,例如,如果accounts.getAccounts()只返回对内部帐户列表的引用,那么当您在foo方法中迭代该列表时,另一个线程可能会即时修改该列表。所以,确保它getAccounts是安全的,然后你的代码是好的。

更新

对 getAccounts() 有不同的调用堆栈与其线程安全无关。这是一个非线程安全的实现

class Accounts {
    private List<Account> accounts;
    public List<Account> getAccounts() {
        return accounts;
    }
}

在这种情况下,每个调用线程都将获得对内部结构的相同引用,因此在您的foo方法中执行之后

final List<Account> accountList = accounts.getAccounts();

在 thread1 中,一些 thread2 可以修改 accountList,它可以中断在foo. 您应该创建防御副本,例如:

class Accounts {
    private List<Account> accounts;
    public List<Account> getAccounts() {        
        synchronized(this) {  
          return new ArrayList<>(accounts);
        }
    }
}

注意synchronized并返回对列表副本的引用,我们以一种希望线程安全的方式创建它(“希望”,因为我们必须确保对 的所有修改也accountssynchronized在同一个对象上)。

在您的实现中,您通过执行 REST API 调用来获取列表,该调用应该为每个调用返回一个新列表,独立于另一个。所以你的实现是安全的。


推荐阅读