Multi threaded Producer Consumer with bounded buffer I've developed the following multi-threaded code and would like a review. It is supposed to be implementing the Producer-consumer problem as defined here. 

I have multiple consumers and one producer. I would like to know whether there are any problems with my use of concurrency and also if you have any suggestions on how to better implement the solution. I don't have a lot of experience with concurrent programming and am trying to improve.

package consumerproducer;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;


public class ConsumerProducer1 {

    private static int QUEUE_SIZE = 10;

    private Deque&lt;Integer&gt; queue = new ArrayDeque&lt;Integer&gt;(QUEUE_SIZE);


    public synchronized boolean addWorkItem(Integer i) {
        if (queue.size() &lt; QUEUE_SIZE) {
            queue.addLast(i);
            return true;
        } else {
            return false;
        }
    }

    public synchronized Integer getWork() {
        if (queue.size() &gt; 0) {
            return queue.removeFirst();
        } else {
            return null;
        }
    }

    public static void main(String[] args) {
        new ConsumerProducer1().go();
    }

    public void go() {
        ConsumerThread ct = new ConsumerThread();
        ct.start();
        ConsumerThread ct2 = new ConsumerThread();
        ct2.start();
        ProducerThread pt = new ProducerThread();
        pt.start();
    }

    class ConsumerThread extends Thread {
        public void run() {
            while(true) {
                Integer work = getWork();
                if (work == null) {
                    Thread.yield();
                } else {
                    System.out.println("Thread: " + this.getId() + " received work: " + work);
                }
            }
        }
    }

    class ProducerThread extends Thread {
        private List&lt;Integer&gt; retryList = new ArrayList&lt;Integer&gt;();
        public void run() {
            while(true) {
                Integer currWork;
                if (retryList.size() == 0) {
                    currWork = (int) (Math.random() * 100);
                    System.out.println("Thread: " + this.getId() + " generated new work: " + currWork);
                } else {
                    currWork = retryList.remove(0);
                    System.out.println("Thread: " + this.getId() + " retrying old work: " + currWork);
                }
                if (!addWorkItem(currWork)) {
                    System.out.println("Thread: " + this.getId() + " could not add work (because buffer is probably full): " + currWork);
                    retryList.add(currWork);
                    Thread.yield();
                } else {
                    System.out.println("Thread: " + this.getId() + " added work to queue: " + currWork);
                }
            }
        }
    }
}
Suggest adding final to the QUEUE_SIZE constant and the collection instance also.

synchronized usage for protecting the ArrayDeque collection from concurrent access, but ArrayDeque is not thread safe. Perhaps two threads, one calls getWork() and another the addWorkItem(Integer i). Suggest using a thread safe collection instead of ArrayDeque.

Multiple threads may not always have access to System.out. Instead can use a concurrent logger or other mechanism to capture output, or have each thread write to a thread safe collection as an instance in ConsumerProducer1. Another option is having a Callable that returns desired output as a String, with the main routine the only thread accessing System.out and echoing that result.