6

I have several workers, that use ArrayBlockingQueue.

Every worker takes one object from queue, process it, and in result can get several objects, that will be put into queue for further processing. So, worker = producer + consumer.

Worker:

public class Worker implements Runnable
{
    private BlockingQueue<String> processQueue = null;

    public Worker(BlockingQueue<String> processQueue)
    {
        this.processQueue = processQueue;
    }

    public void run()
    {
        try
        {
            do
            {
                String item = this.processQueue.take();
                ArrayList<String> resultItems = this.processItem(item);

                for(String resultItem : resultItems)
                {
                    this.processQueue.put(resultItem);
                }
            }
            while(true);
        }
        catch(Exception)
        {
            ...
        }
    }

    private ArrayList<String> processItem(String item) throws Exception
    {
        ...
    }
}

Main:

public class Test
{
    public static void main(String[] args) throws Exception
    {
        new Test().run();
    }

    private void run() throws Exception
    {
        BlockingQueue<String> processQueue = new ArrayBlockingQueue<>(10000);
        processQueue.put("lalala");

        Executor service = Executors.newFixedThreadPool(100);
        for(int i=0; i<100; ++i)
        {
            service.execute(new Worker(processQueue));
        }
    }
}

Whats is the best way to stop workers, when there is no more work ?

First, what I have in mind, is to check periodically how many items in queue and how many items are currently in process. If both are equal to zero, then do something like "shutdownNow()" on ExecutorService. But I am not sure this is the best way.

Oleg Golovanov
  • 905
  • 1
  • 14
  • 24
  • More interesting question is what will be your protection against cyclic patterns, obviously the queue may reproduce itself under some circumstances. – andbi Apr 02 '12 at 21:23
  • Could you please clarify the use case? Do you want the app to terminate itself when the queue is empty or that when a request is made to the app to shutdown, it will do so only after the queue is empty? – Handerson Apr 02 '12 at 22:27
  • @Osw, what do you mean with "cyclic pattern" here? – Oleg Golovanov Apr 03 '12 at 12:37
  • @Handerson, yes, i want to terminate app itself when there is no more work. – Oleg Golovanov Apr 03 '12 at 12:39
  • @OlegGolovanov If you could post a simple example (with the producer, consumer, executor) of what you are trying to achieve it might be easier to understand what your problem is. – assylias Apr 03 '12 at 14:26

3 Answers3

2

If there's no more work to do, put a message into the queue saying so and have the workers shut themselves down at their own convenience. This is a good way to prevent data corruption.

If you need to notify another thread that all the workers have gone home, you can use a CountDownLatch to do so.

mre
  • 43,520
  • 33
  • 120
  • 170
  • 1
    I can not use "Poison Pill" cause i don't have one producer, that knows, when he has no more work to produce. About CountDownLatch: in order to do so, every worker has to know, when there is no more work :) – Oleg Golovanov Apr 03 '12 at 12:46
1

Sounds like you have your solution--use a separate in-progress queue, the size of which will be the number of items currently being processed. If you use the convention that accesses to either queue is in synchronized(theArrayBlockingQueue) blocks then all should be well. In particular, when moving an item to the processing state, remove it from theArrayBlockingQueue and add it to the processingQueue within the same synchronized block.

karmakaze
  • 34,689
  • 1
  • 30
  • 32
  • Thanks for answer :) Do i really need "processingQueue", or AtomicInteger ( representing, how many items are currently in process ) would be enough ? May be i don't know something. – Oleg Golovanov Apr 03 '12 at 12:56
  • I like having 'state' represented in data-structures rather than in local variables of threads. For instance, I could as a controlling thread, synchronize on theArrayBlockingQueue, kill all the workers, return the items from the work-in-progress queue back to theArrayBlockingQueue and later restart the workers. Much harder to do if you have to communicate with threads to recover their state. – karmakaze Apr 10 '12 at 20:31
0

I have slightly amended your code, not sure if that's what you expect, but at least it terminates! If you use shutdownNow instead of shutdown your workers will be interrupted and unless you put them back to work, will exit with no guarantee that the queue is empty.

public class Test {

    public static void main(String[] args) throws Exception {
        new Test().run();
    }

    private void run() throws Exception {
        BlockingQueue<String> processQueue = new ArrayBlockingQueue<>(10000);
        processQueue.put("lalalalalalalalalalalalala"); //a little longer to make sure there is enough to process

        ExecutorService service = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; ++i) {
            service.execute(new Worker(processQueue));
        }
        service.shutdown(); //orderly shutdown = lets the tasks terminate what they are doing
        service.awaitTermination(1, TimeUnit.SECONDS); //blocks until all tasks have finished or throws TimeOutException if timeout is reached
    }

    public static class Worker implements Runnable {

        private BlockingQueue<String> processQueue = null;
        private int count = 0;

        public Worker(BlockingQueue<String> processQueue) {
            this.processQueue = processQueue;
        }

        @Override
        public void run() {
            try {
                do {
                    //tries to get something from the queue for 100ms and returns null if it could not get anything
                    String item = this.processQueue.poll(100, TimeUnit.MILLISECONDS);
                    if (item == null) break; //Ends the job because the queue was empty
                    count++;
                    List<String> resultItems = this.processItem(item);

                    for (String resultItem : resultItems) {
                        this.processQueue.put(resultItem);
                    }
                } while (true);
            } catch (InterruptedException e) {
                System.out.println("Interrupted");
                Thread.currentThread().interrupt();
            }
            if (count != 0) System.out.println(Thread.currentThread() + ": processed " + count + " entries");
        }

        private List<String> processItem(String item) { //let's put the string back less final character
            if (item.isEmpty()) {
                return Collections.<String> emptyList();
            } else {
                return Arrays.asList(item.substring(0, item.length() - 1));
            }
        }
    }
}
assylias
  • 321,522
  • 82
  • 660
  • 783