繁体   English   中英

parallelHashMap和原子值

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM