org.imgscalr
Class AsyncScalr
java.lang.Object
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:
- Non-blocking, asynchronous scale operations that can continue execution
while waiting on the scaled result.
- Serialize all scale requests down into a maximum number of
simultaneous scale operations with no additional/complex logic. The
number of simultaneous scale operations is caller-configurable (see
THREAD_COUNT
) so as best to optimize the host system (e.g. 1 scale
thread per core).
- No need to worry about overloading the host system with too many scale
operations, they will simply queue up in this class and execute in-order.
- Synchronous/blocking behavior can still be achieved (if desired) by
calling
get()
or get(long, TimeUnit)
immediately on
the returned Future
from any of the methods below.
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:
- JVM Heap Memory (Assume physical machine memory is always sufficiently
large)
- # 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 Thread
s 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
Thread
s 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)
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)
|
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
.
AsyncScalr
public AsyncScalr()
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...)