我的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;


该代码与新实现完全一样,其工作原理是:“应”确保在完全写入之前不会部分读取值的更新,并且获取的值应保持一致。给出的,我找不到合适的例子,也没有答案,我将对其进行测试并发表任何发现的问题。

10-08 06:33