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   * @deprecated Use {@link QueuedThreadPool}
39   * @author Greg Wilkins <gregw@mortbay.com>
40   * @author Juancarlo Anez <juancarlo@modelistica.com>
41   */
42  public class BoundedThreadPool extends AbstractLifeCycle implements Serializable, ThreadPool
43  {
44      private static int __id;
45      private boolean _daemon;
46      private int _id;
47      private List _idle;
48  
49      private final Object _lock = new Object();
50      private final Object _joinLock = new Object();
51  
52      private long _lastShrink;
53      private int _maxIdleTimeMs=60000;
54      private int _maxThreads=255;
55      private int _minThreads=1;
56      private String _name;
57      private List _queue;
58      private Set _threads;
59      private boolean _warned=false;
60      int _lowThreads=0;
61      int _priority= Thread.NORM_PRIORITY;
62  
63      /* ------------------------------------------------------------------- */
64      /* Construct
65       */
66      public BoundedThreadPool()
67      {
68          Log.warn(BoundedThreadPool.class+" deprecated: use "+QueuedThreadPool.class);
69          _name="btpool"+__id++;
70      }
71  
72      /* ------------------------------------------------------------ */
73      /** Run job.
74       * @return true 
75       */
76      public boolean dispatch(Runnable job) 
77      {
78          synchronized(_lock)
79          {       
80              if (!isRunning() || job==null)
81                  return false;
82  
83              // Are there no threads available?
84              int idle=_idle.size();
85              if (idle>0)
86              {
87                  PoolThread thread=(PoolThread)_idle.remove(idle-1);
88                  thread.dispatch(job);
89              }
90              else
91              {
92                  // Are we at max size?
93                  if (_threads.size()<_maxThreads)
94                  {    
95                      // No - create a new thread!
96                      newThread(job);
97                  }
98                  else 
99                  {
100                     if (!_warned)    
101                     {
102                         _warned=true;
103                         Log.debug("Out of threads for {}",this);
104                     }
105                     _queue.add(job);
106                 }
107             }
108         }
109 
110         return true;
111     }
112 
113     /* ------------------------------------------------------------ */
114     /** Get the number of idle threads in the pool.
115      * @see #getThreads
116      * @return Number of threads
117      */
118     public int getIdleThreads()
119     {
120         return _idle==null?0:_idle.size();
121     }
122     
123     /* ------------------------------------------------------------ */
124     /**
125      * @return low resource threads threshhold
126      */
127     public int getLowThreads()
128     {
129         return _lowThreads;
130     }
131 
132     
133     /* ------------------------------------------------------------ */
134     /** Get the maximum thread idle time.
135      * Delegated to the named or anonymous Pool.
136      * @see #setMaxIdleTimeMs
137      * @return Max idle time in ms.
138      */
139     public int getMaxIdleTimeMs()
140     {
141         return _maxIdleTimeMs;
142     }
143     
144     /* ------------------------------------------------------------ */
145     /** Set the maximum number of threads.
146      * Delegated to the named or anonymous Pool.
147      * @see #setMaxThreads
148      * @return maximum number of threads.
149      */
150     public int getMaxThreads()
151     {
152         return _maxThreads;
153     }
154 
155     /* ------------------------------------------------------------ */
156     /** Get the minimum number of threads.
157      * Delegated to the named or anonymous Pool.
158      * @see #setMinThreads
159      * @return minimum number of threads.
160      */
161     public int getMinThreads()
162     {
163         return _minThreads;
164     }
165 
166     /* ------------------------------------------------------------ */
167     /** 
168      * @return The name of the BoundedThreadPool.
169      */
170     public String getName()
171     {
172         return _name;
173     }
174 
175     /* ------------------------------------------------------------ */
176     /** Get the number of threads in the pool.
177      * @see #getIdleThreads
178      * @return Number of threads
179      */
180     public int getThreads()
181     {
182         return _threads.size();
183     }
184 
185     /* ------------------------------------------------------------ */
186     /** Get the priority of the pool threads.
187      *  @return the priority of the pool threads.
188      */
189     public int getThreadsPriority()
190     {
191         return _priority;
192     }
193 
194     /* ------------------------------------------------------------ */
195     public int getQueueSize()
196     {
197         synchronized(_lock)
198         {
199             return _queue.size();
200         }
201     }
202 
203     /* ------------------------------------------------------------ */
204     /** 
205      * Delegated to the named or anonymous Pool.
206      */
207     public boolean isDaemon()
208     {
209         return _daemon;
210     }
211 
212     /* ------------------------------------------------------------ */
213     public boolean isLowOnThreads()
214     {
215         synchronized(_lock)
216         {
217             // maybe make this volatile?
218             return _queue.size()>_lowThreads;
219         }
220     }
221 
222     /* ------------------------------------------------------------ */
223     public void join() throws InterruptedException
224     {
225         synchronized (_joinLock)
226         {
227             while (isRunning())
228                 _joinLock.wait();
229         }
230         
231         // TODO remove this semi busy loop!
232         while (isStopping())
233             Thread.sleep(10);
234     }
235 
236     /* ------------------------------------------------------------ */
237     /** 
238      * Delegated to the named or anonymous Pool.
239      */
240     public void setDaemon(boolean daemon)
241     {
242         _daemon=daemon;
243     }
244 
245     /* ------------------------------------------------------------ */
246     /**
247      * @param lowThreads low resource threads threshhold
248      */
249     public void setLowThreads(int lowThreads)
250     {
251         _lowThreads = lowThreads;
252     }
253     
254     /* ------------------------------------------------------------ */
255     /** Set the maximum thread idle time.
256      * Threads that are idle for longer than this period may be
257      * stopped.
258      * Delegated to the named or anonymous Pool.
259      * @see #getMaxIdleTimeMs
260      * @param maxIdleTimeMs Max idle time in ms.
261      */
262     public void setMaxIdleTimeMs(int maxIdleTimeMs)
263     {
264         _maxIdleTimeMs=maxIdleTimeMs;
265     }
266 
267     /* ------------------------------------------------------------ */
268     /** Set the maximum number of threads.
269      * Delegated to the named or anonymous Pool.
270      * @see #getMaxThreads
271      * @param maxThreads maximum number of threads.
272      */
273     public void setMaxThreads(int maxThreads)
274     {
275         if (isStarted() && maxThreads<_minThreads)
276             throw new IllegalArgumentException("!minThreads<maxThreads");
277         _maxThreads=maxThreads;
278     }
279 
280     /* ------------------------------------------------------------ */
281     /** Set the minimum number of threads.
282      * Delegated to the named or anonymous Pool.
283      * @see #getMinThreads
284      * @param minThreads minimum number of threads
285      */
286     public void setMinThreads(int minThreads)
287     {
288         if (isStarted() && (minThreads<=0 || minThreads>_maxThreads))
289             throw new IllegalArgumentException("!0<=minThreads<maxThreads");
290         _minThreads=minThreads;
291         synchronized (_lock)
292         {
293             while (isStarted() && _threads.size()<_minThreads)
294             {
295                 newThread(null);   
296             }
297         }
298     }
299 
300     /* ------------------------------------------------------------ */
301     /** 
302      * @param name Name of the BoundedThreadPool to use when naming Threads.
303      */
304     public void setName(String name)
305     {
306         _name= name;
307     }
308 
309     /* ------------------------------------------------------------ */
310     /** Set the priority of the pool threads.
311      *  @param priority the new thread priority.
312      */
313     public void setThreadsPriority(int priority)
314     {
315         _priority=priority;
316     }
317 
318     /* ------------------------------------------------------------ */
319     /* Start the BoundedThreadPool.
320      * Construct the minimum number of threads.
321      */
322     protected void doStart() throws Exception
323     {
324         if (_maxThreads<_minThreads || _minThreads<=0)
325             throw new IllegalArgumentException("!0<minThreads<maxThreads");
326         
327         _threads=new HashSet();
328         _idle=new ArrayList();
329         _queue=new LinkedList();
330         
331         for (int i=0;i<_minThreads;i++)
332         {
333             newThread(null);
334         }   
335     }
336 
337     /* ------------------------------------------------------------ */
338     /** Stop the BoundedThreadPool.
339      * New jobs are no longer accepted,idle threads are interrupted
340      * and stopJob is called on active threads.
341      * The method then waits 
342      * min(getMaxStopTimeMs(),getMaxIdleTimeMs()), for all jobs to
343      * stop, at which time killJob is called.
344      */
345     protected void doStop() throws Exception
346     {   
347         super.doStop();
348         
349         for (int i=0;i<100;i++)
350         {
351             synchronized (_lock)
352             {
353                 Iterator iter = _threads.iterator();
354                 while (iter.hasNext())
355                     ((Thread)iter.next()).interrupt();
356             }
357             
358             Thread.yield();
359             if (_threads.size()==0)
360                break;
361             
362             try
363             {
364                 Thread.sleep(i*100);
365             }
366             catch(InterruptedException e){}
367         }
368 
369         // TODO perhaps force stops
370         if (_threads.size()>0)
371             Log.warn(_threads.size()+" threads could not be stopped");
372         
373         synchronized (_joinLock)
374         {
375             _joinLock.notifyAll();
376         }
377     }
378 
379     /* ------------------------------------------------------------ */
380     protected PoolThread newThread(Runnable job)
381     {
382         synchronized(_lock)
383         {
384             PoolThread thread =new PoolThread(job);
385             _threads.add(thread);
386             thread.setName(_name+"-"+_id++);
387             thread.start();  
388             return thread;
389         }
390     }
391 
392     /* ------------------------------------------------------------ */
393     /** Stop a Job.
394      * This method is called by the Pool if a job needs to be stopped.
395      * The default implementation does nothing and should be extended by a
396      * derived thread pool class if special action is required.
397      * @param thread The thread allocated to the job, or null if no thread allocated.
398      * @param job The job object passed to run.
399      */
400     protected void stopJob(Thread thread, Object job)
401     {
402         thread.interrupt();
403     }
404     
405 
406     /* ------------------------------------------------------------ */
407     /** Pool Thread class.
408      * The PoolThread allows the threads job to be
409      * retrieved and active status to be indicated.
410      */
411     public class PoolThread extends Thread 
412     {
413         Runnable _job=null;
414 
415         /* ------------------------------------------------------------ */
416         PoolThread()
417         {
418             setDaemon(_daemon);
419             setPriority(_priority);
420         }
421 
422         /* ------------------------------------------------------------ */
423         PoolThread(Runnable job)
424         {
425             setDaemon(_daemon);
426             setPriority(_priority);
427             _job=job;
428         }
429 
430         /* ------------------------------------------------------------ */
431         /** BoundedThreadPool run.
432          * Loop getting jobs and handling them until idle or stopped.
433          */
434         public void run()
435         {
436             try
437             {
438                 Runnable job=null;
439 
440                 synchronized (this)
441                 {
442                     job=_job;
443                     _job=null;
444                 }
445                 
446                 while (isRunning())
447                 {
448                     if (job!=null)
449                     {
450                         Runnable todo=job;
451                         job=null;
452                         todo.run();
453                     }
454                     else
455                     {
456                         // No job
457                         synchronized (_lock)
458                         {
459                             // is there a queued job?
460                             if (_queue.size()>0)
461                             {
462                                 job=(Runnable)_queue.remove(0);
463                                 continue;
464                             }
465                             else
466                             {
467                                 _warned=false;
468                                 
469                                 // consider shrinking the thread pool
470                                 if (_threads.size()>_maxThreads ||     // we have too many threads  OR
471                                     _idle.size()>0 &&                  // are there idle threads?
472                                     _threads.size()>_minThreads)       // AND are there more than min threads?
473                                 {
474                                     long now = System.currentTimeMillis();
475                                     if ((now-_lastShrink)>getMaxIdleTimeMs())
476                                     {
477                                         _lastShrink=now;
478                                         return;
479                                     }
480                                 }
481                             }
482                                
483                             // we are going idle!
484                             _idle.add(this);
485                         }
486 
487                         try
488                         {
489                             synchronized (this)
490                             {
491                                 if (_job==null)
492                                     this.wait(getMaxIdleTimeMs());
493                                 job=_job;
494                                 _job=null;
495                             }
496                         }
497                         catch (InterruptedException e)
498                         {
499                             Log.ignore(e);
500                         }
501                         finally
502                         {
503                             synchronized (_lock)
504                             {
505                                 _idle.remove(this);
506                             }
507                         }
508                     }
509                 }
510             }
511             finally
512             {
513                 synchronized (_lock)
514                 {
515                     _threads.remove(this);
516                 }
517                 
518                 Runnable job=null;
519                 synchronized (this)
520                 {
521                     job=_job;
522                 }
523                 if (job!=null && isRunning())
524                     BoundedThreadPool.this.dispatch(job);
525             }
526         }
527         
528         /* ------------------------------------------------------------ */
529         void dispatch(Runnable job)
530         {
531             synchronized (this)
532             {
533                 if(_job!=null || job==null)
534                     throw new IllegalStateException();
535                 _job=job;
536                 this.notify();
537             }
538         }
539     }
540 
541 }