2

As I said in title, I'm trying to make Producer-Consumer case with the usage of JavaFx. For the start I will show what the problem is:

Basically I got TabPane with three tabs: enter image description here

  • "Main Menu" with Buttons to "start production", "stop production" and two TextAreas each for showing results of producers-consumers work (there will be 2 for each branch) enter image description here
  • "First Producer" for typing producer name and slider for time of production (basically value for Thread.sleep(long m))
  • "Second Producer" is exactly like the first So, now to the matter itself. After "accepting" data of both producers and starting production-consumption I got stuck on a problem with settingText to both TextAreas of producers and consumers. I tried binding them to tasks, but that didn't work out (maybe I did something wrong, I don't know).

Here is my code also:

Controller.java:

package com.example.case4javafx;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;

import java.net.URL;
import java.util.LinkedList;
import java.util.ResourceBundle;

public class Controller implements Initializable {

    private LinkedList<String> listOfProducts = new LinkedList<>();

    @FXML
    private Slider sldFirstProd, sldSecondProd;

    int mSldFirstProdValue, mSldSecondProdValue;

    @FXML
    private TextField txtFirstProd, txtSecondProd;

    @FXML
    private static TextArea txtProdInfo, txtConsInfo;

    private String nameOfProd1, nameOfProd2;
    private int timeOfProd1, timeOfProd2;

    private Producer producer1, producer2;
    private Consumer consumer1, consumer2;

    @FXML
    private Button btnStartProduction, btnStopProduction, btnFirstProdClear, btnFirstProdAccept, btnSecondProdClear, btnSecondProdAccept;

    @FXML
    private Label lblSldFirstProd, lblSldSecondProd;




    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        sldFirstProd.valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observableValue, Number number, Number t1) {
                mSldFirstProdValue = (int) sldFirstProd.getValue();
                lblSldFirstProd.setText(mSldFirstProdValue + " ms");
            }
        });


        sldSecondProd.valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observableValue, Number number, Number t1) {
                mSldSecondProdValue = (int) sldSecondProd.getValue();
                lblSldSecondProd.setText(mSldSecondProdValue + " ms");
            }
        });
    }

    public void onStartProduction(ActionEvent e) throws InterruptedException {
        if(btnFirstProdAccept.isDisabled() && btnSecondProdAccept.isDisabled()) {
            producer1 = new Producer(listOfProducts, nameOfProd1, timeOfProd1);
            Thread t1 = new Thread(producer1);

            consumer1 = new Consumer(listOfProducts);
            Thread t2 = new Thread(consumer1);


            producer2 = new Producer(listOfProducts, nameOfProd1, timeOfProd1);
            Thread t3 = new Thread(producer2);

            consumer2 = new Consumer(listOfProducts);
            Thread t4 = new Thread(consumer2);

            t1.setDaemon(true);
            t2.setDaemon(true);
            t3.setDaemon(true);
            t4.setDaemon(true);

            t1.start();
            t2.start();
            t3.start();
            t4.start();

            btnStartProduction.setDisable(true);
        }

    }

    public void onStopProduction(ActionEvent e) {
        if(producer1 != null) {
            producer1.cancel();
        }
        if(producer2 != null) {
            producer2.cancel();
        }
        if(consumer1 != null) {
            consumer1.cancel();
        }
        if(consumer2 != null) {
            consumer2.cancel();
        }
        setEnableDisableProdData(txtFirstProd, sldFirstProd, true);
        setEnableDisableProdData(txtSecondProd, sldSecondProd, true);
        setEnableDisableProdButt(btnFirstProdClear, btnFirstProdAccept, true);
        setEnableDisableProdButt(btnSecondProdClear, btnSecondProdAccept, true);
        btnStartProduction.setDisable(false);
    }

    public void onClearFirstProducer(ActionEvent e) {
        setClearProd(txtFirstProd, sldFirstProd, lblSldFirstProd);
    }

    public void onAcceptFirstProducer(ActionEvent e) {
        nameOfProd1 = txtFirstProd.getText();
        timeOfProd1 = mSldFirstProdValue;
        setEnableDisableProdButt(btnFirstProdClear, btnFirstProdAccept, false);
        setClearProd(txtFirstProd, sldFirstProd, lblSldFirstProd);
        setEnableDisableProdData(txtFirstProd, sldFirstProd, false);
    }

    public void onClearSecondProducer(ActionEvent e) {
        setClearProd(txtSecondProd, sldSecondProd, lblSldSecondProd);
    }

    public void onAcceptSecondProducer(ActionEvent e) {
        nameOfProd2 = txtSecondProd.getText();
        timeOfProd2 = mSldSecondProdValue;
        setEnableDisableProdButt(btnSecondProdClear, btnSecondProdAccept, false);
        setClearProd(txtSecondProd, sldSecondProd, lblSldSecondProd);
        setEnableDisableProdData(txtSecondProd, sldSecondProd, false);
    }

    public void setEnableDisableProdData(TextField txtProdName, Slider sldProd, boolean toActivDeactiv) {
        if(toActivDeactiv) {
            txtProdName.setDisable(false);
            sldProd.setDisable(false);
        } else {
            txtProdName.setDisable(true);
            sldProd.setDisable(true);
        }
    }

    public void setEnableDisableProdButt(Button clear, Button accept, boolean toActivDeactiv) {
        if(toActivDeactiv) {
            clear.setDisable(false);
            accept.setDisable(false);
        } else {
            clear.setDisable(true);
            accept.setDisable(true);
        }
    }

    public void setClearProd(TextField txtProdName, Slider sldProd, Label sldValueInfo) {
        txtProdName.setText("");
        sldProd.setValue(0);
        sldValueInfo.setText("500 ms");
    }

    public static void setTxtAreaOfProd(String input) {
        txtProdInfo.setText(input);
    }

    public static void setTxtAreaOfCons(String input) {
        txtConsInfo.setText(input);
    }

}

