// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library.threadpool; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import openadk.library.ADK; import openadk.library.Agent; /** * <p>This class manages a pool of Pull mode threads</p> * * To use this class setup as follows. *<p><ul> * // Thread Pooling <br> * ThreadPoolManager threadPoolManager = new ThreadPoolManager(3);<br> * threadPoolManager.setThreadRunLimit(300); // 5 minutes <br> *<br> * // Now, connect to all zones to register and query<br> * for( Zone zone : this.getZoneFactory().getAllZones() ) {<br> *   // Thread Pooling<br> *   zone.setThreadPoolManager(threadPoolManager);<br> * <br> *   zone.setSubscriber(schoolInfoHandler, StudentDTD.SCHOOLINFO);<br> *   zone.setQueryResults(schoolInfoHandler);<br> *   try {<br> *      zone.connect(ADKFlags.PROV_REGISTER);<br> *   } catch (Exception e) {<br> *      zone.connect(ADKFlags.PROV_NONE);<br> *   }<br> * <br> *   // Request all students<br> *   Query query = new Query(StudentDTD.SCHOOLINFO);<br> *   zone.query(query); <br> * }<br> *<br> * // Thread Pool start<br> * threadPoolManager.execute(); </ul></p> */ public class ThreadPoolManager implements MessageDispatchThreadListener { private MessageDispatcherThreadPoolExecutor threadExecutor = null; // private ArrayList<MessageDispatcherTask> tasks = new ArrayList<MessageDispatcherTask>(); private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<Runnable>()); /** * Constructor for a thread pool with 1 to 5 threads. */ public ThreadPoolManager () { setupPoolManager(1, 5, 10); } /** * Constructor for a thread pool with a fixed pool size. * * @poolSize - Number of threads in pool */ public ThreadPoolManager (int poolSize) { setupPoolManager(poolSize, poolSize, 10); } /** * Constructor for a thread pool with a fixed pool size. * * @param minPoolSize * corePoolSize the number of threads to keep in the pool, even * if they are idle. * @param maxPoolSize * the maximum number of threads to allow in the pool. * @param keepAliveTime * when the number of threads is greater than the core, this is * the maximum time in milliseconds that excess idle threads will * wait for new tasks before terminating. */ public ThreadPoolManager (int minPoolSize, int maxPoolSize, long keepAliveTime) { setupPoolManager(minPoolSize, maxPoolSize, keepAliveTime); } private void setupPoolManager(int minPoolSize, int maxPoolSize, long keepAliveTime) { if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ) { Agent.getLog().info( "Starting thread pool with " + minPoolSize + "-" +maxPoolSize + " threads" ); } threadExecutor = new MessageDispatcherThreadPoolExecutor(minPoolSize, maxPoolSize, keepAliveTime); threadExecutor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); threadExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); threadExecutor.addListener(this); Runtime.getRuntime().addShutdownHook(new Thread("ThreadPoolManager shutdown") { public void run() { // Shutdown int i = 0; shutdown(); while (!threadExecutor.isTerminated()) { try { Thread.sleep(1000); i++; if (i == 60) // give the threadExecutor 60 seconds to shutdown gracefully threadExecutor.shutdownNow(); } catch (InterruptedException e) { Agent.getLog().error( "Problem shuting down thread pool: " + e.getMessage(), e ); } } } }); } /** * Initiates an orderly shutdown in which previously submitted tasks are * executed, but no new tasks will be accepted. Invocation has no additional * effect if already shut down. */ public void shutdown() { if (threadExecutor.isTerminating()) { if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ){ Agent.getLog().info( "Thread pool manager is terminating" ); } } else if (threadExecutor.isShutdown()) { if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ){ Agent.getLog().info( "Thread pool manager is shutdown" ); } } else { if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ){ Agent.getLog().info( "Thread pool manager will shutdown; " + threadExecutor.getLargestPoolSize() + " threads used" ); } threadExecutor.setRejectedExecutionHandler(new ShutdownExecutionHandler ()); } threadExecutor.shutdown(); //threadExecutor.getQueue().clear(); } /** * Call this method to start all tasks that were previously queued through * <code>addTask(Runnable)</code> method. */ public void execute() { // Start threads and place in runnable state threadExecutor.prestartCoreThread(); if ( tasks != null ) { synchronized(tasks) { for (Runnable task : tasks) { execute(task); } tasks.clear(); tasks = null; } } } /** * Execute task with no initial delay. The task can execute repeatedly with * a variable delay if it implements {@link IThreadPoolDelayTask}. * * @param task * the task to execute */ public void execute(Runnable task) { if (task instanceof IThreadPoolDelayTask) { IThreadPoolDelayTask sch = (IThreadPoolDelayTask)task; long delay = sch.getScheduleDelay(); threadExecutor.scheduleWithFixedDelay(task, 0, delay <= 0 ? 60000 : delay, TimeUnit.MILLISECONDS); } else { threadExecutor.execute(task); } } /** * Execute task after waiting the specified delay. The task can execute * repeatedly with a variable delay if it implements * {@link IThreadPoolDelayTask}. * * @param task * the task to execute * @param delay * the time in milliseconds to delay before execution */ public void execute(Runnable task, long delay) { if (task instanceof IThreadPoolDelayTask) { IThreadPoolDelayTask sch = (IThreadPoolDelayTask)task; long schdlDelay = sch.getScheduleDelay(); threadExecutor.scheduleWithFixedDelay(task, delay, schdlDelay <= 0 ? 60000 : schdlDelay, TimeUnit.MILLISECONDS); } else { threadExecutor.schedule(task, delay, TimeUnit.MILLISECONDS); } } /** * Creates and executes a periodic task that becomes enabled first after the * given initial delay, and subsequently with the given delay between the * termination of one execution and the commencement of the next. * * @param task * the task to execute * @param initialDelay * the time to delay first execution in milliseconds * @param delay * the delay between the termination of one execution and the * commencement of the next in milliseconds * @return a Future representing pending completion of the task, and whose * get() method will throw an exception upon cancellation. */ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long delay) { return threadExecutor.scheduleWithFixedDelay(task, initialDelay, delay, TimeUnit.MILLISECONDS); } /** * Creates and executes a periodic action that becomes enabled first after * the given initial delay, and subsequently with the given period. * * @param task * the task to execute * @param initialDelay * the time to delay first execution in milliseconds * @param period * the period between successive executions in milliseconds * @return a Future representing pending completion of the task, and whose * get() method will throw an exception upon cancellation. */ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period) { return threadExecutor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.MILLISECONDS); } /* * * Returns the core number of threads. * @return the core number of threads * / public int getPoolSize() { return threadExecutor.getCorePoolSize(); } */ /** * Returns the approximate number of threads that are actively executing * tasks. * * @return the number of threads */ public int getActiveCount() { return threadExecutor.getActiveCount(); } /** * Returns the core number of threads. * @return Returns the core number of threads. * @see #setCorePoolSize */ public int getCorePoolSize() { return threadExecutor.getCorePoolSize(); } /** * Returns the maximum allowed number of threads. * @return the maximum allowed number of threads * @see #setMaximumPoolSize */ public int getMaximumPoolSize() { return threadExecutor.getMaximumPoolSize(); } /** * Returns the thread keep-alive time, which is the amount of time which * threads in excess of the core pool size may remain idle before being * terminated. * * @return the time limit in milliseconds * @see #setKeepAliveTime */ public long getKeepAliveTime() { return threadExecutor.getKeepAliveTime(TimeUnit.MILLISECONDS); } /* * * Returns the internal {@link ScheduledThreadPoolExecutor} allowing direct * access to the thread pool for advanced applications. * <tt>ThreadPoolManager</tt> * * @return the {@link ScheduledThreadPoolExecutor} */ public ScheduledThreadPoolExecutor getThreadExecutor() { return threadExecutor; } /** * Sets the core number of threads. This overrides any value set in the * constructor. If the new value is smaller than the current value, excess * existing threads will be terminated when they next become idle. If * larger, new threads will, if needed, be started to execute any queued * tasks. * * @param corePoolSize the new core size * @see #getCorePoolSize */ public void setCorePoolSize(int corePoolSize) { threadExecutor.setCorePoolSize(corePoolSize); if (corePoolSize > threadExecutor.getMaximumPoolSize()) threadExecutor.setMaximumPoolSize(corePoolSize); } /** * Sets the maximum allowed number of threads. This overrides any value set * in the constructor. If the new value is smaller than the current value, * excess existing threads will be terminated when they next become idle. * * @param maxPoolSize the new maximum * @see #getMaximumPoolSize */ public void setMaximumPoolSize(int maxPoolSize) { threadExecutor.setMaximumPoolSize(maxPoolSize); } /** * Sets the time limit for which threads may remain idle before being * terminated. If there are more than the core number of threads currently * in the pool, after waiting this amount of time without processing a task, * excess threads will be terminated. This overrides any value set in the * constructor. * * @param time * the time to wait in milliseconds. A time value of zero will cause excess * threads to terminate immediately after executing tasks. * @see #getKeepAliveTime */ public void setKeepAliveTime(long time) { threadExecutor.setKeepAliveTime(time, TimeUnit.MILLISECONDS); } /** * Removes this task from the executor's internal queue if it is present, * thus causing it not to be run if it has not already started. * * @param task the task to remove * @return true if the task was removed */ public boolean removeTask(Runnable task) { return threadExecutor.remove(task); } /** * Adds a task to be run in a thread pool. Tasks will not start until the * <code>execute()</code> method is called. The task can execute with a * variable delay if it implements {@link IThreadPoolDelayTask}. * * @param task - Runnable most likely HttpPullProtocolHandler */ public void addTask(Runnable task) { List<Runnable> t = tasks; if (t != null) { synchronized(t) { t.add(task); // queue up tasks until execute() is called } } else { execute(task); } } /** * Called when a task is leaving the active pool and is not scheduled to * run again. */ public void threadCompleted(Runnable r, Throwable t) { //if( ( ADK.debug & ADK.DBG_LIFECYCLE ) != 0 ) { // Agent.getLog().debug( "Task Completed: " + r.toString() ); //} } }