Thread Producer - Consumer-Problem

public class ProducerAndConsumer<T> {
    private boolean available = false;
    private Map<T, T> map;
    private T i;
    Semaphore mutex ;
    Semaphore notEmpty;

    public ProducerAndConsumer(int n, Semaphore mutex, Semaphore notEmpty) {
        map = new HashMap<>(n);
        this.mutex = mutex;
        this.notEmpty = notEmpty;
    }

    public void add(T key, T value) throws InterruptedException {
        try {
            mutex.acquire();
            map.put(key, value);
        } finally {
            notEmpty.release();
        }
    }

    public T remove(T key) throws InterruptedException {
        try {
            notEmpty.acquire();
            T elem = map.remove(key);
            if (elem == null) {
                System.out.println(key + ": " + elem + " is Null");
                throw new NullPointerException();
            } else {
                return elem;
            }
        }finally {
            mutex.release();
        }
    }

    public T getValue(T key) throws InterruptedException {
        T value = map.get(key);
        return value;
    }

}

class Producer extends Thread {
    static Random generator = new Random();
    private int number;
    ProducerAndConsumer producerAndConsumer;
    Random random;
    Semaphore mutex = new Semaphore(1);
    Semaphore notEmpty = new Semaphore(0);

    public Producer(int number, ProducerAndConsumer producerAndConsumer) {
        this.producerAndConsumer = producerAndConsumer;
        this.number = number;
        random = new Random();
    }

    @Override
    public void run() {
        
        for (int i = 0; i < 3; i++) {
            try {
                Thread.currentThread().sleep(Math.abs(generator.nextInt() % 1000));
            } catch (InterruptedException e) {
            }
            ;
            try {
                producerAndConsumer.add(i, random.nextInt());
                System.out.println("Producer: " + number + ": " + i + " produziert!");
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }
}

class Consume extends Thread {
    static Random generator = new Random();
    private int number;
    ProducerAndConsumer producerAndConsumer;
    Random random;
    Semaphore mutex = new Semaphore(1);
    Semaphore notEmpty = new Semaphore(0);
    public Consume(int number, ProducerAndConsumer producerAndConsumer) {
        this.producerAndConsumer = producerAndConsumer;
        this.number = number;
        random = new Random();
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.currentThread().sleep(Math.abs(generator.nextInt() % 1000));
            } catch (InterruptedException e) {
            }
            ;
            try {
                producerAndConsumer.remove(i);
                System.out.println("Producer: " + number + ": " + i + " konzumiert!");
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }
}

class Main2 {

    public static void main(String[] args) throws InterruptedException {
        Semaphore mutex = new Semaphore(1);
        Semaphore notEmpty = new Semaphore(0);
        ProducerAndConsumer<Integer> producerAndConsumer = new ProducerAndConsumer<>(10, mutex, notEmpty);
        Random random = new Random(10);

        for (int i = 0; i < 3; i++) {
            Producer producer = new Producer(i, producerAndConsumer);
            producer.start();
        }

        for (int i = 0; i < 3; i++) {
            Consume consume = new Consume(i, producerAndConsumer);
            consume.start();
        }
    }
}

Wenn ich ein Producer und ein Consumer habe, dann funktioniert aber wenn ich 3 Producer und 3 Consumer habe, wie in der Code, dann funktioniert nicht mehr.

Vielen Dank im Voraus

Es ist nicht ganz klar, was der Code machen soll.

Beim schnellen Überfliegen und kurzen Testen, ein wichtiges Detail: Immer, wenn man ein new Random(...) erstellt, sollte man dort eine feste Zahl als Argument übergeben - damit der Code reproduzierbar immer das gleiche macht. (Manchmal verursacht das gepostete Programm ja keinen Fehler…)

Ansonsten … es gibt drei Producer und drei Consumer. Wenn ich das richtig sehe, ist ein Problem, dass z.B. Consumer 1 versucht, die 0 aus der Map zu entfernen, wenn gerade gar keine drin ist. „Not empty“ ist ja kein hinreichendes Kriterium dafür…