简体   繁体   中英

concurrentHashMap and Atomic Values

My Rest API works fine. However, I'm concerned about concurrency issues, though I've tested via scripts and have yet to see any. In my studies, I encountered some material with regards to utilizing Atomic Values with concurrentHasMap to avoid what amounts to dirty reads. My questions is twofold. First, should I be concerned, given my implementation? Second, if I should be, what would be the most prudent way to implement Atomic values, if indeed I should? I've contemplated dropping the wrapper class for the RestTemplate and simply passing a String back to the Angular 4 component as a catalyst for speed, but given I may use the value objects elsewhere, I'm hesitant. See, implementation below.

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

 }

Changed implementation of IQuoteService and implementation TickerService to use concurrenHashMap with 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;

The code works precisely as it did prior with the with the new implementation being that it "should" ensure that updates to values are not partially read prior to being completely written, and the values obtained should be consistent. Given, I could find no sound examples and acquire no answers on this topic, I will test this and post any issues I find.

The main concurrency risks with this code come about if refreshQuotes() was to be called concurrently. If this is a risk, then refreshQuotes just needs to be marked as synchronized.

Working on the premise that refreshQuotes() is only ever called once at a time, and that Quote/LocalDateTime are both immutable; then the question appears to be does updating immutable values within a ConcurrentHashMap risk dirty reads/writes. The answer is no, the values are immutable and ConcurrentHashMap handles the concurrency of updating the references.

For more information, I strongly recommend reading JSR133 (The Java Memory Model) . It covers in some detail when data will and will not become visible between threads. Doug Lea's JSR133 Cookbook will almost certainly give you far more information than you ever wanted to know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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