concurrentHashMap和Atomic Values

时间:2017-12-24 06:58:13

标签: java angular spring-boot atomic concurrenthashmap

我的Rest API工作正常。但是,我关注并发问题,虽然我已经通过脚本进行了测试,但还没有看到任何问题。在我的研究中,我遇到了一些关于利用concurrentHasMap使用原子值的材料,以避免相当于脏读。我的问题是双重的。首先,考虑到我的实施,我应该担心吗?第二,如果我应该,那么实施原子价值最谨慎的方法是什么呢?我已经考虑为RestTemplate删除包装类并简单地将String传递回Angular 4组件作为速度的催化剂,但考虑到我可能在其他地方使用值对象,我犹豫不决。参见下面的实现。

@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);
       }
   }

 }

2 个答案:

答案 0 :(得分:0)

更改了IQuoteService和实现TickerService的实现,以将concurrenHashMap与原子参考一起使用:

@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;

代码的工作原理与之前的实现完全相同,新实现是“应该”确保在完全写入之前不会部分读取值的更新,并且获得的值应该是一致的。鉴于,我找不到合理的例子并且没有得到关于这个主题的答案,我将对此进行测试并发布我发现的任何问题。

答案 1 :(得分:0)

如果要同时调用refreshQuotes(),则出现此代码的主要并发风险。如果存在这种风险,则只需将refreshQuotes标记为已同步。

在这样的前提下,一次只能调用一次refreshQuotes(),并且Quote / LocalDateTime都是不可变的。那么问题似乎是在ConcurrentHashMap中更新不可变值是否存在脏读/写风险。答案是否定的,值是不可变的,并且ConcurrentHashMap处理并发更新引用。

有关更多信息,我强烈建议阅读JSR133 (The Java Memory Model)。它详细介绍了线程之间何时将出现数据以及何时将看不到数据。道格·李的JSR133 Cookbook几乎可以肯定会为您提供比您想知道的更多的信息。