0

I made a producer-consumer program. It's just a program in core java without any GUI(Swing or SWT). It has one producer who put objects into the queue. Also there is a few consumers who must add some staff(for example String) into Every object in that shared queue. So, every consumer must handle every object in a shared queue. In this case - every BookShelf must have items from All consumers in "books" ArrayList. consumers.

Question: What condition should I use in consumers to finish their threads correctly? Here are the code fragments of the program. Maybe I implemented it in wrong way.

Here is an object for the queue:

public class BookShelf {
private int id;
private String name;
private int height;
private int weigh;
List<String> books = Collections.synchronizedList(new ArrayList<String>());

public BookShelf(int id, String name) {
    this.id = id;
    this.name = name;
}
public void addBook(String book) {
  books.add(book);
}
public boolean eq(String book) {
synchronized (books) {
    for (String b: books) {
    if (b.equalsIgnoreCase(book)) {
        return true;
    }
    }
}
return false;
}
 other setters and getters..

}

Here is the producer class:

public class Producer implements Runnable {
private BlockingQueue myQueue;

public Producer(BlockingQueue myQueue) {
this.myQueue = myQueue;
}

public void run() {
for(int i=0; i<7; i++){
    try {
    System.out.println("Produced: " + i);
    BookShelf myBookShelf = new BookShelf(i, "book #" + i);
    myQueue.put(myBookShelf);
    } catch (InterruptedException ex) {
    //Proper handle
    }
}
}

}

Here is one of consumers class:

 public class Consumer implements Runnable {
 private BlockingQueue myQueue;

public Consumer(BlockingQueue myQueue) {
    this.myQueue = myQueue; }

public void run() {
    while(true){
        try {
            BookShelf tempBookShelf = (BookShelf) myQueue.take();

            //eq() is my method to check if ArraList has a book.
            if (tempBookShelf.eq("Abc book")) {
                System.out.println("It already has book");
                myQueue.put(tempBookShelf);
                Thread.sleep(2000);
            } else {
                tempBookShelf.addBook("Abc book");
                myQueue.put(tempBookShelf);
                Thread.sleep(2000);
            }
        } catch (InterruptedException ex) {
            //Proper handle
        }
    }
}
}

Here is main class:

public class ProducerConsumerTest {

public static void main(String[] args) {

     BlockingQueue sharedQueue = new LinkedBlockingQueue();
     Thread prodThread = new Thread(new Producer(sharedQueue));
     Thread consThread = new Thread(new Consumer(sharedQueue));
     Thread consThread2 = new Thread(new Consumer2(sharedQueue));

     prodThread.start();
     consThread.start();
     consThread2.start();
}
 }
