While generating the page, choose a new unique identifier for each image that the page will display. This might be something simple like a number obtained by incrementing an AtomicInteger, or it might be something more sophisticated like a UUID to prevent users from guessing other users' image URLs. Put these unique identifiers into the URLs that the client will use to retrieve the images.
Once you've chosen the identifier for an image, construct a Callable that will generate and return the image, and submit it to a ThreadPoolExecutor to be run asynchronously. This gives you back a Future which can be used to retrieve the result. Save the Future in a map, with the image's identifier as the key.
Later, when the client requests the image, you can take the image identifier and look it up in the map to find the associated Future object. Calling get()
on the Future will return the image, waiting for the generator to finish if necessary. (If the requested identifier isn't found in the map, return a 404 error.)
To avoid filling up the server's memory with old images, you'll probably want to discard them after a few minutes. To do that, each time you create a task and store its Future into the map of available images, you can put a task into a DelayQueue that'll remove the entry from the map after some suitable delay. Use a daemon thread to take items from this queue and act on them, in a loop.
It'd also be a good idea to call cancel(true)
on the Future when removing it from the map, in case the generator was still running for some reason. (Otherwise it would continue running even though the image will no longer be accessible anyway.)