Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design: Object with single use?

Context

I have a class called ImageLoader.

When I call ImageLoader's getPicture( pictureID ), if the picture is not cached, I store pictureID in an instance variable, create a new thread which eventually calls my callback() function in this same ImageLoader class. callback() then uses this pictureID as the key to cache the picture.

Problem

Do you see the pickle? If I call getPicture( somePictureID ) twice in a row, and the callback() function for the first call hasn't happened yet, I will overwrite the previous pictureID which the callback() function will use. (See the code below if you still don't see the pickle.)

Now I know you're thinking, why don't I just add a little synchronization on the variable to make it thread-safe?

Because who knows how long the thread querying the picture will take, and that could really slow down the whole process of loading multiple images.

How I'm Thinking of Solving It

So my bright idea is to create an inner class that servers as a slave and only has a single use. I execute some function in this slave class once, and I never reuse the object.

Question

Is this an appropriate approach to solving this kind of problem? I feel like this might fall into some pattern I'm not aware of. If I'm totally off the mark with this one, can you suggest another solution?

Simplified Code

//the instance variable with a race condition
private int pictureID

//loads the image associated with the pictureID
public void getPicture( int pictureID )
{
     Bitmap bitmap = cache.get( pictureID );
     if ( bitmap != null )
     {
         //load image
         return; 
     }

     int post_params[] = { pictureID, /* more parameters */ };

     this.pictureID = pictureID; //PROBLEM: race condition!
     new HttpRequest( this ).execute( post_params ); //starts new thread and queries server
}

 //gets called when the query finishes, json contains my image
 public void callback( JSONObject json )
 {
    Bitmap bitmap = getBitmap( json ); //made up method to simplify code
    cache.put( this.pictureID, bitmap ); //PROBLEM: race condition!

    //load image
 }
like image 315
Kacy Avatar asked Nov 05 '25 16:11

Kacy


1 Answers

To me this looks like a classic case of the Memoizer pattern, and the easiest way to use it is probably Guava's CacheBuilder, something like:

private LoadingCache<Integer, Image> cache = CacheBuilder.newBuilder().
        build(new CacheLoader<Integer, Image>() {
            @Override
            public Image load(Integer key) throws Exception {
                return httpGetImageExpensively(key);
            }
        });

public Image getPicture(int pictureId) {
   return cache.getUnchecked(pictureId); // blocks until image is in cache
}

Now, the HTTP request will happen in the first thread that calls getPicture() for a given ID; subsequent callers with that ID will block until the first thread has put the image in cache, but you can make multiple concurrent requests for different IDs.

Things to think about:

  • How slow is loading going to be? Do you need a timeout mechanism?
  • What if the loading fails? The cache.get() method throws a (checked) ExecutionException, which is why I'm using getUnchecked() instead, but that just throws an (unchecked) UncheckedExecutionException instead. If possible, I would do most of the error handling in httpGetImageExpensively(), and think through the possible cases there (bad ID, DNS failure, etc.) but the callers of getPicture() will still need to deal with that somehow.
  • If loading a given ID fails once, is it going to fail every time? If so, you probably don't want to waste time re-HTTP-GETting it every time, and you need to add a mechanism for that. Note though that the question of whether it's worth retrying may have a different answer for different types of failure.
  • This implementation blocks each calling thread until there's an image ready for it. Is that workable for this application? If not, you may want to replace getPicture() with something a little fancier that takes an image handler callback object or lambda, and then internally put the actual calls to httpGetImageExpensively() on an ExecutorService, so getPicture() can return immediately. (Depending on the application, there may be other issues, e.g. in a Swing app it might be desirable to invoke the callback on the event dispatch thread.)
like image 153
David Moles Avatar answered Nov 07 '25 11:11

David Moles