View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.executor;
21  
22  import java.util.ArrayList;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  import java.util.concurrent.Executors;
27  import java.util.concurrent.LinkedBlockingQueue;
28  import java.util.concurrent.RejectedExecutionHandler;
29  import java.util.concurrent.ThreadFactory;
30  import java.util.concurrent.ThreadPoolExecutor;
31  import java.util.concurrent.TimeUnit;
32  import java.util.concurrent.atomic.AtomicInteger;
33  
34  import org.apache.mina.core.session.IoEvent;
35  
36  /**
37   * A {@link ThreadPoolExecutor} that does not maintain the order of {@link IoEvent}s.
38   * This means more than one event handler methods can be invoked at the same
39   * time with mixed order.  For example, let's assume that messageReceived, messageSent,
40   * and sessionClosed events are fired.
41   * <ul>
42   * <li>All event handler methods can be called simultaneously.
43   *     (e.g. messageReceived and messageSent can be invoked at the same time.)</li>
44   * <li>The event order can be mixed up.
45   *     (e.g. sessionClosed or messageSent can be invoked before messageReceived
46   *           is invoked.)</li>
47   * </ul>
48   * If you need to maintain the order of events per session, please use
49   * {@link OrderedThreadPoolExecutor}.
50   *
51   * @author The Apache MINA Project (dev@mina.apache.org)
52   * @version $Rev: 689337 $, $Date: 2008-08-27 04:18:17 +0200 (Wed, 27 Aug 2008) $
53   * @org.apache.xbean.XBean
54   */
55  public class UnorderedThreadPoolExecutor extends ThreadPoolExecutor {
56  
57      private static final Runnable EXIT_SIGNAL = new Runnable() {
58          public void run() {
59              throw new Error(
60                      "This method shouldn't be called. " +
61                      "Please file a bug report.");
62          }
63      };
64  
65      private final Set<Worker> workers = new HashSet<Worker>();
66  
67      private volatile int corePoolSize;
68      private volatile int maximumPoolSize;
69      private volatile int largestPoolSize;
70      private final AtomicInteger idleWorkers = new AtomicInteger();
71  
72      private long completedTaskCount;
73      private volatile boolean shutdown;
74  
75      private final IoEventQueueHandler queueHandler;
76  
77      public UnorderedThreadPoolExecutor() {
78          this(16);
79      }
80  
81      public UnorderedThreadPoolExecutor(int maximumPoolSize) {
82          this(0, maximumPoolSize);
83      }
84  
85      public UnorderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize) {
86          this(corePoolSize, maximumPoolSize, 30, TimeUnit.SECONDS);
87      }
88  
89      public UnorderedThreadPoolExecutor(
90              int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
91          this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory());
92      }
93  
94      public UnorderedThreadPoolExecutor(
95              int corePoolSize, int maximumPoolSize,
96              long keepAliveTime, TimeUnit unit,
97              IoEventQueueHandler queueHandler) {
98          this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), queueHandler);
99      }
100 
101     public UnorderedThreadPoolExecutor(
102             int corePoolSize, int maximumPoolSize,
103             long keepAliveTime, TimeUnit unit,
104             ThreadFactory threadFactory) {
105         this(corePoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, null);
106     }
107 
108     public UnorderedThreadPoolExecutor(
109             int corePoolSize, int maximumPoolSize,
110             long keepAliveTime, TimeUnit unit,
111             ThreadFactory threadFactory, IoEventQueueHandler queueHandler) {
112         super(0, 1, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>(), threadFactory, new AbortPolicy());
113         if (corePoolSize < 0) {
114             throw new IllegalArgumentException("corePoolSize: " + corePoolSize);
115         }
116 
117         if (maximumPoolSize == 0 || maximumPoolSize < corePoolSize) {
118             throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize);
119         }
120 
121         if (queueHandler == null) {
122             queueHandler = IoEventQueueHandler.NOOP;
123         }
124 
125         this.corePoolSize = corePoolSize;
126         this.maximumPoolSize = maximumPoolSize;
127         this.queueHandler = queueHandler;
128     }
129 
130     public IoEventQueueHandler getQueueHandler() {
131         return queueHandler;
132     }
133 
134     @Override
135     public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
136         // Ignore the request.  It must always be AbortPolicy.
137     }
138 
139     private void addWorker() {
140         synchronized (workers) {
141             if (workers.size() >= maximumPoolSize) {
142                 return;
143             }
144 
145             Worker worker = new Worker();
146             Thread thread = getThreadFactory().newThread(worker);
147             idleWorkers.incrementAndGet();
148             thread.start();
149             workers.add(worker);
150 
151             if (workers.size() > largestPoolSize) {
152                 largestPoolSize = workers.size();
153             }
154         }
155     }
156 
157     private void addWorkerIfNecessary() {
158         if (idleWorkers.get() == 0) {
159             synchronized (workers) {
160                 if (workers.isEmpty() || idleWorkers.get() == 0) {
161                     addWorker();
162                 }
163             }
164         }
165     }
166 
167     private void removeWorker() {
168         synchronized (workers) {
169             if (workers.size() <= corePoolSize) {
170                 return;
171             }
172             getQueue().offer(EXIT_SIGNAL);
173         }
174     }
175 
176     @Override
177     public int getMaximumPoolSize() {
178         return maximumPoolSize;
179     }
180 
181     @Override
182     public void setMaximumPoolSize(int maximumPoolSize) {
183         if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) {
184             throw new IllegalArgumentException("maximumPoolSize: "
185                     + maximumPoolSize);
186         }
187 
188         synchronized (workers) {
189             this.maximumPoolSize = maximumPoolSize;
190             int difference = workers.size() - maximumPoolSize;
191             while (difference > 0) {
192                 removeWorker();
193                 --difference;
194             }
195         }
196     }
197 
198     @Override
199     public boolean awaitTermination(long timeout, TimeUnit unit)
200             throws InterruptedException {
201 
202         long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
203 
204         synchronized (workers) {
205             while (!isTerminated()) {
206                 long waitTime = deadline - System.currentTimeMillis();
207                 if (waitTime <= 0) {
208                     break;
209                 }
210 
211                 workers.wait(waitTime);
212             }
213         }
214         return isTerminated();
215     }
216 
217     @Override
218     public boolean isShutdown() {
219         return shutdown;
220     }
221 
222     @Override
223     public boolean isTerminated() {
224         if (!shutdown) {
225             return false;
226         }
227 
228         synchronized (workers) {
229             return workers.isEmpty();
230         }
231     }
232 
233     @Override
234     public void shutdown() {
235         if (shutdown) {
236             return;
237         }
238 
239         shutdown = true;
240 
241         synchronized (workers) {
242             for (int i = workers.size(); i > 0; i --) {
243                 getQueue().offer(EXIT_SIGNAL);
244             }
245         }
246     }
247 
248     @Override
249     public List<Runnable> shutdownNow() {
250         shutdown();
251 
252         List<Runnable> answer = new ArrayList<Runnable>();
253         Runnable task;
254         while ((task = getQueue().poll()) != null) {
255             if (task == EXIT_SIGNAL) {
256                 getQueue().offer(EXIT_SIGNAL);
257                 Thread.yield(); // Let others take the signal.
258                 continue;
259             }
260 
261             getQueueHandler().polled(this, (IoEvent) task);
262             answer.add(task);
263         }
264 
265         return answer;
266     }
267 
268     @Override
269     public void execute(Runnable task) {
270         if (shutdown) {
271             rejectTask(task);
272         }
273 
274         checkTaskType(task);
275 
276         IoEvent e = (IoEvent) task;
277         boolean offeredEvent = queueHandler.accept(this, e);
278         if (offeredEvent) {
279             getQueue().offer(e);
280         }
281 
282         addWorkerIfNecessary();
283 
284         if (offeredEvent) {
285             queueHandler.offered(this, e);
286         }
287     }
288 
289     private void rejectTask(Runnable task) {
290         getRejectedExecutionHandler().rejectedExecution(task, this);
291     }
292 
293     private void checkTaskType(Runnable task) {
294         if (!(task instanceof IoEvent)) {
295             throw new IllegalArgumentException("task must be an IoEvent or its subclass.");
296         }
297     }
298 
299     @Override
300     public int getActiveCount() {
301         synchronized (workers) {
302             return workers.size() - idleWorkers.get();
303         }
304     }
305 
306     @Override
307     public long getCompletedTaskCount() {
308         synchronized (workers) {
309             long answer = completedTaskCount;
310             for (Worker w: workers) {
311                 answer += w.completedTaskCount;
312             }
313 
314             return answer;
315         }
316     }
317 
318     @Override
319     public int getLargestPoolSize() {
320         return largestPoolSize;
321     }
322 
323     @Override
324     public int getPoolSize() {
325         synchronized (workers) {
326             return workers.size();
327         }
328     }
329 
330     @Override
331     public long getTaskCount() {
332         return getCompletedTaskCount();
333     }
334 
335     @Override
336     public boolean isTerminating() {
337         synchronized (workers) {
338             return isShutdown() && !isTerminated();
339         }
340     }
341 
342     @Override
343     public int prestartAllCoreThreads() {
344         int answer = 0;
345         synchronized (workers) {
346             for (int i = corePoolSize - workers.size() ; i > 0; i --) {
347                 addWorker();
348                 answer ++;
349             }
350         }
351         return answer;
352     }
353 
354     @Override
355     public boolean prestartCoreThread() {
356         synchronized (workers) {
357             if (workers.size() < corePoolSize) {
358                 addWorker();
359                 return true;
360             } else {
361                 return false;
362             }
363         }
364     }
365 
366     @Override
367     public void purge() {
368         // Nothing to purge in this implementation.
369     }
370 
371     @Override
372     public boolean remove(Runnable task) {
373         boolean removed = super.remove(task);
374         if (removed) {
375             getQueueHandler().polled(this, (IoEvent) task);
376         }
377         return removed;
378     }
379 
380     @Override
381     public int getCorePoolSize() {
382         return corePoolSize;
383     }
384 
385     @Override
386     public void setCorePoolSize(int corePoolSize) {
387         if (corePoolSize < 0) {
388             throw new IllegalArgumentException("corePoolSize: " + corePoolSize);
389         }
390         if (corePoolSize > maximumPoolSize) {
391             throw new IllegalArgumentException("corePoolSize exceeds maximumPoolSize");
392         }
393 
394         synchronized (workers) {
395             if (this.corePoolSize > corePoolSize) {
396                 for (int i = this.corePoolSize - corePoolSize; i > 0; i --) {
397                     removeWorker();
398                 }
399             }
400             this.corePoolSize = corePoolSize;
401         }
402     }
403 
404     private class Worker implements Runnable {
405 
406         private volatile long completedTaskCount;
407         private Thread thread;
408 
409         public void run() {
410             thread = Thread.currentThread();
411 
412             try {
413                 for (;;) {
414                     Runnable task = fetchTask();
415 
416                     idleWorkers.decrementAndGet();
417 
418                     if (task == null) {
419                         synchronized (workers) {
420                             if (workers.size() > corePoolSize) {
421                                 // Remove now to prevent duplicate exit.
422                                 workers.remove(this);
423                                 break;
424                             }
425                         }
426                     }
427 
428                     if (task == EXIT_SIGNAL) {
429                         break;
430                     }
431 
432                     try {
433                         if (task != null) {
434                             queueHandler.polled(UnorderedThreadPoolExecutor.this, (IoEvent) task);
435                             runTask(task);
436                         }
437                     } finally {
438                         idleWorkers.incrementAndGet();
439                     }
440                 }
441             } finally {
442                 synchronized (workers) {
443                     workers.remove(this);
444                     UnorderedThreadPoolExecutor.this.completedTaskCount += completedTaskCount;
445                     workers.notifyAll();
446                 }
447             }
448         }
449 
450         private Runnable fetchTask() {
451             Runnable task = null;
452             long currentTime = System.currentTimeMillis();
453             long deadline = currentTime + getKeepAliveTime(TimeUnit.MILLISECONDS);
454             for (;;) {
455                 try {
456                     long waitTime = deadline - currentTime;
457                     if (waitTime <= 0) {
458                         break;
459                     }
460 
461                     try {
462                         task = getQueue().poll(waitTime, TimeUnit.MILLISECONDS);
463                         break;
464                     } finally {
465                         if (task == null) {
466                             currentTime = System.currentTimeMillis();
467                         }
468                     }
469                 } catch (InterruptedException e) {
470                     // Ignore.
471                     continue;
472                 }
473             }
474             return task;
475         }
476 
477         private void runTask(Runnable task) {
478             beforeExecute(thread, task);
479             boolean ran = false;
480             try {
481                 task.run();
482                 ran = true;
483                 afterExecute(task, null);
484                 completedTaskCount ++;
485             } catch (RuntimeException e) {
486                 if (!ran) {
487                     afterExecute(task, e);
488                 }
489                 throw e;
490             }
491         }
492     }
493 }