com.thebuzzmedia.imgscalr
Class AsyncScalr

java.lang.Object
  extended by com.thebuzzmedia.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 offering more control over the scaling and ordering of a large number of scale operations.

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.

Up until now it was left to the caller to implement their own serialization or limiting logic to handle these use-cases, but 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 mirrored calls 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 will perform the scale operation. Future.get() or Future.get(long, TimeUnit) can be used to block on the returned Future, waiting for the scale operation to complete and return the resultant BufferedImage.

This design provides the following features:

This class also allows callers to provide their own (custom) ExecutorService for processing scale operations for maximum flexibility; otherwise this class utilizes a fixed ThreadPoolExecutor via Executors.newFixedThreadPool(int) that will create the given number of threads and let them sit idle, waiting for work.

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, 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 the class is referenced for the first time or explicitly set with one of the setter methods. 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 reference this class.

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

Field Summary
static int DEFAULT_THREAD_COUNT
          Default thread count used to initialize the internal ExecutorService if a count isn't specified via setServiceThreadCount(int) before this class is used.
 
Constructor Summary
AsyncScalr()
           
 
Method Summary
static ExecutorService getService()
          Used to get access to the internal ExecutorService used by this class to process scale operations.
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.Method scalingMethod, Scalr.Mode resizeMode, Scalr.Rotation rotation, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, Scalr.Mode resizeMode, Scalr.Rotation rotation, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, Scalr.Rotation rotation, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Method scalingMethod, Scalr.Rotation rotation, 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> resize(BufferedImage src, Scalr.Mode resizeMode, Scalr.Rotation rotation, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Mode resizeMode, Scalr.Rotation rotation, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Rotation rotation, int targetSize, BufferedImageOp... ops)
           
static Future<BufferedImage> resize(BufferedImage src, Scalr.Rotation rotation, int targetWidth, int targetHeight, BufferedImageOp... ops)
           
static void setService(ExecutorService service)
          Used to initialize the internal ExecutorService which runs tasks generated by this class with the given service.
static void setServiceThreadCount(int threadCount)
          Used to adjust the fixed number of threads (min/max) used by the internal ThreadPoolExecutor to executor scale operations.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

DEFAULT_THREAD_COUNT

public static final int DEFAULT_THREAD_COUNT
Default thread count used to initialize the internal ExecutorService if a count isn't specified via setServiceThreadCount(int) before this class is used.

Default value is 2.

See Also:
Constant Field Values
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 unless you have passed in a custom ExecutorService that specifically creates/uses daemon threads (which will exit immediately).

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.

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

setService

public static void setService(ExecutorService service)
                       throws IllegalArgumentException
Used to initialize the internal ExecutorService which runs tasks generated by this class with the given service.

NOTE: This operation will call ExecutorService.shutdown() on any existing ExecutorService currently set on this class. This means this operation will block until all pending (queued) scale operations are completed.

Parameters:
service - A specific ExecutorService instance that will be used by this class to process scale operations.
Throws:
IllegalArgumentException - if service is null.

setServiceThreadCount

public static void setServiceThreadCount(int threadCount)
                                  throws IllegalArgumentException
Used to adjust the fixed number of threads (min/max) used by the internal ThreadPoolExecutor to executor scale operations.

The following logic is used when applying thread count changes using this method:

  1. If this is the first time the service is being initialized, a new ThreadPoolExecutor is created with the given fixed number of threads.
  2. If a service has already been set and it is of type ThreadPoolExecutor then the methods ThreadPoolExecutor.setCorePoolSize(int) and ThreadPoolExecutor.setMaximumPoolSize(int) are used to adjust the current fixed size of the thread pool without destroying the executor and creating a new one. This avoids unnecessary garbage for the GC and helps keep the task queue intact.
  3. If a service has already been set, but it is not of type ThreadPoolExecutor, then it will be shutdown after all pending tasks have completed and replaced with a new instance of type ThreadPoolExecutor with the given number of fixed threads.
In the case where an existing ThreadPoolExecutor thread count is adjusted, if the given threadCount is smaller than the current number of threads in the pool, the extra threads will only be killed after they have completed their work and become idle. No scaling operations will be interrupted.

Parameters:
threadCount - The fixed number of threads (min/max) that the service will be configured to use to process scale operations.
Throws:
IllegalArgumentException - if threadCount is < 1.

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

resize

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

Copyright 2011 The Buzz Media, LLC