wnbc
  • 1
  • 2
  • try the [disruptor](https://github.com/LMAX-Exchange/disruptor) which does that pretty well. – Timmy Apr 13 '13 at 08:24
  • 1
    I'm having compilation errors when I pasted it on Eclipse. Please put the real code (like where is Consumer2?) – dierre Apr 13 '13 at 08:30
  • It's just a program in core java without any GUI(Swing or SWT). Consumer2 is just a copy of Consumer. – wnbc Apr 13 '13 at 08:42
  • Disruptor seems to be a good solution. Unfortunately I must do this program in plain java without any third-party frameworks. – wnbc Apr 13 '13 at 08:46
  • Is there a `Consumer2` class at all? Or did you intend for it to simply be a second instance of `Consumer`. – Tim Bender Apr 13 '13 at 10:14
  • No, it's not an instance of one class. There are two classes. – wnbc Apr 13 '13 at 10:23
  • @wnbc it's not only that. Inside the consumer you are using method not declared in the BookShelf code (like eq). Again, post the functioning code, then we can help you. – dierre Apr 13 '13 at 10:47
  • Ok. I added eq() method in BookShelf class. – wnbc Apr 13 '13 at 11:12

4 Answers4

1

Register each consumer with the producer. Each consumer has its own queue and the producer puts the object into all the queues. Each consumer then process on the same instance of the object.

    public interface Consumer{
        public void process(BookShelf bs);
    }

    public class Producer implements Runnable{
        private final List<Consumer> consumers = new CopyOnWriteArrayList<Consumer>(); // thread safe but not efficient with lots of changes

        public void register(Consumer c){
            consumers.add(c); // thread safe
        }

        public void run(){
            for(;;){
                BookShelf bs = generateBookShelfByWhateverMeans();
                for (Consumer c : consumers){
                    c.process(bs);
                }
            }
        }
    }

    public class BookShelfConsumer implements Runnable, Consumer{
        private final BlockingQueue<BookShelf> queue = new LinkedTransferQueue<BookShelf>(); // unbounded & thread safe

        public void process(BookShelf bs){
            queue.offer(bs); // non-blocking
        }

        public void run(){
            for(;;){
                BookShelf bs = queue.take(); // blocks until got object or interrupted 
                // catch InterruptedException 
                // do whatever this consumer is supposed to do with the object
            }
        }
    }
Timmy
  • 248
  • 1
  • 9
  • Thank you for your answer. But It's not exactly what I need. Yes, consumers deal with every object in the queue. But every consumer has its own queue. What I need is that every BookShelf has in "books ArrayList" items from All consumers (not one as in your example) – wnbc Apr 13 '13 at 11:57
  • I have to say that I agree with @Timmy. If you need every consumer to operate on every object the producer will keep the main queue and every consumer will work on its own queue with the same reference of the object (you can use that reference as a semaphore so that every consumer can edit the object safely) – dierre Apr 13 '13 at 15:14
0

I would try using SwingWorker instead. It has a done() method that is executed when it's finished. See this page for some code examples.

If it's not Swing you are using, there is a similar function in Swt called Jobs. Check this page for examples. It also has a done() method being executed when the job is done.

Omid
  • 823
  • 1
  • 11
  • 31
0

Also there is a few(N number) consumers who must add some staff(for example String) into Every object in that shared queue

I assume you mean every consumer must add their thing to every object which ever enters the queue. In that case, this is not a producer-consumer problem, this is more like an observer-observable problem. Basically, when a new BookShelf is created, that is the Observable. All of the Observers should be notified about the BookShelf and given the opportunity to add their own Book.

Tim Bender
  • 20,112
  • 2
  • 49
  • 58
  • Thank you for the advice. But in a description of this task there is info that it must be a kind of producer-consumer pattern. And if I implement it like Observer pattern it will be wrong ;( – wnbc Apr 13 '13 at 10:05
  • Welp, if your so called consumers are shoving stuff onto the same queue then they are in fact behaving like producers, right? Honestly, you're probably best served by clarifying your question. – Tim Bender Apr 13 '13 at 10:17
0

I recommend using a ConcurrentLinkedQueue in Bookshelf instead of a synchronized list - it's lock free (doesn't need to be synchronized) and will probably be more efficient.

To end your consumers, change their while(true) loops to while(!cancel) loops. Give each consumer a cancel boolean as an instance variable that initializes to false, and give them a cancel() method that sets cancel to true. Call cancel() on your consumers when you're done with them. If you will always be canceling all of your consumers at once (instead of selectively canceling some but not others), then you can use a static cancel instead of an instance cancel.

Zim-Zam O'Pootertoot
  • 17,888
  • 4
  • 41
  • 69
  • This is good idea! Can anyone advice me what condition should I use to call cancel() ?? If I check it like `if (sharedQueue.size() == 0) { setCancel(); }` It doesn't work correctly, because at time when one consumer check the size of the queue, other consumers can take and then put objects in queue. But this object won't be handled because this consumer is over due to cancel() method. – wnbc Apr 13 '13 at 16:04
  • The producer should call `cancel()` when its run method terminates. If this is how you implement it, then the consumers' while loops should be `while(!cancel && !queue.isEmpty())` so that you finish consuming the queue before canceling the consumers. Use `!isEmpty()` and not `!size() == 0` because some queue implementations use a linear-time `size()` method – Zim-Zam O'Pootertoot Apr 13 '13 at 16:14