1   // ========================================================================
2   // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.thread;
16  
17  import java.io.Serializable;
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.Set;
24  
25  import org.mortbay.component.AbstractLifeCycle;
26  import org.mortbay.log.Log;
27  
28  /* ------------------------------------------------------------ */
29  /** A pool of threads.
30   * <p>
31   * Avoids the expense of thread creation by pooling threads after
32   * their run methods exit for reuse.
33   * <p>
34   * If the maximum pool size is reached, jobs wait for a free thread.
35   * By default there is no maximum pool size.  Idle threads timeout
36   * and terminate until the minimum number of threads are running.
37   * <p>
38   * @author Greg Wilkins <gregw@mortbay.com>
39   * @author Juancarlo Anez <juancarlo@modelistica.com>
40   */
41  public class QueuedThreadPool extends AbstractLifeCycle implements Serializable, ThreadPool
42  {
43      private static int __id;
44      
45      private String _name;
46      private Set _threads;
47      private List _idle;
48      private Runnable[] _jobs;
49      private int _nextJob;
50      private int _nextJobSlot;
51      private int _queued;
52      
53      private boolean _daemon;
54      private int _id;
55  
56      private final Object _threadLock = new Lock();
57      private final Object _idleLock = new Lock();
58      private final Object _jobsLock = new Lock();
59      private final Object _joinLock = new Lock();
60  
61      private long _lastShrink;
62      private int _maxIdleTimeMs=60000;
63      private int _maxThreads=25;
64      private int _minThreads=2;
65      private boolean _warned=false;
66      private int _lowThreads=0;
67      private int _priority= Thread.NORM_PRIORITY;
68      private int _spawnOrShrinkAt=0;
69  
70      /* ------------------------------------------------------------------- */
71      /* Construct
72       */
73      public QueuedThreadPool()
74      {
75          _name="qtp"+__id++;
76      }
77      
78      /* ------------------------------------------------------------------- */
79      /* Construct
80       */
81      public QueuedThreadPool(int maxThreads)
82      {
83          this();
84          setMaxThreads(maxThreads);
85      }
86  
87      /* ------------------------------------------------------------ */
88      /** Run job.
89       * @return true 
90       */
91      public boolean dispatch(Runnable job) 
92      {  
93          if (!isRunning() || job==null)
94              return false;
95  
96  
97          PoolThread thread=null;
98          boolean spawn=false;
99              
100         // Look for an idle thread
101         synchronized(_idleLock)
102         {
103             int idle=_idle.size();
104             if (idle>0)
105                 thread=(PoolThread)_idle.remove(idle-1);
106             else
107             {
108                 // Are we at max size?
109                 if (_threads.size()<_maxThreads)
110                     spawn=true;
111                 else 
112                 {
113                     if (!_warned)    
114                     {
115                         _warned=true;
116                         Log.debug("Out of threads for {}",this);
117                     }
118                 }
119             }
120         }
121         
122         if (thread!=null)
123             thread.dispatch(job);
124         else
125         {
126             synchronized(_jobsLock)
127             {
128                 _queued++;
129                 _jobs[_nextJobSlot++]=job;
130                 if (_nextJobSlot==_jobs.length)
131                     _nextJobSlot=0;
132                 if (_nextJobSlot==_nextJob)
133                 {
134                     // Grow the job queue
135                     Runnable[] jobs= new Runnable[_jobs.length+_maxThreads];
136                     int split=_jobs.length-_nextJob;
137                     if (split>0)
138                         System.arraycopy(_jobs,_nextJob,jobs,0,split);
139                     if (_nextJob!=0)
140                         System.arraycopy(_jobs,0,jobs,split,_nextJobSlot);
141                     
142                     _jobs=jobs;
143                     _nextJob=0;
144                     _nextJobSlot=_queued;
145                 }
146                 
147                 if (spawn && _queued<=_spawnOrShrinkAt)
148                     spawn=false;
149             }
150             
151             if (spawn)
152                 newThread();
153         }
154         
155 
156         return true;
157     }
158 
159     /* ------------------------------------------------------------ */
160     /** Get the number of idle threads in the pool.
161      * @see #getThreads
162      * @return Number of threads
163      */
164     public int getIdleThreads()
165     {
166         return _idle==null?0:_idle.size();
167     }
168     
169     /* ------------------------------------------------------------ */
170     /**
171      * @return low resource threads threshhold
172      */
173     public int getLowThreads()
174     {
175         return _lowThreads;
176     }
177     
178     /* ------------------------------------------------------------ */
179     /** Get the maximum thread idle time.
180      * Delegated to the named or anonymous Pool.
181      * @see #setMaxIdleTimeMs
182      * @return Max idle time in ms.
183      */
184     public int getMaxIdleTimeMs()
185     {
186         return _maxIdleTimeMs;
187     }
188     
189     /* ------------------------------------------------------------ */
190     /** Set the maximum number of threads.
191      * Delegated to the named or anonymous Pool.
192      * @see #setMaxThreads
193      * @return maximum number of threads.
194      */
195     public int getMaxThreads()
196     {
197         return _maxThreads;
198     }
199 
200     /* ------------------------------------------------------------ */
201     /** Get the minimum number of threads.
202      * Delegated to the named or anonymous Pool.
203      * @see #setMinThreads
204      * @return minimum number of threads.
205      */
206     public int getMinThreads()
207     {
208         return _minThreads;
209     }
210 
211     /* ------------------------------------------------------------ */
212     /** 
213      * @return The name of the BoundedThreadPool.
214      */
215     public String getName()
216     {
217         return _name;
218     }
219 
220     /* ------------------------------------------------------------ */
221     /** Get the number of threads in the pool.
222      * @see #getIdleThreads
223      * @return Number of threads
224      */
225     public int getThreads()
226     {
227         return _threads.size();
228     }
229 
230     /* ------------------------------------------------------------ */
231     /** Get the priority of the pool threads.
232      *  @return the priority of the pool threads.
233      */
234     public int getThreadsPriority()
235     {
236         return _priority;
237     }
238 
239     /* ------------------------------------------------------------ */
240     public int getQueueSize()
241     {
242         return _queued;
243     }
244     
245     /* ------------------------------------------------------------ */
246     /**
247      * @return the spawnOrShrinkAt  The number of queued jobs (or idle threads) needed 
248      * before the thread pool is grown (or shrunk)
249      */
250     public int getSpawnOrShrinkAt()
251     {
252         return _spawnOrShrinkAt;
253     }
254 
255     /* ------------------------------------------------------------ */
256     /**
257      * @param spawnOrShrinkAt The number of queued jobs (or idle threads) needed 
258      * before the thread pool is grown (or shrunk)
259      */
260     public void setSpawnOrShrinkAt(int spawnOrShrinkAt)
261     {
262         _spawnOrShrinkAt=spawnOrShrinkAt;
263     }
264 
265     /* ------------------------------------------------------------ */
266     /** 
267      * Delegated to the named or anonymous Pool.
268      */
269     public boolean isDaemon()
270     {
271         return _daemon;
272     }
273 
274     /* ------------------------------------------------------------ */
275     public boolean isLowOnThreads()
276     {
277         return _queued>_lowThreads;
278     }
279 
280     /* ------------------------------------------------------------ */
281     public void join() throws InterruptedException
282     {
283         synchronized (_joinLock)
284         {
285             while (isRunning())
286                 _joinLock.wait();
287         }
288         
289         // TODO remove this semi busy loop!
290         while (isStopping())
291             Thread.sleep(100);
292     }
293 
294     /* ------------------------------------------------------------ */
295     /** 
296      * Delegated to the named or anonymous Pool.
297      */
298     public void setDaemon(boolean daemon)
299     {
300         _daemon=daemon;
301     }
302 
303     /* ------------------------------------------------------------ */
304     /**
305      * @param lowThreads low resource threads threshhold
306      */
307     public void setLowThreads(int lowThreads)
308     {
309         _lowThreads = lowThreads;
310     }
311     
312     /* ------------------------------------------------------------ */
313     /** Set the maximum thread idle time.
314      * Threads that are idle for longer than this period may be
315      * stopped.
316      * Delegated to the named or anonymous Pool.
317      * @see #getMaxIdleTimeMs
318      * @param maxIdleTimeMs Max idle time in ms.
319      */
320     public void setMaxIdleTimeMs(int maxIdleTimeMs)
321     {
322         _maxIdleTimeMs=maxIdleTimeMs;
323     }
324 
325     /* ------------------------------------------------------------ */
326     /** Set the maximum number of threads.
327      * Delegated to the named or anonymous Pool.
328      * @see #getMaxThreads
329      * @param maxThreads maximum number of threads.
330      */
331     public void setMaxThreads(int maxThreads)
332     {
333         if (isStarted() && maxThreads<_minThreads)
334             throw new IllegalArgumentException("!minThreads<maxThreads");
335         _maxThreads=maxThreads;
336     }
337 
338     /* ------------------------------------------------------------ */
339     /** Set the minimum number of threads.
340      * Delegated to the named or anonymous Pool.
341      * @see #getMinThreads
342      * @param minThreads minimum number of threads
343      */
344     public void setMinThreads(int minThreads)
345     {
346         if (isStarted() && (minThreads<=0 || minThreads>_maxThreads))
347             throw new IllegalArgumentException("!0<=minThreads<maxThreads");
348         _minThreads=minThreads;
349         synchronized (_threadLock)
350         {
351             while (isStarted() && _threads.size()<_minThreads)
352             {
353                 newThread();   
354             }
355         }
356     }
357 
358     /* ------------------------------------------------------------ */
359     /** 
360      * @param name Name of the BoundedThreadPool to use when naming Threads.
361      */
362     public void setName(String name)
363     {
364         _name= name;
365     }
366 
367     /* ------------------------------------------------------------ */
368     /** Set the priority of the pool threads.
369      *  @param priority the new thread priority.
370      */
371     public void setThreadsPriority(int priority)
372     {
373         _priority=priority;
374     }
375 
376     /* ------------------------------------------------------------ */
377     /* Start the BoundedThreadPool.
378      * Construct the minimum number of threads.
379      */
380     protected void doStart() throws Exception
381     {
382         if (_maxThreads<_minThreads || _minThreads<=0)
383             throw new IllegalArgumentException("!0<minThreads<maxThreads");
384         
385         _threads=new HashSet();
386         _idle=new ArrayList();
387         _jobs=new Runnable[_maxThreads];
388         
389         for (int i=0;i<_minThreads;i++)
390         {
391             newThread();
392         }   
393     }
394 
395     /* ------------------------------------------------------------ */
396     /** Stop the BoundedThreadPool.
397      * New jobs are no longer accepted,idle threads are interrupted
398      * and stopJob is called on active threads.
399      * The method then waits 
400      * min(getMaxStopTimeMs(),getMaxIdleTimeMs()), for all jobs to
401      * stop, at which time killJob is called.
402      */
403     protected void doStop() throws Exception
404     {   
405         super.doStop();
406         
407         for (int i=0;i<100;i++)
408         {
409             synchronized (_threadLock)
410             {
411                 Iterator iter = _threads.iterator();
412                 while (iter.hasNext())
413                     ((Thread)iter.next()).interrupt();
414             }
415             
416             Thread.yield();
417             if (_threads.size()==0)
418                break;
419             
420             try
421             {
422                 Thread.sleep(i*100);
423             }
424             catch(InterruptedException e){}
425         }
426 
427         // TODO perhaps force stops
428         if (_threads.size()>0)
429             Log.warn(_threads.size()+" threads could not be stopped");
430         
431         synchronized (_joinLock)
432         {
433             _joinLock.notifyAll();
434         }
435     }
436 
437     /* ------------------------------------------------------------ */
438     protected void newThread()
439     {
440         synchronized(_threadLock)
441         {
442             PoolThread thread =new PoolThread();
443             _threads.add(thread);
444             thread.setName(_name+"-"+_id++);
445             thread.start(); 
446         }
447     }
448 
449     /* ------------------------------------------------------------ */
450     /** Stop a Job.
451      * This method is called by the Pool if a job needs to be stopped.
452      * The default implementation does nothing and should be extended by a
453      * derived thread pool class if special action is required.
454      * @param thread The thread allocated to the job, or null if no thread allocated.
455      * @param job The job object passed to run.
456      */
457     protected void stopJob(Thread thread, Object job)
458     {
459         thread.interrupt();
460     }
461     
462 
463     /* ------------------------------------------------------------ */
464     /** Pool Thread class.
465      * The PoolThread allows the threads job to be
466      * retrieved and active status to be indicated.
467      */
468     public class PoolThread extends Thread 
469     {
470         Runnable _job=null;
471         boolean _isIdle=false;
472 
473         /* ------------------------------------------------------------ */
474         PoolThread()
475         {
476             setDaemon(_daemon);
477             setPriority(_priority);
478         }
479         
480         /* ------------------------------------------------------------ */
481         /** BoundedThreadPool run.
482          * Loop getting jobs and handling them until idle or stopped.
483          */
484         public void run()
485         {
486             try
487             {
488                 Runnable job=null;
489 
490                 while (isRunning())
491                 {
492                     if (job!=null)
493                     {
494                         _isIdle=false;
495                         Runnable todo=job;
496                         job=null;
497                         todo.run();
498                     }
499                     else
500                     {
501                         // Look for a job
502                         synchronized (_jobsLock)
503                         {
504                             // is there a queued job?
505                             if (_queued>0)
506                             {
507                                 _queued--;
508                                 job=_jobs[_nextJob++];
509                                 if (_nextJob==_jobs.length)
510                                     _nextJob=0;
511                                 continue;
512                             }
513                         }
514 
515 
516                         synchronized(_idleLock)
517                         {
518                             _warned=false;
519                             
520                             // consider shrinking the thread pool
521                             if ((_threads.size()>_maxThreads ||     // we have too many threads  OR
522                                  _idle.size()>_spawnOrShrinkAt &&        // are there idle threads?
523                                 _threads.size()>_minThreads))       // AND are there more than min threads?
524                             {
525                                 // check this thread really is idle
526                                 _isIdle=_idle.contains(this);
527                                
528                                 long now = System.currentTimeMillis();
529                                 if (_isIdle && (now-_lastShrink)>getMaxIdleTimeMs())
530                                 {
531                                     _lastShrink=now;
532                                     return;
533                                 }
534                             }
535                         
536                             // we are going idle!
537                             if (!_isIdle)
538                             {
539                                 _idle.add(this);
540                                 _isIdle=true;
541                             }
542                         }
543 
544                         // wait for a job
545                         try
546                         {
547                             synchronized (this)
548                             {
549                                 if (_job==null)
550                                     this.wait(getMaxIdleTimeMs());
551                                 job=_job;
552                                 _job=null;
553                             }
554                         }
555                         catch (InterruptedException e)
556                         {
557                             Log.ignore(e);
558                         }
559                     }
560                 }
561             }
562             finally
563             {
564                 synchronized (_idleLock)
565                 {
566                     _idle.remove(this);
567                 }
568                 synchronized (_threadLock)
569                 {
570                     _threads.remove(this);
571                 }
572                 
573                 Runnable job=null;
574                 synchronized (this)
575                 {
576                     job=_job;
577                 }
578                 // we died with a job! reschedule it
579                 if (job!=null && isRunning())
580                     QueuedThreadPool.this.dispatch(job);
581             }
582         }
583         
584         /* ------------------------------------------------------------ */
585         void dispatch(Runnable job)
586         {
587             synchronized (this)
588             {
589                 if(_job!=null || job==null)
590                     throw new IllegalStateException();
591                 _job=job;
592                 this.notify();
593             }
594         }
595     }
596 
597     private class Lock{}
598 }