org.imgscalr
Class AsyncScalr

java.lang.Object
  extended by org.imgscalr.AsyncScalr

public class AsyncScalr
extends Object

Class used to provide the asynchronous versions of all the methods defined in Scalr for the purpose of efficiently handling large amounts of image operations via a select number of processing threads asynchronously.

Given that image-scaling operations, especially when working with large images, can be very hardware-intensive (both CPU and memory), in large-scale deployments (e.g. a busy web application) it becomes increasingly important that the scale operations performed by imgscalr be manageable so as not to fire off too many simultaneous operations that the JVM's heap explodes and runs out of memory or pegs the CPU on the host machine, staving all other running processes.

Up until now it was left to the caller to implement their own serialization or limiting logic to handle these use-cases. Given imgscalr's popularity in web applications it was determined that this requirement be common enough that it should be integrated directly into the imgscalr library for everyone to benefit from.

Every method in this class wraps the matching methods in the Scalr class in new Callable instances that are submitted to an internal ExecutorService for execution at a later date. A Future is returned to the caller representing the task that is either currently performing the scale operation or will at a future date depending on where it is in the ExecutorService's queue. Future.get() or Future.get(long, TimeUnit) can be used to block on the Future, waiting for the scale operation to complete and return the resultant BufferedImage to the caller.

This design provides the following features:

Performance

When tuning this class for optimal performance, benchmarking your particular hardware is the best approach. For some rough guidelines though, there are two resources you want to watch closely:
  1. JVM Heap Memory (Assume physical machine memory is always sufficiently large)
  2. # of CPU Cores
You never want to allocate more scaling threads than you have CPU cores and on a sufficiently busy host where some of the cores may be busy running a database or a web server, you will want to allocate even less scaling threads.

So as a maximum you would never want more scaling threads than CPU cores in any situation and less so on a busy server.

If you allocate more threads than you have available CPU cores, your scaling operations will slow down as the CPU will spend a considerable amount of time context-switching between threads on the same core trying to finish all the tasks in parallel. You might still be tempted to do this because of the I/O delay some threads will encounter reading images off disk, but when you do your own benchmarking you'll likely find (as I did) that the actual disk I/O necessary to pull the image data off disk is a much smaller portion of the execution time than the actual scaling operations.

If you are executing on a storage medium that is unexpectedly slow and I/O is a considerable portion of the scaling operation (e.g. S3 or EBS volumes), feel free to try using more threads than CPU cores to see if that helps; but in most normal cases, it will only slow down all other parallel scaling operations.

As for memory, every time an image is scaled it is decoded into a BufferedImage and stored in the JVM Heap space (decoded image instances are always larger than the source images on-disk). For larger images, that can use up quite a bit of memory. You will need to benchmark your particular use-cases on your hardware to get an idea of where the sweet spot is for this; if you are operating within tight memory bounds, you may want to limit simultaneous scaling operations to 1 or 2 regardless of the number of cores just to avoid having too many BufferedImage instances in JVM Heap space at the same time.

These are rough metrics and behaviors to give you an idea of how best to tune this class for your deployment, but nothing can replacement writing a small Java class that scales a handful of images in a number of different ways and testing that directly on your deployment hardware.

Resource Overhead

The ExecutorService utilized by this class won't be initialized until one of the operation methods are called, at which point the service will be instantiated for the first time and operation queued up.

More specifically, if you have no need for asynchronous image processing offered by this class, you don't need to worry about wasted resources or hanging/idle threads as they will never be created if you never use this class.

Cleaning up Service Threads

By default the Threads created by the internal ThreadPoolExecutor do not run in daemon mode; which means they will block the host VM from exiting until they are explicitly shut down in a client application; in a server application the container will shut down the pool forcibly.

If you have used the AsyncScalr class and are trying to shut down a client application, you will need to call getService() then ExecutorService.shutdown() or ExecutorService.shutdownNow() to have the threads terminated; you may also want to look at the ExecutorService.awaitTermination(long, TimeUnit) method if you'd like to more closely monitor the shutting down process (and finalization of pending scale operations).

Reusing Shutdown AsyncScalr

If you have previously called shutdown on the underlying service utilized by this class, subsequent calls to any of the operations this class provides will invoke the internal checkService() method which will replace the terminated underlying ExecutorService with a new one via the createService() method.

Custom Implementations

If a subclass wants to customize the ExecutorService or ThreadFactory used under the covers, this can be done by overriding the createService() method which is invoked by this class anytime a new ExecutorService is needed.

By default the createService() method delegates to the createService(ThreadFactory) method with a new instance of DefaultThreadFactory. Either of these methods can be overridden and customized easily if desired.

TIP: A common customization to this class is to make the Threads generated by the underlying factory more server-friendly, in which case the caller would want to use an instance of the ServerThreadFactory when creating the new ExecutorService.

This can be done in one line by overriding createService() and returning the result of: return createService(new ServerThreadFactory());

By default this class uses an ThreadPoolExecutor internally to handle execution of queued image operations. If a different type of ExecutorService is desired, again, simply overriding the createService() method of choice is the right way to do that.

Since:
3.2
Author:
Riyad Kalla (software@thebuzzmedia.com)

