View Javadoc

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("Max threads for {}",this);
117                     }
118                 }
119             }
120         }
121         
122         if (thread!=null)
123         {
124             thread.dispatch(job);
125         }
126         else
127         {
128             synchronized(_jobsLock)
129             {
130                 _queued++;
131                 _jobs[_nextJobSlot++]=job;
132                 if (_nextJobSlot==_jobs.length)
133                     _nextJobSlot=0;
134                 if (_nextJobSlot==_nextJob)
135                 {
136                     // Grow the job queue
137                     Runnable[] jobs= new Runnable[_jobs.length+_maxThreads];
138                     int split=_jobs.length-_nextJob;
139                     if (split>0)
140                         System.arraycopy(_jobs,_nextJob,jobs,0,split);
141                     if (_nextJob!=0)
142                         System.arraycopy(_jobs,0,jobs,split,_nextJobSlot);
143                     
144                     _jobs=jobs;
145                     _nextJob=0;
146                     _nextJobSlot=_queued;
147                 }
148                 
149                 if (spawn && _queued<=_spawnOrShrinkAt)
150                     spawn=false;
151             }
152             
153             if (spawn)
154                 newThread();
155         }
156         
157 
158         return true;
159     }
160 
161     /* ------------------------------------------------------------ */
162     /** Get the number of idle threads in the pool.
163      * @see #getThreads
164      * @return Number of threads
165      */
166     public int getIdleThreads()
167     {
168         return _idle==null?0:_idle.size();
169     }
170     
171     /* ------------------------------------------------------------ */
172     /**
173      * @return low resource threads threshhold
174      */
175     public int getLowThreads()
176     {
177         return _lowThreads;
178     }
179     
180     /* ------------------------------------------------------------ */
181     /** Get the maximum thread idle time.
182      * Delegated to the named or anonymous Pool.
183      * @see #setMaxIdleTimeMs
184      * @return Max idle time in ms.
185      */
186     public int getMaxIdleTimeMs()
187     {
188         return _maxIdleTimeMs;
189     }
190     
191     /* ------------------------------------------------------------ */
192     /** Set the maximum number of threads.
193      * Delegated to the named or anonymous Pool.
194      * @see #setMaxThreads
195      * @return maximum number of threads.
196      */
197     public int getMaxThreads()
198     {
199         return _maxThreads;
200     }
201 
202     /* ------------------------------------------------------------ */
203     /** Get the minimum number of threads.
204      * Delegated to the named or anonymous Pool.
205      * @see #setMinThreads
206      * @return minimum number of threads.
207      */
208     public int getMinThreads()
209     {
210         return _minThreads;
211     }
212 
213     /* ------------------------------------------------------------ */
214     /** 
215      * @return The name of the BoundedThreadPool.
216      */
217     public String getName()
218     {
219         return _name;
220     }
221 
222     /* ------------------------------------------------------------ */
223     /** Get the number of threads in the pool.
224      * @see #getIdleThreads
225      * @return Number of threads
226      */
227     public int getThreads()
228     {
229         return _threads.size();
230     }
231 
232     /* ------------------------------------------------------------ */
233     /** Get the priority of the pool threads.
234      *  @return the priority of the pool threads.
235      */
236     public int getThreadsPriority()
237     {
238         return _priority;
239     }
240 
241     /* ------------------------------------------------------------ */
242     public int getQueueSize()
243     {
244         return _queued;
245     }
246     
247     /* ------------------------------------------------------------ */
248     /**
249      * @return the spawnOrShrinkAt  The number of queued jobs (or idle threads) needed 
250      * before the thread pool is grown (or shrunk)
251      */
252     public int getSpawnOrShrinkAt()
253     {
254         return _spawnOrShrinkAt;
255     }
256 
257     /* ------------------------------------------------------------ */
258     /**
259      * @param spawnOrShrinkAt The number of queued jobs (or idle threads) needed 
260      * before the thread pool is grown (or shrunk)
261      */
262     public void setSpawnOrShrinkAt(int spawnOrShrinkAt)
263     {
264         _spawnOrShrinkAt=spawnOrShrinkAt;
265     }
266 
267     /* ------------------------------------------------------------ */
268     /** 
269      * Delegated to the named or anonymous Pool.
270      */
271     public boolean isDaemon()
272     {
273         return _daemon;
274     }
275 
276     /* ------------------------------------------------------------ */
277     public boolean isLowOnThreads()
278     {
279         return _queued>_lowThreads;
280     }
281 
282     /* ------------------------------------------------------------ */
283     public void join() throws InterruptedException
284     {
285         synchronized (_joinLock)
286         {
287             while (isRunning())
288                 _joinLock.wait();
289         }
290         
291         // TODO remove this semi busy loop!
292         while (isStopping())
293             Thread.sleep(100);
294     }
295 
296     /* ------------------------------------------------------------ */
297     /** 
298      * Delegated to the named or anonymous Pool.
299      */
300     public void setDaemon(boolean daemon)
301     {
302         _daemon=daemon;
303     }
304 
305     /* ------------------------------------------------------------ */
306     /**
307      * @param lowThreads low resource threads threshhold
308      */
309     public void setLowThreads(int lowThreads)
310     {
311         _lowThreads = lowThreads;
312     }
313     
314     /* ------------------------------------------------------------ */
315     /** Set the maximum thread idle time.
316      * Threads that are idle for longer than this period may be
317      * stopped.
318      * Delegated to the named or anonymous Pool.
319      * @see #getMaxIdleTimeMs
320      * @param maxIdleTimeMs Max idle time in ms.
321      */
322     public void setMaxIdleTimeMs(int maxIdleTimeMs)
323     {
324         _maxIdleTimeMs=maxIdleTimeMs;
325     }
326 
327     /* ------------------------------------------------------------ */
328     /** Set the maximum number of threads.
329      * Delegated to the named or anonymous Pool.
330      * @see #getMaxThreads
331      * @param maxThreads maximum number of threads.
332      */
333     public void setMaxThreads(int maxThreads)
334     {
335         if (isStarted() && maxThreads<_minThreads)
336             throw new IllegalArgumentException("!minThreads<maxThreads");
337         _maxThreads=maxThreads;
338     }
339 
340     /* ------------------------------------------------------------ */
341     /** Set the minimum number of threads.
342      * Delegated to the named or anonymous Pool.
343      * @see #getMinThreads
344      * @param minThreads minimum number of threads
345      */
346     public void setMinThreads(int minThreads)
347     {
348         if (isStarted() && (minThreads<=0 || minThreads>_maxThreads))
349             throw new IllegalArgumentException("!0<=minThreads<maxThreads");
350         _minThreads=minThreads;
351         synchronized (_threadLock)
352         {
353             while (isStarted() && _threads.size()<_minThreads)
354             {
355                 newThread();   
356             }
357         }
358     }
359 
360     /* ------------------------------------------------------------ */
361     /** 
362      * @param name Name of the BoundedThreadPool to use when naming Threads.
363      */
364     public void setName(String name)
365     {
366         _name= name;
367     }
368 
369     /* ------------------------------------------------------------ */
370     /** Set the priority of the pool threads.
371      *  @param priority the new thread priority.
372      */
373     public void setThreadsPriority(int priority)
374     {
375         _priority=priority;
376     }
377 
378     /* ------------------------------------------------------------ */
379     /* Start the BoundedThreadPool.
380      * Construct the minimum number of threads.
381      */
382     protected void doStart() throws Exception
383     {
384         if (_maxThreads<_minThreads || _minThreads<=0)
385             throw new IllegalArgumentException("!0<minThreads<maxThreads");
386         
387         _threads=new HashSet();
388         _idle=new ArrayList();
389         _jobs=new Runnable[_maxThreads];
390         
391         for (int i=0;i<_minThreads;i++)
392         {
393             newThread();
394         }   
395     }
396 
397     /* ------------------------------------------------------------ */
398     /** Stop the BoundedThreadPool.
399      * New jobs are no longer accepted,idle threads are interrupted
400      * and stopJob is called on active threads.
401      * The method then waits 
402      * min(getMaxStopTimeMs(),getMaxIdleTimeMs()), for all jobs to
403      * stop, at which time killJob is called.
404      */
405     protected void doStop() throws Exception
406     {   
407         super.doStop();
408         
409         for (int i=0;i<100;i++)
410         {
411             synchronized (_threadLock)
412             {
413                 Iterator iter = _threads.iterator();
414                 while (iter.hasNext())
415                     ((Thread)iter.next()).interrupt();
416             }
417             
418             Thread.yield();
419             if (_threads.size()==0)
420                break;
421             
422             try
423             {
424                 Thread.sleep(i*100);
425             }
426             catch(InterruptedException e){}
427         }
428 
429         // TODO perhaps force stops
430         if (_threads.size()>0)
431             Log.warn(_threads.size()+" threads could not be stopped");
432         
433         synchronized (_joinLock)
434         {
435             _joinLock.notifyAll();
436         }
437     }
438 
439     /* ------------------------------------------------------------ */
440     protected void newThread()
441     {
442         synchronized(_threadLock)
443         {
444             PoolThread thread =new PoolThread();
445             _threads.add(thread);
446             thread.setName(_name+"-"+_id++);
447             thread.start(); 
448         }
449     }
450 
451     /* ------------------------------------------------------------ */
452     /** Stop a Job.
453      * This method is called by the Pool if a job needs to be stopped.
454      * The default implementation does nothing and should be extended by a
455      * derived thread pool class if special action is required.
456      * @param thread The thread allocated to the job, or null if no thread allocated.
457      * @param job The job object passed to run.
458      */
459     protected void stopJob(Thread thread, Object job)
460     {
461         thread.interrupt();
462     }
463     
464 
465     /* ------------------------------------------------------------ */
466     /** Pool Thread class.
467      * The PoolThread allows the threads job to be
468      * retrieved and active status to be indicated.
469      */
470     public class PoolThread extends Thread 
471     {
472         Runnable _job=null;
473         boolean _alive=true;
474 
475         /* ------------------------------------------------------------ */
476         PoolThread()
477         {
478             setDaemon(_daemon);
479             setPriority(_priority);
480         }
481         
482         /* ------------------------------------------------------------ */
483         /** BoundedThreadPool run.
484          * Loop getting jobs and handling them until idle or stopped.
485          */
486         public void run()
487         {
488             boolean idle=false;
489             Runnable job=null;
490             try
491             {
492                 while (isRunning())
493                 {   
494                     if (job!=null)
495                     {
496                         final Runnable todo=job;
497                         job=null;
498                         idle=false;
499                         todo.run();
500                     }
501                     else if (idle)
502                     {
503                         // should this idle thread exit?
504                         synchronized(_idleLock)
505                         {
506                             _warned=false;
507                             
508                             // consider shrinking the thread pool
509                             if ((_threads.size()>_maxThreads ||     // we have too many threads  OR
510                                  _idle.size()>_spawnOrShrinkAt &&        // are there idle threads?
511                                 _threads.size()>_minThreads))       // AND are there more than min threads?
512                             {
513                                 long now = System.currentTimeMillis();
514                                 if ((now-_lastShrink)>getMaxIdleTimeMs() && _idle.remove(this))
515                                 {
516                                     _lastShrink=now;
517                                     return;
518                                 }
519                             }
520                         }
521 
522                         // still idle, so wait for a dispatched job
523                         try
524                         {
525                             synchronized (this)
526                             {
527                                 if (_job==null)
528                                     this.wait(getMaxIdleTimeMs());
529                                 job=_job;
530                                 _job=null;
531                             }
532                         }
533                         catch (InterruptedException e)
534                         {
535                             Log.ignore(e);
536                         }
537                     }
538                     else
539                     {
540                         // Look for a queued job
541                         synchronized (_jobsLock)
542                         {
543                             // is there a queued job?
544                             if (_queued>0)
545                             {
546                                 _queued--;
547                                 job=_jobs[_nextJob++];
548                                 if (_nextJob==_jobs.length)
549                                     _nextJob=0;
550                                 continue;
551                             }
552                         }
553                         
554                         // if no job found
555                         if (job==null)
556                         {
557                             // go idle
558                             synchronized (_idleLock)
559                             {
560                                 _idle.add(this);
561                                 idle=true;
562                             }
563                         }
564                     }
565                 }
566             }
567             finally
568             {
569                 synchronized (_idleLock)
570                 {
571                     _idle.remove(this);
572                 }
573                 synchronized (_threadLock)
574                 {
575                     _threads.remove(this);
576                 }
577                 
578                 synchronized (this)
579                 {
580                     job=_job;
581                     _alive=false;
582                 }
583                 // we died with a job! reschedule it
584                 if (job!=null)
585                     QueuedThreadPool.this.dispatch(job);
586             }
587         }
588         
589         /* ------------------------------------------------------------ */
590         void dispatch(Runnable job)
591         {
592             synchronized (this)
593             {
594                 if (_alive)
595                 {
596                     _job=job;
597                     this.notify();
598                 }
599                 else
600                     // thread died while dispatching so reschedule it
601                     QueuedThreadPool.this.dispatch(job);
602             }
603         }
604     }
605 
606     private class Lock{}
607 }