001 package org.imgscalr; 002 003 import java.awt.Color; 004 import java.awt.image.BufferedImage; 005 import java.awt.image.BufferedImageOp; 006 import java.awt.image.ImagingOpException; 007 import java.util.concurrent.Callable; 008 import java.util.concurrent.ExecutorService; 009 import java.util.concurrent.Executors; 010 import java.util.concurrent.Future; 011 import java.util.concurrent.ThreadFactory; 012 import java.util.concurrent.ThreadPoolExecutor; 013 import java.util.concurrent.TimeUnit; 014 import java.util.concurrent.atomic.AtomicInteger; 015 016 import org.imgscalr.Scalr.Method; 017 import org.imgscalr.Scalr.Mode; 018 import org.imgscalr.Scalr.Rotation; 019 020 /** 021 * Class used to provide the asynchronous versions of all the methods defined in 022 * {@link Scalr} for the purpose of efficiently handling large amounts of image 023 * operations via a select number of processing threads asynchronously. 024 * <p/> 025 * Given that image-scaling operations, especially when working with large 026 * images, can be very hardware-intensive (both CPU and memory), in large-scale 027 * deployments (e.g. a busy web application) it becomes increasingly important 028 * that the scale operations performed by imgscalr be manageable so as not to 029 * fire off too many simultaneous operations that the JVM's heap explodes and 030 * runs out of memory or pegs the CPU on the host machine, staving all other 031 * running processes. 032 * <p/> 033 * Up until now it was left to the caller to implement their own serialization 034 * or limiting logic to handle these use-cases. Given imgscalr's popularity in 035 * web applications it was determined that this requirement be common enough 036 * that it should be integrated directly into the imgscalr library for everyone 037 * to benefit from. 038 * <p/> 039 * Every method in this class wraps the matching methods in the {@link Scalr} 040 * class in new {@link Callable} instances that are submitted to an internal 041 * {@link ExecutorService} for execution at a later date. A {@link Future} is 042 * returned to the caller representing the task that is either currently 043 * performing the scale operation or will at a future date depending on where it 044 * is in the {@link ExecutorService}'s queue. {@link Future#get()} or 045 * {@link Future#get(long, TimeUnit)} can be used to block on the 046 * <code>Future</code>, waiting for the scale operation to complete and return 047 * the resultant {@link BufferedImage} to the caller. 048 * <p/> 049 * This design provides the following features: 050 * <ul> 051 * <li>Non-blocking, asynchronous scale operations that can continue execution 052 * while waiting on the scaled result.</li> 053 * <li>Serialize all scale requests down into a maximum number of 054 * <em>simultaneous</em> scale operations with no additional/complex logic. The 055 * number of simultaneous scale operations is caller-configurable (see 056 * {@link #THREAD_COUNT}) so as best to optimize the host system (e.g. 1 scale 057 * thread per core).</li> 058 * <li>No need to worry about overloading the host system with too many scale 059 * operations, they will simply queue up in this class and execute in-order.</li> 060 * <li>Synchronous/blocking behavior can still be achieved (if desired) by 061 * calling <code>get()</code> or <code>get(long, TimeUnit)</code> immediately on 062 * the returned {@link Future} from any of the methods below.</li> 063 * </ul> 064 * <h3>Performance</h3> 065 * When tuning this class for optimal performance, benchmarking your particular 066 * hardware is the best approach. For some rough guidelines though, there are 067 * two resources you want to watch closely: 068 * <ol> 069 * <li>JVM Heap Memory (Assume physical machine memory is always sufficiently 070 * large)</li> 071 * <li># of CPU Cores</li> 072 * </ol> 073 * You never want to allocate more scaling threads than you have CPU cores and 074 * on a sufficiently busy host where some of the cores may be busy running a 075 * database or a web server, you will want to allocate even less scaling 076 * threads. 077 * <p/> 078 * So as a maximum you would never want more scaling threads than CPU cores in 079 * any situation and less so on a busy server. 080 * <p/> 081 * If you allocate more threads than you have available CPU cores, your scaling 082 * operations will slow down as the CPU will spend a considerable amount of time 083 * context-switching between threads on the same core trying to finish all the 084 * tasks in parallel. You might still be tempted to do this because of the I/O 085 * delay some threads will encounter reading images off disk, but when you do 086 * your own benchmarking you'll likely find (as I did) that the actual disk I/O 087 * necessary to pull the image data off disk is a much smaller portion of the 088 * execution time than the actual scaling operations. 089 * <p/> 090 * If you are executing on a storage medium that is unexpectedly slow and I/O is 091 * a considerable portion of the scaling operation (e.g. S3 or EBS volumes), 092 * feel free to try using more threads than CPU cores to see if that helps; but 093 * in most normal cases, it will only slow down all other parallel scaling 094 * operations. 095 * <p/> 096 * As for memory, every time an image is scaled it is decoded into a 097 * {@link BufferedImage} and stored in the JVM Heap space (decoded image 098 * instances are always larger than the source images on-disk). For larger 099 * images, that can use up quite a bit of memory. You will need to benchmark 100 * your particular use-cases on your hardware to get an idea of where the sweet 101 * spot is for this; if you are operating within tight memory bounds, you may 102 * want to limit simultaneous scaling operations to 1 or 2 regardless of the 103 * number of cores just to avoid having too many {@link BufferedImage} instances 104 * in JVM Heap space at the same time. 105 * <p/> 106 * These are rough metrics and behaviors to give you an idea of how best to tune 107 * this class for your deployment, but nothing can replacement writing a small 108 * Java class that scales a handful of images in a number of different ways and 109 * testing that directly on your deployment hardware. 110 * <h3>Resource Overhead</h3> 111 * The {@link ExecutorService} utilized by this class won't be initialized until 112 * one of the operation methods are called, at which point the 113 * <code>service</code> will be instantiated for the first time and operation 114 * queued up. 115 * <p/> 116 * More specifically, if you have no need for asynchronous image processing 117 * offered by this class, you don't need to worry about wasted resources or 118 * hanging/idle threads as they will never be created if you never use this 119 * class. 120 * <h3>Cleaning up Service Threads</h3> 121 * By default the {@link Thread}s created by the internal 122 * {@link ThreadPoolExecutor} do not run in <code>daemon</code> mode; which 123 * means they will block the host VM from exiting until they are explicitly shut 124 * down in a client application; in a server application the container will shut 125 * down the pool forcibly. 126 * <p/> 127 * If you have used the {@link AsyncScalr} class and are trying to shut down a 128 * client application, you will need to call {@link #getService()} then 129 * {@link ExecutorService#shutdown()} or {@link ExecutorService#shutdownNow()} 130 * to have the threads terminated; you may also want to look at the 131 * {@link ExecutorService#awaitTermination(long, TimeUnit)} method if you'd like 132 * to more closely monitor the shutting down process (and finalization of 133 * pending scale operations). 134 * <h3>Reusing Shutdown AsyncScalr</h3> 135 * If you have previously called <code>shutdown</code> on the underlying service 136 * utilized by this class, subsequent calls to any of the operations this class 137 * provides will invoke the internal {@link #checkService()} method which will 138 * replace the terminated underlying {@link ExecutorService} with a new one via 139 * the {@link #createService()} method. 140 * <h3>Custom Implementations</h3> 141 * If a subclass wants to customize the {@link ExecutorService} or 142 * {@link ThreadFactory} used under the covers, this can be done by overriding 143 * the {@link #createService()} method which is invoked by this class anytime a 144 * new {@link ExecutorService} is needed. 145 * <p/> 146 * By default the {@link #createService()} method delegates to the 147 * {@link #createService(ThreadFactory)} method with a new instance of 148 * {@link DefaultThreadFactory}. Either of these methods can be overridden and 149 * customized easily if desired. 150 * <p/> 151 * <strong>TIP</strong>: A common customization to this class is to make the 152 * {@link Thread}s generated by the underlying factory more server-friendly, in 153 * which case the caller would want to use an instance of the 154 * {@link ServerThreadFactory} when creating the new {@link ExecutorService}. 155 * <p/> 156 * This can be done in one line by overriding {@link #createService()} and 157 * returning the result of: 158 * <code>return createService(new ServerThreadFactory());</code> 159 * <p/> 160 * By default this class uses an {@link ThreadPoolExecutor} internally to handle 161 * execution of queued image operations. If a different type of 162 * {@link ExecutorService} is desired, again, simply overriding the 163 * {@link #createService()} method of choice is the right way to do that. 164 * 165 * @author Riyad Kalla (software@thebuzzmedia.com) 166 * @since 3.2 167 */ 168 @SuppressWarnings("javadoc") 169 public class AsyncScalr { 170 /** 171 * System property name used to set the number of threads the default 172 * underlying {@link ExecutorService} will use to process async image 173 * operations. 174 * <p/> 175 * Value is "<code>imgscalr.async.threadCount</code>". 176 */ 177 public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount"; 178 179 /** 180 * Number of threads the internal {@link ExecutorService} will use to 181 * simultaneously execute scale requests. 182 * <p/> 183 * This value can be changed by setting the 184 * <code>imgscalr.async.threadCount</code> system property (see 185 * {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value > 0. 186 * <p/> 187 * Default value is <code>2</code>. 188 */ 189 public static final int THREAD_COUNT = Integer.getInteger( 190 THREAD_COUNT_PROPERTY_NAME, 2); 191 192 /** 193 * Initializer used to verify the THREAD_COUNT system property. 194 */ 195 static { 196 if (THREAD_COUNT < 1) 197 throw new RuntimeException("System property '" 198 + THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to " 199 + THREAD_COUNT + ", but THREAD_COUNT must be > 0."); 200 } 201 202 protected static ExecutorService service; 203 204 /** 205 * Used to get access to the internal {@link ExecutorService} used by this 206 * class to process scale operations. 207 * <p/> 208 * <strong>NOTE</strong>: You will need to explicitly shutdown any service 209 * currently set on this class before the host JVM exits. 210 * <p/> 211 * You can call {@link ExecutorService#shutdown()} to wait for all scaling 212 * operations to complete first or call 213 * {@link ExecutorService#shutdownNow()} to kill any in-process operations 214 * and purge all pending operations before exiting. 215 * <p/> 216 * Additionally you can use 217 * {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a 218 * shutdown command to try and wait until the service has finished all 219 * tasks. 220 * 221 * @return the current {@link ExecutorService} used by this class to process 222 * scale operations. 223 */ 224 public static ExecutorService getService() { 225 return service; 226 } 227 228 /** 229 * @see Scalr#apply(BufferedImage, BufferedImageOp...) 230 */ 231 public static Future<BufferedImage> apply(final BufferedImage src, 232 final BufferedImageOp... ops) throws IllegalArgumentException, 233 ImagingOpException { 234 checkService(); 235 236 return service.submit(new Callable<BufferedImage>() { 237 public BufferedImage call() throws Exception { 238 return Scalr.apply(src, ops); 239 } 240 }); 241 } 242 243 /** 244 * @see Scalr#crop(BufferedImage, int, int, BufferedImageOp...) 245 */ 246 public static Future<BufferedImage> crop(final BufferedImage src, 247 final int width, final int height, final BufferedImageOp... ops) 248 throws IllegalArgumentException, ImagingOpException { 249 checkService(); 250 251 return service.submit(new Callable<BufferedImage>() { 252 public BufferedImage call() throws Exception { 253 return Scalr.crop(src, width, height, ops); 254 } 255 }); 256 } 257 258 /** 259 * @see Scalr#crop(BufferedImage, int, int, int, int, BufferedImageOp...) 260 */ 261 public static Future<BufferedImage> crop(final BufferedImage src, 262 final int x, final int y, final int width, final int height, 263 final BufferedImageOp... ops) throws IllegalArgumentException, 264 ImagingOpException { 265 checkService(); 266 267 return service.submit(new Callable<BufferedImage>() { 268 public BufferedImage call() throws Exception { 269 return Scalr.crop(src, x, y, width, height, ops); 270 } 271 }); 272 } 273 274 /** 275 * @see Scalr#pad(BufferedImage, int, BufferedImageOp...) 276 */ 277 public static Future<BufferedImage> pad(final BufferedImage src, 278 final int padding, final BufferedImageOp... ops) 279 throws IllegalArgumentException, ImagingOpException { 280 checkService(); 281 282 return service.submit(new Callable<BufferedImage>() { 283 public BufferedImage call() throws Exception { 284 return Scalr.pad(src, padding, ops); 285 } 286 }); 287 } 288 289 /** 290 * @see Scalr#pad(BufferedImage, int, Color, BufferedImageOp...) 291 */ 292 public static Future<BufferedImage> pad(final BufferedImage src, 293 final int padding, final Color color, final BufferedImageOp... ops) 294 throws IllegalArgumentException, ImagingOpException { 295 checkService(); 296 297 return service.submit(new Callable<BufferedImage>() { 298 public BufferedImage call() throws Exception { 299 return Scalr.pad(src, padding, color, ops); 300 } 301 }); 302 } 303 304 /** 305 * @see Scalr#resize(BufferedImage, int, BufferedImageOp...) 306 */ 307 public static Future<BufferedImage> resize(final BufferedImage src, 308 final int targetSize, final BufferedImageOp... ops) 309 throws IllegalArgumentException, ImagingOpException { 310 checkService(); 311 312 return service.submit(new Callable<BufferedImage>() { 313 public BufferedImage call() throws Exception { 314 return Scalr.resize(src, targetSize, ops); 315 } 316 }); 317 } 318 319 /** 320 * @see Scalr#resize(BufferedImage, Method, int, BufferedImageOp...) 321 */ 322 public static Future<BufferedImage> resize(final BufferedImage src, 323 final Method scalingMethod, final int targetSize, 324 final BufferedImageOp... ops) throws IllegalArgumentException, 325 ImagingOpException { 326 checkService(); 327 328 return service.submit(new Callable<BufferedImage>() { 329 public BufferedImage call() throws Exception { 330 return Scalr.resize(src, scalingMethod, targetSize, ops); 331 } 332 }); 333 } 334 335 /** 336 * @see Scalr#resize(BufferedImage, Mode, int, BufferedImageOp...) 337 */ 338 public static Future<BufferedImage> resize(final BufferedImage src, 339 final Mode resizeMode, final int targetSize, 340 final BufferedImageOp... ops) throws IllegalArgumentException, 341 ImagingOpException { 342 checkService(); 343 344 return service.submit(new Callable<BufferedImage>() { 345 public BufferedImage call() throws Exception { 346 return Scalr.resize(src, resizeMode, targetSize, ops); 347 } 348 }); 349 } 350 351 /** 352 * @see Scalr#resize(BufferedImage, Method, Mode, int, BufferedImageOp...) 353 */ 354 public static Future<BufferedImage> resize(final BufferedImage src, 355 final Method scalingMethod, final Mode resizeMode, 356 final int targetSize, final BufferedImageOp... ops) 357 throws IllegalArgumentException, ImagingOpException { 358 checkService(); 359 360 return service.submit(new Callable<BufferedImage>() { 361 public BufferedImage call() throws Exception { 362 return Scalr.resize(src, scalingMethod, resizeMode, targetSize, 363 ops); 364 } 365 }); 366 } 367 368 /** 369 * @see Scalr#resize(BufferedImage, int, int, BufferedImageOp...) 370 */ 371 public static Future<BufferedImage> resize(final BufferedImage src, 372 final int targetWidth, final int targetHeight, 373 final BufferedImageOp... ops) throws IllegalArgumentException, 374 ImagingOpException { 375 checkService(); 376 377 return service.submit(new Callable<BufferedImage>() { 378 public BufferedImage call() throws Exception { 379 return Scalr.resize(src, targetWidth, targetHeight, ops); 380 } 381 }); 382 } 383 384 /** 385 * @see Scalr#resize(BufferedImage, Method, int, int, BufferedImageOp...) 386 */ 387 public static Future<BufferedImage> resize(final BufferedImage src, 388 final Method scalingMethod, final int targetWidth, 389 final int targetHeight, final BufferedImageOp... ops) { 390 checkService(); 391 392 return service.submit(new Callable<BufferedImage>() { 393 public BufferedImage call() throws Exception { 394 return Scalr.resize(src, scalingMethod, targetWidth, 395 targetHeight, ops); 396 } 397 }); 398 } 399 400 /** 401 * @see Scalr#resize(BufferedImage, Mode, int, int, BufferedImageOp...) 402 */ 403 public static Future<BufferedImage> resize(final BufferedImage src, 404 final Mode resizeMode, final int targetWidth, 405 final int targetHeight, final BufferedImageOp... ops) 406 throws IllegalArgumentException, ImagingOpException { 407 checkService(); 408 409 return service.submit(new Callable<BufferedImage>() { 410 public BufferedImage call() throws Exception { 411 return Scalr.resize(src, resizeMode, targetWidth, targetHeight, 412 ops); 413 } 414 }); 415 } 416 417 /** 418 * @see Scalr#resize(BufferedImage, Method, Mode, int, int, 419 * BufferedImageOp...) 420 */ 421 public static Future<BufferedImage> resize(final BufferedImage src, 422 final Method scalingMethod, final Mode resizeMode, 423 final int targetWidth, final int targetHeight, 424 final BufferedImageOp... ops) throws IllegalArgumentException, 425 ImagingOpException { 426 checkService(); 427 428 return service.submit(new Callable<BufferedImage>() { 429 public BufferedImage call() throws Exception { 430 return Scalr.resize(src, scalingMethod, resizeMode, 431 targetWidth, targetHeight, ops); 432 } 433 }); 434 } 435 436 /** 437 * @see Scalr#rotate(BufferedImage, Rotation, BufferedImageOp...) 438 */ 439 public static Future<BufferedImage> rotate(final BufferedImage src, 440 final Rotation rotation, final BufferedImageOp... ops) 441 throws IllegalArgumentException, ImagingOpException { 442 checkService(); 443 444 return service.submit(new Callable<BufferedImage>() { 445 public BufferedImage call() throws Exception { 446 return Scalr.rotate(src, rotation, ops); 447 } 448 }); 449 } 450 451 protected static ExecutorService createService() { 452 return createService(new DefaultThreadFactory()); 453 } 454 455 protected static ExecutorService createService(ThreadFactory factory) 456 throws IllegalArgumentException { 457 if (factory == null) 458 throw new IllegalArgumentException("factory cannot be null"); 459 460 return Executors.newFixedThreadPool(THREAD_COUNT, factory); 461 } 462 463 /** 464 * Used to verify that the underlying <code>service</code> points at an 465 * active {@link ExecutorService} instance that can be used by this class. 466 * <p/> 467 * If <code>service</code> is <code>null</code>, has been shutdown or 468 * terminated then this method will replace it with a new 469 * {@link ExecutorService} by calling the {@link #createService()} method 470 * and assigning the returned value to <code>service</code>. 471 * <p/> 472 * Any subclass that wants to customize the {@link ExecutorService} or 473 * {@link ThreadFactory} used internally by this class should override the 474 * {@link #createService()}. 475 */ 476 protected static void checkService() { 477 if (service == null || service.isShutdown() || service.isTerminated()) { 478 /* 479 * If service was shutdown or terminated, assigning a new value will 480 * free the reference to the instance, allowing it to be GC'ed when 481 * it is done shutting down (assuming it hadn't already). 482 */ 483 service = createService(); 484 } 485 } 486 487 /** 488 * Default {@link ThreadFactory} used by the internal 489 * {@link ExecutorService} to creates execution {@link Thread}s for image 490 * scaling. 491 * <p/> 492 * More or less a copy of the hidden class backing the 493 * {@link Executors#defaultThreadFactory()} method, but exposed here to make 494 * it easier for implementors to extend and customize. 495 * 496 * @author Doug Lea 497 * @author Riyad Kalla (software@thebuzzmedia.com) 498 * @since 4.0 499 */ 500 protected static class DefaultThreadFactory implements ThreadFactory { 501 protected static final AtomicInteger poolNumber = new AtomicInteger(1); 502 503 protected final ThreadGroup group; 504 protected final AtomicInteger threadNumber = new AtomicInteger(1); 505 protected final String namePrefix; 506 507 DefaultThreadFactory() { 508 SecurityManager manager = System.getSecurityManager(); 509 510 /* 511 * Determine the group that threads created by this factory will be 512 * in. 513 */ 514 group = (manager == null ? Thread.currentThread().getThreadGroup() 515 : manager.getThreadGroup()); 516 517 /* 518 * Define a common name prefix for the threads created by this 519 * factory. 520 */ 521 namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; 522 } 523 524 /** 525 * Used to create a {@link Thread} capable of executing the given 526 * {@link Runnable}. 527 * <p/> 528 * Thread created by this factory are utilized by the parent 529 * {@link ExecutorService} when processing queued up scale operations. 530 */ 531 public Thread newThread(Runnable r) { 532 /* 533 * Create a new thread in our specified group with a meaningful 534 * thread name so it is easy to identify. 535 */ 536 Thread thread = new Thread(group, r, namePrefix 537 + threadNumber.getAndIncrement(), 0); 538 539 // Configure thread according to class or subclass 540 thread.setDaemon(false); 541 thread.setPriority(Thread.NORM_PRIORITY); 542 543 return thread; 544 } 545 } 546 547 /** 548 * An extension of the {@link DefaultThreadFactory} class that makes two 549 * changes to the execution {@link Thread}s it generations: 550 * <ol> 551 * <li>Threads are set to be daemon threads instead of user threads.</li> 552 * <li>Threads execute with a priority of {@link Thread#MIN_PRIORITY} to 553 * make them more compatible with server environment deployments.</li> 554 * </ol> 555 * This class is provided as a convenience for subclasses to use if they 556 * want this (common) customization to the {@link Thread}s used internally 557 * by {@link AsyncScalr} to process images, but don't want to have to write 558 * the implementation. 559 * 560 * @author Riyad Kalla (software@thebuzzmedia.com) 561 * @since 4.0 562 */ 563 protected static class ServerThreadFactory extends DefaultThreadFactory { 564 /** 565 * Overridden to set <code>daemon</code> property to <code>true</code> 566 * and decrease the priority of the new thread to 567 * {@link Thread#MIN_PRIORITY} before returning it. 568 */ 569 @Override 570 public Thread newThread(Runnable r) { 571 Thread thread = super.newThread(r); 572 573 thread.setDaemon(true); 574 thread.setPriority(Thread.MIN_PRIORITY); 575 576 return thread; 577 } 578 } 579 }