2
\$\begingroup\$

What I've created is a CacheBuilder service that will be passed in a list of items, and will build a results cache. The results are produced by making calls to another service (AsyncService) which will take a variable amount of time.

At the same time, other consumers can be checking the results cache, to see if the items exist.

Requirements are:

  • The call the start producing the cache is non-blocking to caller.
  • When the CacheBuilder receives a result, it performs some processing on it (processResult(String str).
  • The results are stored in a id -> result lookup. (HashMap<String, String> resultsCache)
  • The resultsCache can be accessed by outside consumers, even if it hasn't been finished building yet.

Here is the complete code.

AsyncService

/**
* A simple mock of a database or server call for example. 
* Sleeps for a random amount of time before returning. 
* Returns a random int. 
**/
@Service
public class AsyncService { 
    //private int randInt(int min, int max)...

    @Async
    public Future<String> serviceCall(String id) throws InterruptedException{       
        Thread.sleep(randInt(1000, 3000));
        return new AsyncResult<String>(id + ":" + randInt(0, 100));             
    }       
}

CacheBuilder

/**
* The main logic. 
  Receives a list of ids and will populate the resultsCache with it. 
**/
@Component
public class CacheBuilder {
    @Autowired
    private AsyncService aService;

    private HashMap<String, String> resultsCache;

    public HashMap<String, String> getResultsCache() {
        return resultsCache;
    }

    public void setResultsCache(HashMap<String, String> resultsCache) {
        this.resultsCache = resultsCache;
    }

    public String processResult(String str) {
        return str + "appended";
    }

    @Async
    public void createCache(String[] ids) throws InterruptedException,
            ExecutionException {

        HashMap<String, Future<String>> fMap = new HashMap<String, Future<String>>();

        //Clear results cache. 
        resultsCache = new HashMap<String, String>();

        for (String id : ids) {
            //Make service calls for each of the ids. 
            fMap.put(id, aService.serviceCall(id)); 
        }

        boolean complete = false;
        int i = 0;

        //Now loop over and keep checking if they're done. 
        while (!complete) {
            //This loop only checks one id at a time, before sleeping. 
            //Probably should check all of them. 
            String id = ids[i];
            Future<String> fs = fMap.get(id);

            if (fs.isDone()) {
                //If they are done:
                // - Process them
                // - Remove the id from the list of ids to check
                // - Add the processed result to the results cache. 
                String result = processResult(fs.get());
                System.out.println("adding: " + result);

                resultsCache.put(id, result);

                fMap.remove(id);
                ids = (String[]) ArrayUtils.remove(ids, i);

            } else {
                Thread.sleep(100);
            }

            if (ids.length == 0) {
                complete = true;
                break;
            }
            i = (i + 1) % ids.length;

        }

    }
}

AsyncTester (Our consumer)

public void run() throws InterruptedException, ExecutionException{
     String[] ids = {"foo", "bar", "biz", "bash"};     
        //Start building cache
        cb.createCache(ids);            

        //Immediately start checking cache to see if the results are there
        for (int i = 0; true; i++){
            Thread.sleep(100);
            i = i %ids.length;
            String id = ids[i]; 
            String result = cb.getResultsCache().get(id);

            //Do what's required depending on whether the result is there or not. 
            if (result != null){
                System.out.println(id + ": Result exists!" + result);
                ids = (String[]) ArrayUtils.remove(ids, i);
                if (ids.length ==0) break;
            }
            else
            {
                System.out.println(id + ": Result doesn't exist yet");
            }
        }           
}

Now what I'm interested in is my use of @Async and Future and the way I'm checking whether results are done.

Mainly what I'm concerned about is how in CacheBuilder I'm burning cycles repeatedly checking if the future is complete.

I'm wondering if this kind of problem is already well solved by an existing, preferably Spring, library.

Also concerned about:

  • Is there a potential for issues re: concurrent modification here?
  • I haven't put any error handling in, which doesn't bother me for the purposes of this demonstration. But does this design lend itself well to error handling?
\$\endgroup\$
2
  • \$\begingroup\$ Are you on Java 8? \$\endgroup\$
    – h.j.k.
    Mar 2, 2016 at 2:21
  • 1
    \$\begingroup\$ @h.j.k. - Java 7 - though there's no reason I can't be using 8, except that the project was started in 7. \$\endgroup\$
    – dwjohnston
    Mar 2, 2016 at 2:44

1 Answer 1

1
\$\begingroup\$
  1. To check future's result, it's better use Future.get(long timeout, TimeUnit unit) if you only need to check result sequential, which also support timeout parameter.
  2. HashMap is not thread safe for structural modification.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.