Producer.java:

package com.example.case4javafx;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

import java.util.LinkedList;
import java.util.List;

public class Producer extends Task<Void> {


    private final LinkedList<String> dataOfProducts;
    private final String nameOfProducer;
    private final int timeOfProduction;

    private final int capacity;

    private Object lock;

    public Producer(LinkedList<String> refToData, String nameOfProducer, int timeOfProduction, Object lock) {
        this.dataOfProducts = refToData;
        this.nameOfProducer = nameOfProducer;
        this.timeOfProduction = timeOfProduction;
        this.capacity = 10;
        this.lock = lock;
    }

    @Override
    protected Void call() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (lock) {
                System.out.println("I'm a producer");
                if (isCancelled()) {
                    updateMessage("Cancelled");
                    break;
                }
                while (dataOfProducts.size() == capacity) {
                    lock.wait();
                }
                updateMessage("Producer produced-" + value);

                // to insert the jobs in the list
                String itemToAdd = nameOfProducer + " " + Integer.toString(value++);
                dataOfProducts.add(itemToAdd);
                // notifies the consumer thread that
                // now it can start consuming
                lock.notify();

                // makes the working of program easier
                // to  understand
                try {
                    Thread.sleep(this.timeOfProduction);
                } catch (InterruptedException e) {
                    if (isCancelled()) {
                        updateMessage("Cancelled");
                        break;
                    }
                }
            }
        }
        return null;
    }
}

Consumer.java:

package com.example.case4javafx;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

public class Consumer extends Task<Void> {


    private final Object lock;
    private final LinkedList<String> dataOfProducts;

    public Consumer(LinkedList<String> dataOfProducts, Object lock) {
        this.dataOfProducts = dataOfProducts;
        this.lock = lock;
    }

    @Override
    protected Void call() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (lock) {
                System.out.println("I'm a consumer");
                if (isCancelled()) {
                    updateMessage("Cancelled");
                    break;
                }
                while (dataOfProducts.size() == 0) {
                    lock.wait();
                }

                // to insert the jobs in the list
                String resultOfProducer = dataOfProducts.removeFirst();
                // notifies the consumer thread that
                // now it can start consuming
                lock.notify();

                // makes the working of program easier
                // to  understand
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    if (isCancelled()) {
                        updateMessage("Cancelled");
                        break;
                    }
                }
            }
        }
        return null;
    }
}

Producer and Consumer classes are extending Task abstract class and both are implementing abstract method call().

If you know the answer to this problem ("sending" String data from Thread Producer / Consumer with assignment "mission" to both of this TextAreas accordingly) I would be very thankful.

