7. Chunking a large task Large Task Chunk Chunk Chunk Chunk Thread Thread Thread Thread Wait for all Main thread Continue
8. Thread Pool e.g. Tomcat web server Input Stream Thread Thread Thread Thread Queue Queue Output Stream
9. Executor / Callable Task Pool implementation public class ParallelWorkPool { private int taskId = 0; private int poolSize = 10; private final ExecutorService exec; private ExecutorCompletionService <OutputData> ecs; public ParallelWorkPool(int poolSize) { if ( poolSize > 0 ) this.poolSize = poolSize; exec = Executors.newFixedThreadPool(this.poolSize); ecs = new ExecutorCompletionService <OutputData>( exec ); } public Integer submit(MyTask task) { task.setId(++taskId); ecs.submit(task); return taskId; } public Map< Integer, OutputData > waitForAllTasks() { Map< Integer, OutputData > mapResults = new TreeMap< Integer, OutputData >(); for ( int i=0; i < taskId; i++ ) { Future<OutputData> result = ecs.take(); mapResults.put(result.get().getId(), result.get()); } return mapResults; } } // class ParallelWorkPool
10. Callable Task public class MyTask implements Callable<OutputData> { private InputData inputData; private OutputData outputData; public MyTask( InputData inputData ) { this.inputData = inputData; outputData = new OutputData(); } public void setId(int id) { outputData.setId( id ); } public int getId() { return outputData.getId(); } // this actually performs the task - inside the thread public OutputData call() { outputData.setValues( doSomething( inputData ) ); return outputData; } } // class MyTask
11. Using the Task Pool ParallelWorkPool pool = new ParallelWorkPool(2); pool.submit( new MyTask( new InputData(3.14, 2.718) ); pool.submit( new MyTask( new InputData(0, 1) ); pool.submit( new MyTask( new InputData("http://www.mapquest.com") ); pool.submit( new MyTask( new InputData("Mechanicsburg, PA") ); // wait for all tasks to complete Map<Integer, OutputData > mapResults = pool.waitForAllTasks(); for (Map.Entry<Integer,OutputData> result : mapResults.entrySet() ) { // use result.getKey() if we care about which task we have doSomething( result.getValue() ); }
12. Threadsafe cache class class MTCache <Resource> { public class CacheEntry { public Resource resource; public Calendar timeToDie; } private ConcurrentHashMap <String, CacheEntry> mapCache; private Sweeper sweeper = null; public MTCache() { mapCache = new ConcurrentHashMap <String, CacheEntry>(); } public void destroy() { sweeper.shutdown(); sweeper = null; } public void clearCache() { mapCache.clear(); } public Resource getCachedEntry(String path) { CacheEntry entry = mapCache.get(path); if ( entry != null ) return entry.resource; else return addCacheEntry( path ); } // … addCacheEntry() } // class MTCache<> private synchronized Resource addCacheEntry(String path) { Resource resource = null; CacheEntry cacheEntry = mapCache.get( path ); if ( cacheEntry != null ) resource = cacheEntry.resource; else { // OBTAIN THE RESOURCE resource = getResource(path); if ( resource != null ) { // Get current date / time Calendar timeToDie = Calendar.getInstance(); timeToDie.add(Calendar.MINUTE, imageTTLMinutes); cacheEntry = new CacheEntry(resource, timeToDie); mapCache.put( path, cacheEntry ); } } return resource; } This is an an example of the Double Checked Locking pattern
13. Threadsafe cache class - Reloaded class MTCache <Resource> { public class CacheEntry { public Resource resource; public Calendar timeToDie; } private ConcurrentHashMap <String, CacheEntry> mapCache; private Sweeper sweeper = null; public MTCache() { mapCache = new ConcurrentHashMap <String, CacheEntry>(); } public void destroy() { sweeper.shutdown(); sweeper = null; } public void clearCache() { mapCache.clear(); } public Resource getCachedEntry(String path) { CacheEntry entry = mapCache.get(path); if ( entry != null ) return entry.resource; else return addCacheEntry( path ); } // … addCacheEntry() } // class MTCache<> private Resource addCacheEntry(String path) { // OBTAIN THE RESOURCE Resource resource = getResource(path); if ( resource != null ) { // Get current date / time Calendar timeToDie = Calendar.getInstance(); timeToDie.add(Calendar.MINUTE, imageTTLMinutes); cacheEntry = new CacheEntry(resource, timeToDie); resource = mapCache. putIfAbsent ( path, cacheEntry ) .resource; } return resource; } I would favor the synchronized implementation if the resource was costly to obtain – no risk of duplicated resource creation. The “reloaded” version is better for small objects.
14. Background Thread for cache cleanup class Sweeper { public Sweeper() { thread = new SweeperThread(); } public void start() { thread.start(); } public void shutdown() { thread.killMe = true ; } SweeperThread thread; class SweeperThread extends Thread { public volatile boolean die = false ; public void run() { long i=0; while ( !die ) { TimeUnit. SECONDS .sleep(1); // check kill flag once every second if ( (++i % (60*ttlMinutes)) == 0 ) { // sweep and remove expired resources Calendar now = Calendar. getInstance (); for (Iterator<CacheEntry> it=mapCache.values().iterator(); it.hasNext();) { CacheEntry entry = it.next(); if ( now.after(entry.timeToDie) ) it.remove(); } } } // while no die flag } // run() } // class SweeperThread } // class Sweeper