[英]concurrentHashMap and Atomic Values
我的Rest API正常工作。 但是,尽管我已经通过脚本进行了测试,但是还没有发现任何问题,但是我担心并发问题。 在我的研究中,我遇到了一些有关在并发HasMap中使用原子值以避免造成脏读的问题。 我的问题是双重的。 首先,考虑到实施情况,我应该担心吗? 第二,如果我应该的话,如果确实应该的话,实现原子值的最审慎的方法是什么? 我已经考虑过删除RestTemplate的包装器类,并简单地将String返回给Angular 4组件作为速度的催化剂,但是鉴于我可能在其他地方使用value对象,我很犹豫。 参见下面的实现。
@Service
@EnableScheduling
public class TickerService implements IQuoteService {
@Autowired
private ApplicationConstants Constants;
private ConcurrentHashMap<String,Quote> quotes = new ConcurrentHashMap<String, Quote>();
private ConcurrentHashMap<String,LocalDateTime> quoteExpirationQueue = new ConcurrentHashMap<String, LocalDateTime>();
private final RestTemplate restTemplate;
public TickerService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Quote getQuote(String symbol) {
if (this.quotes.containsKey(symbol)){
Quote q = (Quote)this.quotes.get(symbol);
//Update Expiration
LocalDateTime ldt = LocalDateTime.now();
this.quoteExpirationQueue.put(symbol, ldt.plus(Constants.getQuoteExpirationMins(),ChronoUnit.MINUTES));
return q;
} else {
QuoteResponseWrapper qRes = this.restTemplate.getForObject( Constants.getRestURL(symbol), QuoteResponseWrapper.class, symbol);
ArrayList<Quote> res = new ArrayList<Quote>();
res = qRes.getQuoteResponse().getResult();
//Add to Cache
quotes.put(symbol, res.get(0));
//Set Expiration
LocalDateTime ldt = LocalDateTime.now();
this.quoteExpirationQueue.put(symbol, ldt.plus(Constants.getQuoteExpirationMins(),ChronoUnit.MINUTES));
return res.get(0);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ConcurrentHashMap<String,Quote> getQuotes(){
return this.quotes;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Scheduled(fixedDelayString = "${application.quoteRefreshFrequency}")
public void refreshQuotes(){
if (quoteExpirationQueue.isEmpty()) {
return;
}
LocalDateTime ldt = LocalDateTime.now();
//Purge Expired Quotes
String expiredQuotes = quoteExpirationQueue.entrySet().stream().filter(x -> x.getValue().isBefore(ldt)).map(p -> p.getKey()).collect(Collectors.joining(","));
if (!expiredQuotes.equals("")) {
this.purgeQuotes(expiredQuotes.split(","));
}
String allQuotes = quoteExpirationQueue.entrySet().stream().filter(x -> x.getValue().isAfter(ldt)).map(p -> p.getKey()).collect(Collectors.joining(","));
List<String> qList = Arrays.asList(allQuotes.split(","));
Stack<String> stack = new Stack<String>();
stack.addAll(qList);
// Break Requests Into Manageable Chunks using property file settings
while (stack.size() > Constants.getMaxQuoteRequest()) {
String qSegment = "";
int i = 0;
while (i < Constants.getMaxQuoteRequest() && !stack.isEmpty()) {
qSegment = qSegment.concat(stack.pop() + ",");
i++;
}
logger.debug(qSegment.substring(0, qSegment.lastIndexOf(",")));
this.updateQuotes(qSegment);
}
// Handle Remaining Request Delta
if (stack.size() < Constants.getMaxQuoteRequest() && !stack.isEmpty()) {
String rSegment = "";
while (!stack.isEmpty()){
rSegment = rSegment.concat(stack.pop() + ",");
}
logger.debug(rSegment);
this.updateQuotes(rSegment.substring(0, rSegment.lastIndexOf(",")));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void updateQuotes(String symbols) {
if (symbols.equals("")) {
return;
}
System.out.println("refreshing -> " + symbols);
QuoteResponseWrapper qRes = this.restTemplate.getForObject( Constants.getRestURL(symbols), QuoteResponseWrapper.class, symbols);
for (Quote q : qRes.getQuoteResponse().getResult()) {
this.quotes.put(q.getSymbol(), q);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void purgeQuotes(String[] symbols) {
for (String q : symbols) {
System.out.println("purging -> " + q);
this.quotes.remove(q);
this.quoteExpirationQueue.remove(q);
}
}
}
将IQuoteService和TickerService的实现更改为将concurrenHashMap与Atomic References结合使用:
@Autowired
private ApplicationConstants Constants;
private ConcurrentHashMap<AtomicReference<String>,AtomicReference<Quote>>
quotes = new ConcurrentHashMap<AtomicReference<String>,AtomicReference<Quote>> ();
private ConcurrentHashMap<AtomicReference<String>,AtomicReference<LocalDateTime>> quoteExpirationQueue = new ConcurrentHashMap<AtomicReference<String>,AtomicReference<LocalDateTime>>();
private final RestTemplate restTemplate;
该代码的工作方式与之前的实现完全相同,因为新实现是“应”确保在完全写入之前不会部分读取值的更新,并且获取的值应保持一致。 给出的,我找不到合适的例子,也没有任何关于该主题的答案,我将对其进行测试并发布任何发现的问题。
如果要同时调用refreshQuotes(),则会产生此代码的主要并发风险。 如果存在风险,则只需将refreshQuotes标记为已同步。
在前提下一次只能调用一次refreshQuotes(),并且Quote / LocalDateTime都是不可变的; 那么问题似乎是在ConcurrentHashMap中更新不可变值是否存在脏读/写风险。 答案是否定的,值是不可变的,并且ConcurrentHashMap处理并发更新引用。
有关更多信息,我强烈建议阅读JSR133(Java内存模型) 。 它详细介绍了线程之间何时将出现数据以及何时将看不到数据。 Doug Lea的JSR133 Cookbook几乎可以肯定会为您提供比您想知道的更多的信息。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.