Hubertius
  • 35
  • 1
  • 6
  • `synchronized(o)` only prevents multiple threads from synchronizing on the same object, `o` at the same time. Each of your `Producer` instances and each of your `Consumer` instances is a _different_ object. `this` is a different object in each thread. When the four threads each synchronize on a different object, that's the same as no synchronization at all. – Solomon Slow May 15 '22 at 20:47
  • Also, This is bad: `while(true){synchronized(o){...}}`.See [_this answer_](https://stackoverflow.com/a/54171548/801894) for the reason why. – Solomon Slow May 15 '22 at 20:51
  • Also, in your `Producer`, you call `notify()` with a comment, "notify consumer that now it can start consuming." But that's not what your `notify()` call does. It notifies `this`, and `this` refers to the Producer object, not the Consumer. Similar to your problem with `synchronized(this)`. In order for an `o.notify()` call to wake a thread that's sleeping in an `o.wait()` call, it has to be the same object, `o` in both threads. – Solomon Slow May 15 '22 at 20:56
  • @SolomonSlow Yeah, I forgot to mention earlier that I took pattern from this article: https://www.geeksforgeeks.org/producer-consumer-solution-using-threads-java/ In that situation there was only one static class, which had both produce() and consume() methods for each "acitivity" let's just say. – Hubertius May 15 '22 at 21:02
  • @SolomonSlow So, for case of replacing "synchronise": - i should implement class ReentrantLock - make lock at the start of the loop - after try with Thread.sleep() with unlock "lock" in finally block? What about wait() and notify? Do I really need to made one class for both behaviours of Producer and Consumer class? Is there no option to make Producer and Consumer "seperatable" let's just say? – Hubertius May 15 '22 at 21:21
  • See my answer, below. In it I used `Object lock=new Object()`, and I used `synchronized(lock)`, and `lock.wait()`, and `lock.notify()`; but you could do essentially the same thing using `Lock lock=new ReentrantLock()`, and using the equivalent method calls, and the outcome would be the same. – Solomon Slow May 15 '22 at 21:32
  • See the javadoc for [BlockingQueue](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/BlockingQueue.html). See [this example for using such a queue in JavaFX](https://stackoverflow.com/questions/24116858/most-efficient-way-to-log-messages-to-javafx-textarea-via-threads-with-simple-cu). The example shows a pattern used to solve a different problem, it is not an exact solution to your problem. There are numerous ways to implement your task. I just referenced one of those. – jewelsea May 15 '22 at 23:29
  • Further [advice](https://stackoverflow.com/questions/18530493/complex-concurrency-in-javafx-using-observablelists-and-properties-from-multipl), and [concurrency examples](https://gist.github.com/jewelsea/5072743), again not exact solutions to your problem. – jewelsea May 15 '22 at 23:34
  • I don’t understand why this question has two close votes. I don’t agree that it should be closed at this time. I guess the thought is that it should be broken down into a couple of smaller, more manageable and specific questions. For example how to implement the producer consumer as one question and how to wire that solution into JavaFX UI in another. Perhaps that is true too. – jewelsea May 15 '22 at 23:41

1 Answers1

3

So, for case of replacing "synchronise"

One solution to two of the problems that I mentioned in comments above, is to create an Object that is used only for synchronization, and pass a reference to the object in to the constructors of classes where it is needed:

I'm not really sure what your overall goal is, but it looks like you are trying to create two producer/consumer pairs of threads (I.e., consumer1 interacts only with producer1, and consumer2 interacts only with producer2.) If that's true, then you're going to need a lock1 and a lock2:

...
private Object lock1, lock2;
private Producer producer1, producer2;
private Consumer consumer1, consumer2;
...
public void onStartProduction(ActionEvent e) throws InterruptedException {
    if(btnFirstProdAccept.isDisabled() && btnSecondProdAccept.isDisabled()) {
        lock1 = new Object();
        producer1 = new Producer(listOfProducts, nameOfProd1, timeOfProd1, lock1);
        Thread t1 = new Thread(producer1);
        consumer1 = new Consumer(listOfProducts, lock1);
        Thread t2 = new Thread(consumer1);
        ...
    }
}

Then, in your Producer class, you can do something like this:

...
private final int capacity;
private final Object lock;

public Producer(LinkedList<String> refToData, String nameOfProducer, int timeOfProduction, Object lock) {
    ...
    this.lock = lock;
    }

Likewise, do the same thing in your Consumer class, and likewise do the same thing for lock2 in the controller.

And now, your producer1 and your consumer1 can use the same shared lock object by doing synchronized(lock), and lock.wait() and lock.notify(); and your producer2 and your consumer2 can have their own, distinct, shared lock object.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • It's even simpler. I want basically 2 producers + 2 consumers working on common data (in that case that will be reference to LinkedList). So because of that I think I'll need only only one lock? One lock object, which reference will be passed to 2 Producers and 2 Consumers constructors and using that mechanism for synchronise. – Hubertius May 15 '22 at 21:31
  • Re, "because of that I think I'll need only only one lock?" That sounds about right. Just create one `lock`, and pass it in to both producers and both consumers. – Solomon Slow May 15 '22 at 21:33
  • 1
    I pasted edited code higher. I did that with your advice of using lock object. Now on every task I'm making 10 iterations for each Producer / Consumer. After producer "filled totally" LinkedList up to value of 10 capacity thread "producer" is moving to "waiting" state and at that moment another "consumer" starts to run. It looks better and is definitely working better than before. I still have to challange problem with communication between thread of JavaFX application and my created threads for passing info to TextAreas, but it's nice to add additional corrections to program. :) – Hubertius May 15 '22 at 21:58