Field Summary
static int THREAD_COUNT
          Number of threads the internal ExecutorService will use to simultaneously execute scale requests.
static String THREAD_COUNT_PROPERTY_NAME
          System property name used to set the number of threads the default underlying ExecutorService will use to process async image operations.
 
Constructor Summary
AsyncScalr()
           
 
Method Summary
static Future<BufferedImage> apply(BufferedImage src, BufferedImageOp... ops)
           
static Future<BufferedImage> crop(BufferedImage src, int width, int height, BufferedImageOp... ops)
           
static Future<BufferedImage> crop(BufferedImage src, int x, int y, int width, int height, BufferedImageOp... ops)
           
static ExecutorService getService()
          Used to get access to the internal ExecutorService used by this class to process scale operations.
static Future<BufferedImage> pad(BufferedImage src, int padding, BufferedImageOp... ops)
           
static Future<BufferedImage> pad(BufferedImage src, int padding, Color color, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, Scalr.Mode resizeMode, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, Scalr.Mode resizeMode, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Mode resizeMode, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Mode resizeMode, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static Future<BufferedImage> rotate(BufferedImage src, Scalr.Rotation rotation, BufferedImageOp... ops)
           
 
Methods inherited from class java.lang.Object
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

THREAD_COUNT_PROPERTY_NAME

public static final String THREAD_COUNT_PROPERTY_NAME
System property name used to set the number of threads the default underlying ExecutorService will use to process async image operations.

Value is "imgscalr.async.threadCount".

See Also:
Constant Field Values

THREAD_COUNT

public static final int THREAD_COUNT
Number of threads the internal ExecutorService will use to simultaneously execute scale requests.

This value can be changed by setting the imgscalr.async.threadCount system property (see THREAD_COUNT_PROPERTY_NAME) to a valid integer value > 0.

Default value is 2.

Constructor Detail

AsyncScalr

public AsyncScalr()
Method Detail

getService

public static ExecutorService getService()
Used to get access to the internal ExecutorService used by this class to process scale operations.

NOTE: You will need to explicitly shutdown any service currently set on this class before the host JVM exits.

You can call ExecutorService.shutdown() to wait for all scaling operations to complete first or call ExecutorService.shutdownNow() to kill any in-process operations and purge all pending operations before exiting.

Additionally you can use ExecutorService.awaitTermination(long, TimeUnit) after issuing a shutdown command to try and wait until the service has finished all tasks.

Returns:
the current ExecutorService used by this class to process scale operations.

apply

public static Future<BufferedImage> apply(BufferedImage src,
                                          BufferedImageOp... ops)
                                   throws IllegalArgumentException,
                                          ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.apply(BufferedImage, BufferedImageOp...)

crop

public static Future<BufferedImage> crop(BufferedImage src,
                                         int width,
                                         int height,
                                         BufferedImageOp... ops)
                                  throws IllegalArgumentException,
                                         ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.crop(BufferedImage, int, int, BufferedImageOp...)

crop

public static Future<BufferedImage> crop(BufferedImage src,
                                         int x,
                                         int y,
                                         int width,
                                         int height,
                                         BufferedImageOp... ops)
                                  throws IllegalArgumentException,
                                         ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.crop(BufferedImage, int, int, int, int, BufferedImageOp...)

pad

public static Future<BufferedImage> pad(BufferedImage src,
                                        int padding,
                                        BufferedImageOp... ops)
                                 throws IllegalArgumentException,
                                        ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.pad(BufferedImage, int, BufferedImageOp...)

pad

public static Future<BufferedImage> pad(BufferedImage src,
                                        int padding,
                                        Color color,
                                        BufferedImageOp... ops)
                                 throws IllegalArgumentException,
                                        ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.pad(BufferedImage, int, Color, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           int targetSize,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           Scalr.Method scalingMethod,
                                           int targetSize,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, Method, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           Scalr.Mode resizeMode,
                                           int targetSize,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, Mode, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           Scalr.Method scalingMethod,
                                           Scalr.Mode resizeMode,
                                           int targetSize,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, Method, Mode, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           int targetWidth,
                                           int targetHeight,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, int, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           Scalr.Method scalingMethod,
                                           int targetWidth,
                                           int targetHeight,
                                           BufferedImageOp... ops)
See Also:
Scalr.resize(BufferedImage, Method, int, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           Scalr.Mode resizeMode,
                                           int targetWidth,
                                           int targetHeight,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, Mode, int, int, BufferedImageOp...)

resize

public static Future<BufferedImage> resize(BufferedImage src,
                                           Scalr.Method scalingMethod,
                                           Scalr.Mode resizeMode,
                                           int targetWidth,
                                           int targetHeight,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.resize(BufferedImage, Method, Mode, int, int, BufferedImageOp...)

rotate

public static Future<BufferedImage> rotate(BufferedImage src,
                                           Scalr.Rotation rotation,
                                           BufferedImageOp... ops)
                                    throws IllegalArgumentException,
                                           ImagingOpException
Throws:
IllegalArgumentException
ImagingOpException
See Also:
Scalr.rotate(BufferedImage, Rotation, BufferedImageOp...)

Copyright 2011 The Buzz Media, LLC