![JAR search and dependency download from the Maven repository](/logo.png)
org.imgscalr.AsyncScalr Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of imgscalr-lib Show documentation
Show all versions of imgscalr-lib Show documentation
imgscalr is an simple and efficient best-practices image-scaling and manipulation library implemented in pure Java.
package org.imgscalr;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImagingOpException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.imgscalr.Scalr.Method;
import org.imgscalr.Scalr.Mode;
import org.imgscalr.Scalr.Rotation;
/**
* Class used to provide the asynchronous versions of all the methods defined in
* {@link 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 {@link Scalr}
* class in new {@link Callable} instances that are submitted to an internal
* {@link ExecutorService} for execution at a later date. A {@link 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 {@link ExecutorService}'s queue. {@link Future#get()} or
* {@link Future#get(long, TimeUnit)} can be used to block on the
* Future
, waiting for the scale operation to complete and return
* the resultant {@link 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
* {@link #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 {@link 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
* {@link 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 {@link 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 {@link 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 {@link Thread}s created by the internal
* {@link 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 {@link AsyncScalr} class and are trying to shut down a
* client application, you will need to call {@link #getService()} then
* {@link ExecutorService#shutdown()} or {@link ExecutorService#shutdownNow()}
* to have the threads terminated; you may also want to look at the
* {@link 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 {@link #checkService()} method which will
* replace the terminated underlying {@link ExecutorService} with a new one via
* the {@link #createService()} method.
* Custom Implementations
* If a subclass wants to customize the {@link ExecutorService} or
* {@link ThreadFactory} used under the covers, this can be done by overriding
* the {@link #createService()} method which is invoked by this class anytime a
* new {@link ExecutorService} is needed.
*
* By default the {@link #createService()} method delegates to the
* {@link #createService(ThreadFactory)} method with a new instance of
* {@link DefaultThreadFactory}. Either of these methods can be overridden and
* customized easily if desired.
*
* TIP: A common customization to this class is to make the
* {@link Thread}s generated by the underlying factory more server-friendly, in
* which case the caller would want to use an instance of the
* {@link ServerThreadFactory} when creating the new {@link ExecutorService}.
*
* This can be done in one line by overriding {@link #createService()} and
* returning the result of:
* return createService(new ServerThreadFactory());
*
* By default this class uses an {@link ThreadPoolExecutor} internally to handle
* execution of queued image operations. If a different type of
* {@link ExecutorService} is desired, again, simply overriding the
* {@link #createService()} method of choice is the right way to do that.
*
* @author Riyad Kalla ([email protected])
* @since 3.2
*/
@SuppressWarnings("javadoc")
public class AsyncScalr {
/**
* System property name used to set the number of threads the default
* underlying {@link ExecutorService} will use to process async image
* operations.
*
* Value is "imgscalr.async.threadCount
".
*/
public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount";
/**
* Number of threads the internal {@link ExecutorService} will use to
* simultaneously execute scale requests.
*
* This value can be changed by setting the
* imgscalr.async.threadCount
system property (see
* {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value > 0.
*
* Default value is 2
.
*/
public static final int THREAD_COUNT = Integer.getInteger(
THREAD_COUNT_PROPERTY_NAME, 2);
/**
* Initializer used to verify the THREAD_COUNT system property.
*/
static {
if (THREAD_COUNT < 1)
throw new RuntimeException("System property '"
+ THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to "
+ THREAD_COUNT + ", but THREAD_COUNT must be > 0.");
}
protected static ExecutorService service;
/**
* Used to get access to the internal {@link 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 {@link ExecutorService#shutdown()} to wait for all scaling
* operations to complete first or call
* {@link ExecutorService#shutdownNow()} to kill any in-process operations
* and purge all pending operations before exiting.
*
* Additionally you can use
* {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a
* shutdown command to try and wait until the service has finished all
* tasks.
*
* @return the current {@link ExecutorService} used by this class to process
* scale operations.
*/
public static ExecutorService getService() {
return service;
}
/**
* @see Scalr#apply(BufferedImage, BufferedImageOp...)
*/
public static Future apply(final BufferedImage src,
final BufferedImageOp... ops) throws IllegalArgumentException,
ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.apply(src, ops);
}
});
}
/**
* @see Scalr#crop(BufferedImage, int, int, BufferedImageOp...)
*/
public static Future crop(final BufferedImage src,
final int width, final int height, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.crop(src, width, height, ops);
}
});
}
/**
* @see Scalr#crop(BufferedImage, int, int, int, int, BufferedImageOp...)
*/
public static Future crop(final BufferedImage src,
final int x, final int y, final int width, final int height,
final BufferedImageOp... ops) throws IllegalArgumentException,
ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.crop(src, x, y, width, height, ops);
}
});
}
/**
* @see Scalr#pad(BufferedImage, int, BufferedImageOp...)
*/
public static Future pad(final BufferedImage src,
final int padding, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.pad(src, padding, ops);
}
});
}
/**
* @see Scalr#pad(BufferedImage, int, Color, BufferedImageOp...)
*/
public static Future pad(final BufferedImage src,
final int padding, final Color color, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.pad(src, padding, color, ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final int targetSize, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, targetSize, ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, Method, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final Method scalingMethod, final int targetSize,
final BufferedImageOp... ops) throws IllegalArgumentException,
ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, scalingMethod, targetSize, ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, Mode, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final Mode resizeMode, final int targetSize,
final BufferedImageOp... ops) throws IllegalArgumentException,
ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, resizeMode, targetSize, ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, Method, Mode, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final Method scalingMethod, final Mode resizeMode,
final int targetSize, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, scalingMethod, resizeMode, targetSize,
ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, int, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final int targetWidth, final int targetHeight,
final BufferedImageOp... ops) throws IllegalArgumentException,
ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, targetWidth, targetHeight, ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, Method, int, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final Method scalingMethod, final int targetWidth,
final int targetHeight, final BufferedImageOp... ops) {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, scalingMethod, targetWidth,
targetHeight, ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, Mode, int, int, BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final Mode resizeMode, final int targetWidth,
final int targetHeight, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, resizeMode, targetWidth, targetHeight,
ops);
}
});
}
/**
* @see Scalr#resize(BufferedImage, Method, Mode, int, int,
* BufferedImageOp...)
*/
public static Future resize(final BufferedImage src,
final Method scalingMethod, final Mode resizeMode,
final int targetWidth, final int targetHeight,
final BufferedImageOp... ops) throws IllegalArgumentException,
ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.resize(src, scalingMethod, resizeMode,
targetWidth, targetHeight, ops);
}
});
}
/**
* @see Scalr#rotate(BufferedImage, Rotation, BufferedImageOp...)
*/
public static Future rotate(final BufferedImage src,
final Rotation rotation, final BufferedImageOp... ops)
throws IllegalArgumentException, ImagingOpException {
checkService();
return service.submit(new Callable() {
public BufferedImage call() throws Exception {
return Scalr.rotate(src, rotation, ops);
}
});
}
protected static ExecutorService createService() {
return createService(new DefaultThreadFactory());
}
protected static ExecutorService createService(ThreadFactory factory)
throws IllegalArgumentException {
if (factory == null)
throw new IllegalArgumentException("factory cannot be null");
return Executors.newFixedThreadPool(THREAD_COUNT, factory);
}
/**
* Used to verify that the underlying service
points at an
* active {@link ExecutorService} instance that can be used by this class.
*
* If service
is null
, has been shutdown or
* terminated then this method will replace it with a new
* {@link ExecutorService} by calling the {@link #createService()} method
* and assigning the returned value to service
.
*
* Any subclass that wants to customize the {@link ExecutorService} or
* {@link ThreadFactory} used internally by this class should override the
* {@link #createService()}.
*/
protected static void checkService() {
if (service == null || service.isShutdown() || service.isTerminated()) {
/*
* If service was shutdown or terminated, assigning a new value will
* free the reference to the instance, allowing it to be GC'ed when
* it is done shutting down (assuming it hadn't already).
*/
service = createService();
}
}
/**
* Default {@link ThreadFactory} used by the internal
* {@link ExecutorService} to creates execution {@link Thread}s for image
* scaling.
*
* More or less a copy of the hidden class backing the
* {@link Executors#defaultThreadFactory()} method, but exposed here to make
* it easier for implementors to extend and customize.
*
* @author Doug Lea
* @author Riyad Kalla ([email protected])
* @since 4.0
*/
protected static class DefaultThreadFactory implements ThreadFactory {
protected static final AtomicInteger poolNumber = new AtomicInteger(1);
protected final ThreadGroup group;
protected final AtomicInteger threadNumber = new AtomicInteger(1);
protected final String namePrefix;
DefaultThreadFactory() {
SecurityManager manager = System.getSecurityManager();
/*
* Determine the group that threads created by this factory will be
* in.
*/
group = (manager == null ? Thread.currentThread().getThreadGroup()
: manager.getThreadGroup());
/*
* Define a common name prefix for the threads created by this
* factory.
*/
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
/**
* Used to create a {@link Thread} capable of executing the given
* {@link Runnable}.
*
* Thread created by this factory are utilized by the parent
* {@link ExecutorService} when processing queued up scale operations.
*/
public Thread newThread(Runnable r) {
/*
* Create a new thread in our specified group with a meaningful
* thread name so it is easy to identify.
*/
Thread thread = new Thread(group, r, namePrefix
+ threadNumber.getAndIncrement(), 0);
// Configure thread according to class or subclass
thread.setDaemon(false);
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
}
/**
* An extension of the {@link DefaultThreadFactory} class that makes two
* changes to the execution {@link Thread}s it generations:
*
* - Threads are set to be daemon threads instead of user threads.
* - Threads execute with a priority of {@link Thread#MIN_PRIORITY} to
* make them more compatible with server environment deployments.
*
* This class is provided as a convenience for subclasses to use if they
* want this (common) customization to the {@link Thread}s used internally
* by {@link AsyncScalr} to process images, but don't want to have to write
* the implementation.
*
* @author Riyad Kalla ([email protected])
* @since 4.0
*/
protected static class ServerThreadFactory extends DefaultThreadFactory {
/**
* Overridden to set daemon
property to true
* and decrease the priority of the new thread to
* {@link Thread#MIN_PRIORITY} before returning it.
*/
@Override
public Thread newThread(Runnable r) {
Thread thread = super.newThread(r);
thread.setDaemon(true);
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy