【发布时间】:2018-10-15 20:50:24
【问题描述】:
我最近遇到了一个竞争条件问题,因为在默认范围(单例范围)服务类中声明了一个实例变量。实例变量的目的是使我的代码更具可读性,并避免将同一变量不断传递给 Service 类中的不同私有方法。示例如下:
@Service
public class SomeServiceImpl implements SomeService {
private final StatusRepository statusRepository;
private Predicate<Status> statusPredicate;
@Autowired
public SomeServiceImpl(StatusRepository statusRepository) {
this.statusRepository = statusRepository;
}
@Override
public List<Status> getAllowedStatuses(String userId) {
statuses = statusRepository.getAll();
initPredicate();
appendPredicateA();
appendPredicateB();
List<Status> results = statuses.stream()
.filter(statusPredicate)
.collect(Collectors.toList());
return results;
}
private void initPredicate() {
statusPredicate = p -> p.getDefault().equals("default");
}
private void appendPredicateA() {
statusPredicate.and(p -> p.getA().equals("A"));
}
private void appendPredicateB() {
statusPredicate.and(p -> p.getB().equals("B"));
}
}
这是我想要实现的目标的一个非常简单的示例。这显然不是线程安全的,因为现在服务类是有状态的。我可以通过将 statusPredicate 变量转换为局部变量并让 void 方法返回谓词来简单地解决此问题在它被附加了新的条件之后,它会变得像这样混乱:
@Override
public List<Status> getAllowedStatuses(String userId) {
statuses = statusRepository.getAll();
Predicate<Status> statusPredicate = p -> p.getDefault().equals("default");
statusPredicate = appendPredicateA(statusPredicate);
statusPredicate = appendPredicateB(statusPredicate);
List<Status> results = statuses.stream()
.filter(statusPredicate)
.collect(Collectors.toList());
return results;
}
它会不断调用修改变量并返回变量。
我知道一些可以解决此问题的解决方案,例如在 Service 类中添加 @RequestScope 以确保来自 HTTP 的每个请求都会获得一个新的 服务对象,或在 Predicate 变量上使用 ThreadLocal。但是,我不太确定什么是最好的方法,以及在 Service 类中声明实例变量是否可以开始。如果从一开始就使 Service 类有状态是不好的,我应该如何构造我的代码以使其更清洁并仍然保持无状态?
请指教!在此先感谢:D
【问题讨论】:
-
出于您怀疑的原因,将服务类设为有状态实际上是不好的。通常最好选择您的其他选择,这确实不是那么糟糕。如果您开始获得大量自定义谓词,则始终可以将它们放在一个列表中并遍历该列表。
-
原代码还不错,可以写的更简洁一些。正在考虑的选项都更糟。
-
@chrylis 感谢您的建议! :D
-
@NathanHughes 您能否详细说明为什么正在考虑的选项都更糟?是不是因为它们违反了单例的契约以及使用 ThreadLocal 产生的潜在不必要的复杂性?另外,你能提供一个关于如何更简洁地编写示例代码的小例子吗?提前致谢!
标签: java spring multithreading spring-mvc thread-safety