Семафор в Java.проблема производитель-потребитель - PullRequest
0 голосов
/ 24 февраля 2019

Я тестирую использование семафоров с типичной проблемой производитель-потребитель, когда у меня есть только один производитель и один потребитель.Производитель добавляет товары по одному, а потребитель может снять несколько товаров одновременно.

Для проведения теста производитель и потребитель хранят и удаляют числа из массива из 10 элементов, где 0 обозначает отсутствие товаров.и любой другой номер представляет продукт.Доступ к хранению и извлечению элементов централизован в классе Data.Я использую мьютекс, чтобы упорядоченно использовать вектор в случае, если у нас одновременно работает более одного потока.

При выполнении этого я замечаю, что количество разрешений не является правильным в соответствии с операциями, выполняемымипотоки.Приложение показывает ошибку, потому что семафор производителя говорит, что у него есть разрешение, но вектор данных заполнен.

package producer.consumer;

import java.io.IOException;

public class ProducerConsumer {

    public static void main(String[] args) throws IOException {
        final int MAX = 10;

        Data data = new Data(MAX);
        Consumer consumer = new Consumer(data);
        Producer producer = new Producer(data);

        consumer.start();
        producer.start();
    }   
}

package producer.consumer;

public class Producer extends Thread{
    private final Data data;

    public Producer(Data data) {
         this.data = data;
    }

    @Override
    public void run() {
        while (true) {
            try {
                data.add((int) (Math.random() * data.getLength()) + 1);
            } catch (InterruptedException ex) {
                System.out.println(ex.getMessage());
            }
        }
    }  
}

package producer.consumer;

import java.util.logging.Level;
import java.util.logging.Logger;

public class Consumer extends Thread{
    private final Data data;

    public Consumer(Data data) {
        this.data = data;
    }

    @Override
    public void run() {
        while (true) {
            try {
                data.remove((int) (Math.random() * data.getLength()) + 1);
            } catch (InterruptedException ex) {
               Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

package producer.consumer;

import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.Semaphore;

public class Data {
    private final int[] data;
    private final Semaphore mutex = new Semaphore(1);
    private final Semaphore semProducer, semConsumer;

    public Data(int MAX) throws IOException {
        data = new int[MAX];
        semProducer = new Semaphore(MAX);
        semConsumer = new Semaphore(0);
    }

    public int getLength() {
        return data.length;
    }

    public void add(int number) throws InterruptedException {

        semProducer.acquire();

        mutex.acquire();
        System.out.println("trying to add a product");

        int i = 0;
        while (data[i] != 0) {
            i++;
        }
        data[i] = number;

        int permits = semConsumer.availablePermits() + 1;

        System.out.println("data added in " + i + " " + Arrays.toString(data)
            + " Resources consumer " + permits
            + " Resources producer  " + semProducer.availablePermits());
        mutex.release();

        semConsumer.release();
    }

    public void remove(int numberElements) throws InterruptedException {

        semConsumer.acquire(numberElements);

        mutex.acquire();
        System.out.println("trying to withdraw " + numberElements);

        for (int i = 0; i < numberElements; i++) {
            if (data[i] != 0) {
                data[i] = 0;
            }
        }

        int permisos = semProducer.availablePermits() + 1;
        System.out.println(" Retired " + numberElements + " " + Arrays.toString(data)
            + " Resources consumer " + semConsumer.availablePermits()
            + " Resources producer " + permisos);
        mutex.release();

        semProducer.release(numberElements);
    }
}

Большое спасибо за помощь.

Ответы [ 2 ]

0 голосов
/ 24 февраля 2019

РЕДАКТИРОВАТЬ Получение и выдача разрешений кажется правильным; но вам нужно убедиться, что потребитель действительно очистит правильное количество элементов.

Например, отредактируйте класс Data с помощью

public void remove(int numberElements) throws InterruptedException {
    semConsumer.acquire(numberElements);
    mutex.acquire();
    System.out.println("remove: num-elem=" + numberElements);

    int consumed=0;
    for (int i = 0; consumed<numberElements; i++) {
        if (data[i] != 0) {
            data[i] = 0;
            consumed++;
        }
    }
    System.out.println(
            " Retired " + numberElements + " " + Arrays.toString(data)  );
    mutex.release();
    semProducer.release(numberElements);
}

Обратите также внимание, что эта реализация не очень эффективна (вам нужно будет выполнять итерацию по всему массиву как при вставке, так и при удалении элементов, что может быть дорогостоящим, когда MAX велико ..)

0 голосов
/ 24 февраля 2019

Ваш потребитель не всегда потребляет то, что, как он утверждает, потребляет.

    for (int i = 0; i < numberElements; i++) {
        if (data[i] != 0) {
            data[i] = 0;
        }
    }

Предположим, что numberElements равно 3, и что у нас есть ровно 3 доступных элемента в данных [7], данных [8], данных [9].

Цикл завершается с i == 3, ничего не удалено, но семафор производителя все равно будет увеличен на 3.

В потребителе, если вы используете iкак индекс массива, он должен охватывать весь массив, и вам нужен отдельный счетчик для «удаленных элементов».

Это не тот случай, когда доступные элементы всегда будут находиться в слотах данных с наименьшим номером, дажехотя производитель заполняет их в первую очередь.Рассмотрим временную последовательность, в которой производителю удается произвести, по крайней мере, 5 элементов, затем потребитель бежит, чтобы потреблять 2, а затем немедленно запускает снова, чтобы потреблять 3, прежде чем они будут произведены.data [0] и data [1] будут пустыми при втором запуске потребителя, и мы столкнемся с описанным мною сценарием.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...