001/*
002 * $Id: ThreadPool.java 5495 2016-04-24 21:27:50Z kredel $
003 */
004
005// package edu.unima.ky.parallel;
006package edu.jas.util;
007
008
009import java.util.LinkedList;
010
011import org.apache.log4j.Logger;
012
013import edu.jas.kern.PreemptingException;
014
015
016/**
017 * Thread pool using stack / list workpile.
018 * @author Akitoshi Yoshida
019 * @author Heinz Kredel
020 */
021
022public class ThreadPool {
023
024
025    /**
026     * Default number of threads to use.
027     */
028    static final int DEFAULT_SIZE = 3;
029
030
031    /**
032     * Number of threads to use.
033     */
034    final int size;
035
036
037    /**
038     * Array of workers.
039     */
040    protected PoolThread[] workers;
041
042
043    /**
044     * Number of idle workers.
045     */
046    protected int idleworkers = 0;
047
048
049    /**
050     * Shutdown request.
051     */
052    protected volatile boolean shutdown = false;
053
054
055    /**
056     * Work queue / stack.
057     */
058    // should be expressed using strategy pattern
059    // List or Collection is not appropriate
060    // LIFO strategy for recursion
061    protected LinkedList<Runnable> jobstack; // FIFO strategy for GB
062
063
064    protected StrategyEnumeration strategy = StrategyEnumeration.LIFO;
065
066
067    private static final Logger logger = Logger.getLogger(ThreadPool.class);
068
069
070    private static final boolean debug = logger.isDebugEnabled();
071
072
073    /**
074     * Constructs a new ThreadPool with strategy StrategyEnumeration.FIFO and
075     * size DEFAULT_SIZE.
076     */
077    public ThreadPool() {
078        this(StrategyEnumeration.FIFO, DEFAULT_SIZE);
079    }
080
081
082    /**
083     * Constructs a new ThreadPool with size DEFAULT_SIZE.
084     * @param strategy for job processing.
085     */
086    public ThreadPool(StrategyEnumeration strategy) {
087        this(strategy, DEFAULT_SIZE);
088    }
089
090
091    /**
092     * Constructs a new ThreadPool with strategy StrategyEnumeration.FIFO.
093     * @param size of the pool.
094     */
095    public ThreadPool(int size) {
096        this(StrategyEnumeration.FIFO, size);
097    }
098
099
100    /**
101     * Constructs a new ThreadPool.
102     * @param strategy for job processing.
103     * @param size of the pool.
104     */
105    public ThreadPool(StrategyEnumeration strategy, int size) {
106        this.size = size;
107        this.strategy = strategy;
108        jobstack = new LinkedList<Runnable>(); // ok for all strategies ?
109        workers = new PoolThread[0];
110    }
111
112
113    /**
114     * thread initialization and start.
115     */
116    public void init() {
117        if (workers == null || workers.length == 0) {
118            workers = new PoolThread[size];
119            for (int i = 0; i < workers.length; i++) {
120                workers[i] = new PoolThread(this);
121                workers[i].start();
122            }
123            logger.info("size = " + size + ", strategy = " + strategy);
124        }
125        if (debug) {
126            Thread.dumpStack();
127        }
128    }
129
130
131    /**
132     * toString.
133     */
134    @Override
135    public String toString() {
136        return "ThreadPool( size=" + getNumber() + ", idle=" + idleworkers + ", " + getStrategy() + ", jobs="
137                        + jobstack.size() + ")";
138    }
139
140
141    /**
142     * number of worker threads.
143     */
144    public int getNumber() {
145        return size;
146        //if (workers == null || workers.length < size) {
147        //    init(); // start threads
148        //}
149        //return workers.length; // not null
150    }
151
152
153    /**
154     * get used strategy.
155     */
156    public StrategyEnumeration getStrategy() {
157        return strategy;
158    }
159
160
161    /**
162     * Terminates the threads.
163     */
164    public void terminate() {
165        while (hasJobs()) {
166            try {
167                Thread.sleep(100);
168                //logger.info("waiting for termination in " + this);
169            } catch (InterruptedException e) {
170                Thread.currentThread().interrupt();
171            }
172        }
173        if (workers == null) {
174            return;
175        }
176        for (int i = 0; i < workers.length; i++) {
177            if (workers[i] == null) {
178                continue;
179            }
180            try {
181                while (workers[i].isAlive()) {
182                    workers[i].interrupt();
183                    workers[i].join(100);
184                }
185            } catch (InterruptedException e) {
186                Thread.currentThread().interrupt();
187            }
188        }
189    }
190
191
192    /**
193     * Cancels the threads.
194     */
195    public int cancel() {
196        shutdown = true;
197        int s = jobstack.size();
198        if (hasJobs()) {
199            synchronized (this) {
200                logger.info("jobs canceled: " + jobstack);
201                jobstack.clear();
202                notifyAll(); // for getJob
203            }
204        }
205        //int re = 0;
206        if (workers == null) {
207            return s;
208        }
209        for (int i = 0; i < workers.length; i++) {
210            if (workers[i] == null) {
211                continue;
212            }
213            try {
214                while (workers[i].isAlive()) {
215                    synchronized (this) {
216                        shutdown = true;
217                        notifyAll(); // for getJob
218                        workers[i].interrupt();
219                    }
220                    //re++;
221                    //if ( re > 3 * workers.length ) {
222                    //    logger.info("give up on: " + workers[i]);
223                    //    break; // give up
224                    //}
225                    workers[i].join(100);
226                }
227            } catch (InterruptedException e) {
228                Thread.currentThread().interrupt();
229            }
230        }
231        return s;
232    }
233
234
235    /**
236     * adds a job to the workpile.
237     * @param job
238     */
239    public synchronized void addJob(Runnable job) {
240        if (workers == null || workers.length < size) {
241            init(); // start threads
242        }
243        jobstack.addLast(job);
244        logger.debug("adding job");
245        if (idleworkers > 0) {
246            logger.debug("notifying a jobless worker");
247            notifyAll();
248        }
249    }
250
251
252    /**
253     * get a job for processing.
254     */
255    protected synchronized Runnable getJob() throws InterruptedException {
256        while (jobstack.isEmpty()) {
257            idleworkers++;
258            logger.debug("waiting");
259            wait(1000);
260            idleworkers--;
261            if (shutdown) {
262                throw new InterruptedException("shutdown in getJob");
263            }
264        }
265        // is expressed using strategy enumeration
266        if (strategy == StrategyEnumeration.LIFO) {
267            return jobstack.removeLast(); // LIFO
268        }
269        return jobstack.removeFirst(); // FIFO
270    }
271
272
273    /**
274     * check if there are jobs for processing.
275     */
276    public boolean hasJobs() {
277        if (jobstack.size() > 0) {
278            return true;
279        }
280        for (int i = 0; i < workers.length; i++) {
281            if (workers[i] == null) {
282                continue;
283            }
284            if (workers[i].isWorking) {
285                return true;
286            }
287        }
288        return false;
289    }
290
291
292    /**
293     * check if there are more than n jobs for processing.
294     * @param n Integer
295     * @return true, if there are possibly more than n jobs.
296     */
297    public boolean hasJobs(int n) {
298        int j = jobstack.size();
299        if (j > 0 && (j + workers.length > n)) {
300            return true;
301        }
302        // if j > 0 no worker should be idle
303        // ( ( j > 0 && ( j+workers.length > n ) ) || ( j > n )
304        int x = 0;
305        for (int i = 0; i < workers.length; i++) {
306            if (workers[i] == null) {
307                continue;
308            }
309            if (workers[i].isWorking) {
310                x++;
311            }
312        }
313        if ((j + x) > n) {
314            return true;
315        }
316        return false;
317    }
318
319}
320
321
322/**
323 * Implements one Thread of the pool.
324 */
325class PoolThread extends Thread {
326
327
328    ThreadPool pool;
329
330
331    private static final Logger logger = Logger.getLogger(PoolThread.class);
332
333
334    private static final boolean debug = logger.isDebugEnabled();
335
336
337    volatile boolean isWorking = false;
338
339
340    /**
341     * @param pool ThreadPool.
342     */
343    public PoolThread(ThreadPool pool) {
344        this.pool = pool;
345    }
346
347
348    /**
349     * Run the thread.
350     */
351    @Override
352    public void run() {
353        logger.info("ready");
354        Runnable job;
355        int done = 0;
356        long time = 0;
357        long t;
358        boolean running = true;
359        while (running) {
360            try {
361                logger.debug("looking for a job");
362                job = pool.getJob();
363                if (job == null) {
364                    break;
365                }
366                if (debug) {
367                    logger.info("working");
368                }
369                t = System.currentTimeMillis();
370                isWorking = true;
371                job.run();
372                isWorking = false;
373                time += System.currentTimeMillis() - t;
374                done++;
375                if (debug) {
376                    logger.info("done");
377                }
378                if (Thread.currentThread().isInterrupted()) {
379                    running = false;
380                    isWorking = false;
381                    //throw new RuntimeException("interrupt in while(running) loop");
382                }
383            } catch (InterruptedException e) {
384                Thread.currentThread().interrupt();
385                running = false;
386                isWorking = false;
387            } catch (PreemptingException e) {
388                logger.debug("catched " + e);
389                //e.printStackTrace();
390            } catch (RuntimeException e) {
391                logger.warn("catched " + e);
392                e.printStackTrace();
393            }
394        }
395        isWorking = false;
396        logger.info("terminated, done " + done + " jobs in " + time + " milliseconds");
397    }
398
399}