Esercizio Thread - Produttore/Consumatore

di il
14 risposte

Esercizio Thread - Produttore/Consumatore

Salve a tutti,

Ho un esercizio da fare la cui traccia è la seguente:

"Un processo produttore genera in modo casuale (anche con ripetizioni) numeri da 1 a 10 (inclusi) e li memorizza in un buffer che può contenere un solo numero alla volta. Due processi consumatori concorrenti tentano di acquisire tali numeri soltanto dopo la loro produzione. Uno dei due consumatori tenta di acquisire solo numeri da 1 a 5, l'altro solo numeri che vanno da 6 a 10 ."

First of all: a quanto ho capito studiando teoria, Thread e Processo non sono sinonimi ma un processo è costituito da uno o più Thread che condividono risorse (e nel testo dell'esercizio credo mi venga richiesto di creare dei thread, non dei processi). Per favore confermatemi se ho capito bene o no.

Per quanto riguarda l'esercizio, lato operativo, prima di andare a vedere la soluzione vorrei spremermi le meningi un po'. Ho creato una classe Produttore dove ho messo un generatore di numeri, un oggetto buffer e un ciclo che "putta" (passatemi il termine) i numeri generati nel buffer.

Ora, se - stando a quello che ho capito - i due Consumatori devono poter accedere allo stesso buffer per prenderne il contenuto tramite il metodo get(), come faccio a indicare al programma che, per l'appunto, l'oggetto buffer deve essere sempre lo stesso (cioè quello creato nel momento in cui istanzio un oggetto Produttore)? Da quello che ho intravisto in una soluzione proposta, si va a creare un altro oggetto Buffer nella classe consumatore e, su questo, si evoca get()...ma così facendo dovrebbe trovarsi un buffer vuoto.

Non so se mi sono spiegato. Nel caso riformulo.

Se qualcuno fosse così gentile da rinfrescarmi la memoria, perché al momento ho una tabula rasa in testa.

Grazie

14 Risposte

  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    "Un processo produttore genera in modo casuale (anche con ripetizioni) numeri da 1 a 10 (inclusi) e li memorizza in un buffer che può contenere un solo numero alla volta. Due processi consumatori concorrenti tentano di acquisire tali numeri soltanto dopo la loro produzione. Uno dei due consumatori tenta di acquisire solo numeri da 1 a 5, l'altro solo numeri che vanno da 6 a 10 ."
    Il buffer pertanto ha lo spazio solo per 1 valore, insomma, NON è una "coda" (di più elementi) che sarebbe un'altra cosa.

    Si deduce che l'uso del buffer debba essere "esclusivo" tra produttore/consumatori. E c'è una cosa che non hai precisato ma è anche immaginabile: un consumatore deve andare in wait se non c'è un valore disponibile e un produttore deve andare in wait se c'è ancora un valore nel buffer che non è stato ancora letto da un consumatore.

    Frengo ha scritto:


    a quanto ho capito studiando teoria, Thread e Processo non sono sinonimi ma un processo è costituito da uno o più Thread che condividono risorse
    Corretto, il processo è la istanza di una applicazione, che definisce cose come lo spazio di indirizzamento del processo, gli "handle" aperti alle risorse (file, grafica ecc...) e altro.
    Un thread è un flusso di esecuzione che occupa una singola unità di elaborazione del sistema (i "core" nei moderni processori). E all'interno di un processo, più thread condividono lo stesso spazio di indirizzamento, le risorse, ecc...

    Frengo ha scritto:


    e nel testo dell'esercizio credo mi venga richiesto di creare dei thread, non dei processi). Per favore confermatemi se ho capito bene o no.
    Sì, devi creare dei thread, java.lang.Thread si intende.

    Frengo ha scritto:


    Per quanto riguarda l'esercizio, lato operativo, prima di andare a vedere la soluzione vorrei spremermi le meningi un po'. Ho creato una classe Produttore dove ho messo un generatore di numeri, un oggetto buffer e un ciclo che "putta" (passatemi il termine) i numeri generati nel buffer.
    Quello che però conta di più è come si gestisce la "sincronizzazione" tra thread. Si può fare in diversi modi: usando la sincronizzazione intrinseca degli oggetti (synchronized e poi la condition-queue ovvero i wait() e notify() degli oggetti) oppure con i meccanismi di più alto livello del framework: java.util.concurrent.locks.ReentrantLock, java.util.concurrent.Semaphore, java.util.concurrent.Exchanger, java.util.concurrent.SynchronousQueue, ecc.. ecc..

    Quindi quale/i meccanismo/i di sincronizzazione conosci e ti è permesso usare?

    Frengo ha scritto:


    come faccio a indicare al programma che, per l'appunto, l'oggetto buffer deve essere sempre lo stesso (cioè quello creato nel momento in cui istanzio un oggetto Produttore)?
    Passando alle istanze dei due consumatori lo stesso medesimo oggetto "buffer", che è quindi "condiviso".
  • Re: Esercizio Thread - Produttore/Consumatore

    Grazie andbin,

    Ho abbozzato del codice ma mi da errore: non riesco a far condividere l'oggetto Buffer istanziato nella classe Produttore (sempre che sia lì che dovevo istanziarlo...).

    Di seguito le classi da me create:
    ___________________________________________________________________________________

    package esercizioTre;
    import java.util.Random;
    import java.nio.IntBuffer;

    public class Produttore extends Thread {

    int numero;

    Random generatoreNumeri = new Random();

    IntBuffer buffer = IntBuffer.allocate(1);

    public void run() {

    for (int i=0;i<100;i++) {

    numero = generatoreNumeri.nextInt((10 - 1) + 1)+1;

    System.out.println("Numero prodotto: "+numero);

    buffer.put(numero);

    if (buffer.hasRemaining()==true) {

    try {

    wait();
    }

    catch (InterruptedException e) {

    }

    }

    }
    }
    }



    _______________________________________________________________

    package esercizioTre;
    import esercizioTre.Produttore;

    public class ConsumatoreUno extends Thread {

    private int numero;

    public void run(){

    if (buffer.hasRemaining()==true) {

    if (buffer.get()>=1 && buffer.get()<=5) {

    numero = buffer.get();

    System.out.println("Consumatore uno consuma "+numero);

    Produttore.notify();
    }
    }

    else {

    try {

    wait();
    }

    catch (InterruptedException e) {

    }
    }
    }
    }


    ________________________________________________________________________


    package esercizioTre;
    import esercizioTre.Produttore;

    public class ConsumatoreDue extends Thread {

    private int numero;

    public void run() {

    if (buffer.hasRemaining()==true) {

    if (buffer.get()>=6 && buffer.get()<=10) {

    numero = buffer.get();

    System.out.println("Consumatore due consuma "+numero);

    Produttore.notify();
    }
    }

    else {

    try {

    wait();
    }

    catch (InterruptedException e) {

    }
    }
    }
    }

    ______________________________________________________________________

    package esercizioTre;

    public class Principale {

    public static void main(String[] args) {

    Produttore threadProduttore = new Produttore();
    ConsumatoreUno threadConsumatoreUno = new ConsumatoreUno();
    ConsumatoreDue threadConsumatoreDue = new ConsumatoreDue();

    threadProduttore.start();
    threadConsumatoreUno.start();
    threadConsumatoreDue.start();

    }

    }
  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    Ho abbozzato del codice ma mi da errore: non riesco a far condividere l'oggetto Buffer istanziato nella classe Produttore (sempre che sia lì che dovevo istanziarlo...).
    No, mi spiace, non ci siamo proprio!

    Innanzitutto ho la netta (!) sensazione che l'obiettivo dell'esercizio NON sia quello di far usare il IntBuffer della NIO (che è una cosa molto particolare). A meno che sia stato richiesto esplicitamente per qualche stranissimo motivo (che ignoro, ovviamente).

    Poi comunque ci sono una valanga di cose che non vanno. Il wait() non va usato in quel modo, va sempre messo in un ciclo (ne ho parlato in un'altra discussione di questi ultimi giorni). Poi in ConsumatoreUno usi buffer ma ... buffer dichiarato dove?? buffer è di Produttore.

    Poi cose tipo in ConsumatoreDue:
    if (buffer.get()>=6 && buffer.get()<=10)

    Se è maggiore-uguale a 6, fai 2 get! ... non ha senso, anche perché ti becchi un bel BufferUnderflowException dato che il buffer ha dimensione 1.
    E non ha senso anche per un altro motivo concettuale. Fai il primo get, leggi es. 3. Questo valore sarebbe per ConsumatoreUno .. non per ConsumatoreDue. Ma tu in ConsumatoreDue lo hai già preso (e tolto), quindi ConsumatoreUno non potrebbe prenderlo. Non ha senso.

    IntBuffer (e gli altri Buffer di NIO) non sono solo dei banali array dove fai put/get quando come vuoi. Sono gestiti con una logica dove c'è il concetto di "position" e "limit". Il position si incrementa ogni volta che scrivi O leggi un valore e non deve superare il limit (che può essere il limite fisico del buffer ma è anche impostabile più basso, volendo).
    In sostanza, anche AMMESSO che l'obiettivo fosse quello di fare usare IntBuffer (e ne dubito), dovresti prima capire come funzionano i buffer di NIO.
  • Re: Esercizio Thread - Produttore/Consumatore

    Grazie andbin,

    Purtroppo non mi è stato dato materiale chiaro dove studiare, quindi il mio è stato un tentativo alla "viva il parroco".

    Avresti qualche fonte da segnalarmi che mi spieghi come si fa un esercizio simile? Io non so dove mettere mano.
  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    Purtroppo non mi è stato dato materiale chiaro dove studiare, quindi il mio è stato un tentativo alla "viva il parroco".

    Avresti qualche fonte da segnalarmi che mi spieghi come si fa un esercizio simile? Io non so dove mettere mano.
    C'è il tutorial Oracle, che ha una sezione dedicata alla Concurrency: https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
    Ma ovviamente è in inglese. Altro non saprei consigliarti ora (semmai libri, quelli su cui ho studiato io .. ma di nuovo in inglese).

    Comunque considera che la questione più grossa dell'esercizio è fare quel buffer "sincronizzato". Il resto è un "banale" uso di quel buffer.

    La sincronizzazione tra i thread si può fare in diversi modi:
    - con il lock intrinseco e la condition-queue intrinseca degli oggetti (synchronized poi wait()/notify() )
    - con ReentrantLock del framework e la sua Condition (sono concettualmente equivalente al punto precedente)
    - con altri meccanismi ... (forse con Exchanger ma ne servirebbero 2 ... no troppo contorto ..)

    A livello pratico una soluzione è una classe es. Buffer che contiene 2 dati: un int del valore e un boolean che indica "presente"/"non presente" (volendo si potrebbe usare un Integer e null=non presente; ma era per evitare complicazioni e boxing/unboxing del valore).

    Poi Buffer potrebbe avere 2 metodi:

    public void put(int valore)
    public int get(int min, int max)

    (synchronized, se usi il lock degli oggetti)

    Attenzione, qui viene la "sincronizzazione":
    - put si blocca se nel buffer c'è un valore non ancora letto, altrimenti inserisce, notifica i potenziali consumer e ritorna.
    - get si blocca se non c'è un valore O se c'è ma NON è nel range min/max richiesto. Se c'è e accettabile, lo toglie, notifica i potenziali producer e ritorna il valore.

    Se arrivi a fare questo, tutto il resto è praticamente "in discesa" perché è poi solo la condivisione dello stesso oggetto buffer e un suo banale uso. Ti assicuro che non è complesso, è più semplice di quanto pensi MA bisogna appunto avere ben chiaro come vanno usati wait/notify degli oggetti.

    Per dubbi, chiedi sempre.
  • Re: Esercizio Thread - Produttore/Consumatore

    Grazie del link e delle dritte andbin,

    Ho letto il tutorial, molto sintetico e chiaro.

    Ho rimesso mano al codice seguendo il tuo suggerimento e ho quindi ora 5 classi:

    - classe Buffer
    - classe Produttore
    - classe Consumatore Uno
    - classe Consumatore Due
    - classe Principale

    Non riesco a capire come condividere fra tutte le classi coinvolte il medesimo oggetto Buffer né dove questo andrebbe istanziato (nella classe Principale, forse?). Ho provato ad importare la classe Buffer ma non cambia nulla.
    ________________________________

    package esercizioTre;

    public class Buffer {

    private int numero;

    private boolean presente = false;


    public synchronized void setNumero(int num) {

    numero = num;
    }


    public synchronized int getNumero() {

    return numero;
    }


    public synchronized void setPresente(boolean a) {

    presente = a;
    }

    public boolean getPresente() {

    return presente;
    }


    }

    --------------------------------------------------------------------------------------

    package esercizioTre;
    import java.util.Random;
    import esercizioTre.Buffer;

    public class Produttore extends Thread {

    private int numero;

    Random generatoreNumeri = new Random();

    Buffer buffer = new Buffer();

    public void run() {

    for (int i=0;i<100;i++) {

    numero = generatoreNumeri.nextInt((10 - 1) + 1)+1;

    System.out.println("Numero prodotto: "+numero);

    if (buffer.getPresente()==false) {

    buffer.setNumero(numero);

    buffer.setPresente(true);

    notifyAll();

    }

    else {

    try {

    wait();
    }

    catch(InterruptedException e){

    }
    }
    }

    }
    }

    ---------------------------------------------------------------------------------------

    package esercizioTre;
    import esercizioTre.Buffer;

    public class ConsumatoreUno extends Thread {

    Buffer buffer = new Buffer();

    private int numero;

    public void run(){

    for (int i=0;i<100;i++) {

    if (buffer.getPresente()==true) {

    if (buffer.getNumero()>=1 && buffer.getNumero()<=5) {

    numero = buffer.getNumero();

    System.out.println("Consumatore uno prende "+numero);

    buffer.setPresente(false);

    notifyAll();

    }

    else {

    try {

    wait();
    }

    catch(InterruptedException e){

    }
    }
    }

    else {

    try {

    wait();
    }

    catch(InterruptedException e){

    }
    }
    }
    }
    }

    --------------------------------------------------------------------------------

    package esercizioTre;
    import esercizioTre.Buffer;

    public class ConsumatoreDue extends Thread {

    Buffer buffer = new Buffer();

    private int numero;

    public void run(){

    for (int i=0;i<100;i++) {

    if (buffer.getPresente()==true) {

    if (buffer.getNumero()>=6 && buffer.getNumero()<=10) {

    numero = buffer.getNumero();

    System.out.println("Consumatore uno prende "+numero);

    buffer.setPresente(false);

    notifyAll();

    }

    else {

    try {

    wait();
    }

    catch(InterruptedException e){

    }
    }
    }

    else {

    try {

    wait();
    }

    catch(InterruptedException e){

    }
    }
    }


    }
    }

    ------------------------------------------------------------------------------------------

    package esercizioTre;
    import esercizioTre.Buffer;

    public class Principale {

    public static void main(String[] args) {


    Produttore threadProduttore = new Produttore();
    ConsumatoreUno threadConsumatoreUno = new ConsumatoreUno();
    ConsumatoreDue threadConsumatoreDue = new ConsumatoreDue();

    threadProduttore.start();
    threadConsumatoreUno.start();
    threadConsumatoreDue.start();

    }

    }
  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    Ho rimesso mano al codice seguendo il tuo suggerimento e ho quindi ora 5 classi:
    Purtroppo è ancora parecchio sbagliato.

    Innanzitutto in Buffer hai solo usato synchronized (e neanche su tutti i metodi). Ma non basta. La logica "bloccante" va messa IN Buffer, non va sparpagliata tra produttori e consumatori. E' in Buffer che devi usare i wait/notify.

    E attenzione: wait va sempre messo in un ciclo (while solitamente) che testa una condizione che è quella che "regge" per far stare il thread in sospensione. Questo è importante in generale ma soprattutto quando c'è di mezzo il notifyAll() .

    Inoltre in Buffer hai messo un getNumero() e basta ma così un consumatore deve sempre prendere il valore .... e se invece non fosse per lui dato che i consumatori trattano range differenti?

    Per questo ho suggerito prima:

    public void put(int valore)
    public int get(int min, int max)

    Questi sono i soli due metodi davvero minimi e sufficienti.

    Infine, come hai appena fatto, ciascun produttore e consumatore si crea il suo Buffer. Ovviamente non va bene, non ci sarebbe alcuna "comunicazione"!

    Frengo ha scritto:


    Non riesco a capire come condividere fra tutte le classi coinvolte il medesimo oggetto Buffer né dove questo andrebbe istanziato (nella classe Principale, forse?). Ho provato ad importare la classe Buffer ma non cambia nulla.
    Produttore/ConsumatoreUno/ConsumatoreDue avranno un costruttore che riceve il Buffer (che poi si tengono incapsulato).
    L'unico oggetto Buffer quindi sarà creato in Principale e passato ai 3 "partecipanti".
  • Re: Esercizio Thread - Produttore/Consumatore

    Ah, quindi la logica bloccante deve essere una caratteristica del Buffer e non di chi lo usa.

    Se wait() e notify() li devo inserire in un ciclo ed essi vanno in Buffer, significa che nella classe Buffer dovrò inserire un ciclo, ma mi sfugge la logica di dover inserire un ciclo all'interno della classe Buffer. Intendo: per come lo intendevo io Buffer dovrebbe essere uno strumento "statico" che viene utilizzato dal produttore e dai consumatori, un contenitore di un singolo int. Non saprei proprio come impostare un ciclo all'interno di esso. E, se vi inserisco un ciclo, poi come faccio con il ciclo interno a Produttore? Deve restare?
  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    Ah, quindi la logica bloccante deve essere una caratteristica del Buffer e non di chi lo usa.
    Sì esatto, perché se fosse "sparpagliata" in giro tra produttori/consumatori: a) sarebbe codice ripetuto inutilmente; b) sarebbe facilmente "scavalcabile"/aggirabile o potrebbe essere fatta differentemente o peggio male (per sbaglio o volutamente/maliziosamente).

    Se tutta la sincronizzazione è solo in Buffer, allora è "incapsulata" e non può essere scavalcata da nessuno, oltre che non ripetuta e anche più facilmente testabile.

    Frengo ha scritto:


    Se wait() e notify() li devo inserire in un ciclo
    wait() va messo in un ciclo. I notify/notifyAll NO.

    Frengo ha scritto:


    ed essi vanno in Buffer, significa che nella classe Buffer dovrò inserire un ciclo, ma mi sfugge la logica di dover inserire un ciclo all'interno della classe Buffer. Intendo: per come lo intendevo io Buffer dovrebbe essere uno strumento "statico" che viene utilizzato dal produttore e dai consumatori, un contenitore di un singolo int. Non saprei proprio come impostare un ciclo all'interno di esso. E, se vi inserisco un ciclo, poi come faccio con il ciclo interno a Produttore? Deve restare?
    Vediamo se questo ti "illumina". Immagina una classica "coda", dove si inseriscono i dati da un lato e si estraggono dall'altro lato.
    Una coda che sia "bloccante" e anche "limitata" cioè con dimensione massima fissa.

    Un metodo es. inserisci(x) si dovrà bloccare se la coda è piena.
    Un metodo es. estrai() si dovrà bloccare se la coda è vuota.

    Ecco una implementazione che ho scritto adesso al volo (è minimale ma corretta e funzionante):
    import java.util.LinkedList;
    
    public class CodaBloccante<T> {
        private final int lunghezzaMax;
        private final LinkedList<T> lista;
    
        public CodaBloccante(int lunghezzaMax) {
            this.lunghezzaMax = lunghezzaMax;
            lista = new LinkedList<T>();
        }
    
        public synchronized void inserisci(T elemento) throws InterruptedException {
            while (lista.size() == lunghezzaMax) {    // la coda è piena? Resta in wait!!
                wait();
            }
            lista.add(elemento);
            notifyAll();
        }
    
        public synchronized T estrai() throws InterruptedException {
            while (lista.size() == 0) {    // la coda è vuota? Resta in wait!!
                wait();
            }
            notifyAll();
            return lista.removeFirst();
        }
    }
    In questo esempio sarebbe stato anche più giusto solo il notify() ma ho usato notifyAll() per estremizzare la cosa in modo più completo.

    Scenario: la coda è vuota e ci sono 2 consumatori in attesa bloccati in quel wait() di estrai(). Un produttore invoca inserisci(x), il wait NON lo esegue (non è piena) quindi inserisce subito e notifica "tutti".
    Con il notifyAll() si svegliano entrambi i consumatori ma solo UNO potrà prendere il valore. L'altro grazie a quel while ritorna a dormire in attesa di altro.

    Certo, come ho detto prima, sarebbe meglio il notify() semplice. Se un produttore inserisce 1 valore, solo un consumatore ha senso che si svegli. Ma era per far capire bene il senso di quei while.

    Ora passiamo al TUO scenario: tu hai 2 consumatori che però trattano range distinti (1-5 e l'altro 6-10). In questo caso NON puoi usare solo il notify(). Il notify() purtroppo risveglia un thread "a caso" tra quelli in attesa. Questo è ben NOTO e soprattutto ben documentato nel javadoc di Object:

    Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation.

    Se usassi notify() rischieresti di svegliare il thread-consumatore "sbagliato", che per concetto NON dovrebbe prendere quel valore (l'altro resterebbe in attesa e non andresti più avanti ..). Allora usi notifyAll(). Ma qui quel while è molto importante: tu svegli entrambi i consumatori, solo uno prenderà per il suo concetto quel valore. L'altro semplicemente deve RI-tornare a dormire.

    Insomma: il notifyAll() si usa quando l'azione di un thread può interessare:
    A) tutti gli altri thread in attesa.
    o
    B) solo uno o alcuni dei thread in attesa (in base alla azione o logica che dovranno fare) ma non potendo sapere né scegliere quali sono, li devi per forza svegliare tutti.

    Se in CodaBloccante ci fosse un inserisci(T[] elementi) per inserire tanti elementi in blocco, qui il notifyAll() sarebbe appropriato.


    Quanto ho "illuminato" con questa descrizione/esempio?
  • Re: Esercizio Thread - Produttore/Consumatore

    Ciao andbin,

    Ti ringrazio per la pazienza e i gentili consigli, ho provato a rimettere mano al codice e ha condividere il medesimo oggetto buffer fra le classi.

    Di seguito le classi.

    package esercizioTre;
    import java.util.ArrayList;

    public class Buffer {

    private ArrayList<Integer> lista = new ArrayList<Integer>(1);


    public synchronized void put(int num) {

    while (lista.isEmpty()==false) {

    try {

    wait();
    }

    catch(InterruptedException e) {

    }

    }

    lista.add(num);
    notifyAll();
    }


    public synchronized Integer get(int min, int max) {

    while (lista.isEmpty() && lista.get(0)<min && lista.get(0)>max) {

    try {

    wait();
    }

    catch(InterruptedException e) {

    }
    }

    notifyAll();

    /*qui dovrei inserire il comando remove ma se lo metto prima del .get(0) "getterò" una beata
    minchia*/
    return lista.get(0);
    }

    }
    ___________________________________________________________________________

    package esercizioTre;
    import java.util.Random;

    public class Produttore extends Thread {

    Random generaNum;

    Buffer buffer;


    public Produttore(Buffer buffer){

    generaNum = new Random();

    buffer = this.buffer;

    }


    public void run(){

    for(int i=0; i<=50 ;i++) {

    int num = generaNum.nextInt(10 - 1 + 1) + 1;

    Integer integer = Integer.valueOf(num);

    buffer.put(integer);

    System.out.println("Produttore genera "+num);

    }

    }

    }

    __________________________________________________________________________

    package esercizioTre;

    public class ConsumatoreUno extends Thread {

    Integer num;

    Buffer buffer;


    public ConsumatoreUno(Buffer buffer) {

    buffer = this.buffer;
    }


    public void run() {

    for(int i=0; i<=50 ;i++) {

    num = buffer.get(1,5);

    System.out.println("Consumatore uno prende "+num);

    }

    }

    }


    ___________________________________________________________________________

    package esercizioTre;

    public class ConsumatoreDue extends Thread {

    Integer num;

    Buffer buffer;

    public ConsumatoreDue(Buffer buffer) {

    buffer = this.buffer;
    }

    public void run() {

    for(int i=0; i<=50 ;i++) {

    num = buffer.get(6,10);

    System.out.println("Consumatore uno prende "+num);

    }

    }

    }


    ____________________________________________________________________________

    package esercizioTre;

    public class Principale {

    public static void main(String[] args) {

    Buffer mioBuffer = new Buffer();

    Produttore p = new Produttore(mioBuffer);
    ConsumatoreUno c1 = new ConsumatoreUno(mioBuffer);
    ConsumatoreDue c2 = new ConsumatoreDue(mioBuffer);

    p.start();
    c1.start();
    c2.start();

    }

    }
  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    Ti ringrazio per la pazienza e i gentili consigli, ho provato a rimettere mano al codice e ha condividere il medesimo oggetto buffer fra le classi.
    Se hai letto bene la mia descrizione+esempio coda precedente, ORA dovresti aver ben capito il senso del wait() e del suo ciclo while.

    Comunque, sul tuo nuovo codice le cose stanno così:
    1) il passaggio e "distribuzione" del Buffer è corretto come concetto ma hai sbagliato negli assegnamenti:
    es.
    buffer = this.buffer; // NO ... è proprio il contrario!

    2) l'uso di ArrayList non è sbagliato MA molto probabilmente non è questo l'obiettivo dell'esercizio, cioè farti usare una "collezione".
    Di per sé bastano 2 informazioni (l'avevo già detto):

    private int valore;
    private boolean presente;

    Comunque, usando una lista, è corretto remove(0) MA a patto che la condizione del while sia corretta (vedi punto 3).

    3) La condizione in get(int min, int max)

    while (lista.isEmpty() && lista.get(0)<min && lista.get(0)>max) {

    è sbagliata (ragionaci meglio). Deve essere true se: non presente OPPURE (=implicitamente se presente) se non nel range richiesto.

    4) Hai fatto un po' di miscugli tra int e Integer. Il put di Buffer riceve int ma il get restituisce Integer. O per entrambi int oppure per entrambi Integer. E se scegli int, non ha senso nel produttore partire da un int, poi ottenere un Integer e passarlo di nuovo come int al put.

    5) Nel produttore, 10 - 1 + 1 scritto così è superfluo...

    6) Nei consumatori, non serve che num sia "di istanza". Basta una variabile locale nel run().

    7) Il catch "vuoto" di InterruptedException non ha molto senso. Ci sono (almeno) 2 strategie riguardo InterruptedException:
    a) Nessun catch, si dichiara e permette che InterruptedException ESCA fuori da put e get. E sarà il chiamante a doverlo gestire (se avviene è perché qualcun'altro ha terminato i thread ma nel tuo caso non c'è nulla del genere).
    b) Si fa catch di InterruptedException in put/get e si lancia fuori una eccezione "unchecked".

    Generalmente meglio la a)
  • Re: Esercizio Thread - Produttore/Consumatore

    Ciao andbin,

    Grazie delle tue correzioni.

    Ho corretto la sintassi del passaggio del Buffer, la condizione del while nel metodo get e ho tolto l'arrayList, inserendo invece una semplice variabile int e un booleano di controllo. Il programma viene ora eseguito, a quanto pare QUASI correttamente.

    L'unica cosa che non mi spiego è come sia possibile che, a volte, il consumatore prenda un numero prima che esso venga generato dal produttore xD

    ___________________________________________________________

    package esercizioTre;

    public class Buffer {

    private int numero;

    private boolean vuoto = true;


    public synchronized void put(int num) {

    while (vuoto==false) {

    try {

    wait();
    }
    catch(InterruptedException e){

    }

    }

    numero=num;
    vuoto = false;
    notifyAll();
    }



    public synchronized int get(int min, int max) {

    while (vuoto==true || (numero<min || numero>max) ) {

    try {

    wait();
    }

    catch(InterruptedException e) {

    }
    }

    int a = numero;
    vuoto = true;
    notifyAll();
    return a;
    }

    }
    -----------------------------------------------------------------------------------

    package esercizioTre;
    import java.util.Random;

    public class Produttore extends Thread {

    Random generaNum;

    Buffer buffer;


    public Produttore(Buffer buffer){

    generaNum = new Random();

    this.buffer = buffer;

    }


    public void run(){

    for(int i=0; i<=50 ;i++) {

    int num = generaNum.nextInt(10 - 1 + 1) + 1;

    buffer.put(num);

    System.out.println("Produttore genera "+num);

    }

    }

    }

    -------------------------------------------------------------------------------------

    package esercizioTre;

    public class ConsumatoreUno extends Thread {

    Integer min = 1;
    Integer max = 5;

    Buffer buffer;


    public ConsumatoreUno(Buffer buffer) {

    this.buffer = buffer;
    }


    public void run() {

    for(int i=0; i<=50 ;i++) {

    int num = buffer.get(min,max);

    System.out.println("Consumatore uno prende "+num);

    }

    }

    }

    ----------------------------------------------------------------------------------------------------

    package esercizioTre;

    public class ConsumatoreDue extends Thread {

    Integer min = 6;
    Integer max = 10;

    Buffer buffer;

    public ConsumatoreDue(Buffer buffer) {

    this.buffer = buffer;
    }

    public void run() {

    for(int i=0; i<=50 ;i++) {

    int num = buffer.get(6,10);

    System.out.println("Consumatore due prende "+num);

    }

    }

    }
    -----------------------------------------------------------------------------------------

    package esercizioTre;

    public class Principale {

    public static void main(String[] args) {

    Buffer mioBuffer = new Buffer();

    Produttore p = new Produttore(mioBuffer);
    ConsumatoreUno c1 = new ConsumatoreUno(mioBuffer);
    ConsumatoreDue c2 = new ConsumatoreDue(mioBuffer);

    p.start();
    c1.start();
    c2.start();


    }

    }
  • Re: Esercizio Thread - Produttore/Consumatore

    Frengo ha scritto:


    Ho corretto la sintassi del passaggio del Buffer, la condizione del while nel metodo get e ho tolto l'arrayList, inserendo invece una semplice variabile int e un booleano di controllo.
    Il codice ora mi pare proprio che sia funzionalmente corretto, cioè hai gestito i test e i vari passi in maniera di per sé appropriata.
    Ci sono comunque queste piccole annotazioni che potrei ancora fare:

    1) Io personalmente avrei messo boolean presente (presente=true allora c'è un valore), ovvero al contrario del tuo. Ma è solo questione di gusti, se è fatto correttamente, vale l'uno o l'altro.

    2) Nel while puoi togliere le parentesi interne:

    while (vuoto==true || (numero<min || numero>max) ) {

    while (vuoto==true || numero<min || numero>max) {

    il significato non cambia, || è un operatore con "short-circuit": se vuoto è true, il resto della espressione NON lo valuta nemmeno!

    3) Al fondo del get hai messo:
    int a = numero;
    vuoto = true;
    notifyAll();
    return a;
    è corretto. Ma lo si può scrivere più semplicemente:
    vuoto = true;
    notifyAll();
    return numero;
    Considera sempre che lì sei in un metodo synchronized quindi, a parte il momento di "sospensione" nel wait() (che rilascia il lock), lì dentro hai la esclusività del lock!! Mentre vengono eseguite queste operazioni, nessun altro thread può interferire, né eseguire un put o get.
    Quindi anche se ritorni il numero dopo aver messo vuoto = true (che sembra una incoerenza), in realtà nella interezza "atomica" del get() è comunque corretto.

    4) Hai ancora il catch "vuoto" di InterruptedException, che non è molto bello. Ragiona su quanto ho già detto prima riguardo le interruzioni (se hai dubbi, chiedi).

    Frengo ha scritto:


    Il programma viene ora eseguito, a quanto pare QUASI correttamente.
    Quello che devi verificare è che la sequenza di numeri 1-5 generata da Produttore sia la stessa presa da ConsumatoreUno e che la sequenza di numeri 6-10 generata sia la stessa presa da ConsumatoreDue.
    Se questo è corretto, la logica è corretta.

    Se la sequenza generata fosse es. 4 7 6 3 2 9 5 10 1 8
    Devi guardare che i soli output di ConsumatoreUno diano in sequenza 4 3 2 5 1
    E separatamente guardare che i soli output di ConsumatoreDue diano in sequenza 7 6 9 10 8

    Frengo ha scritto:


    L'unica cosa che non mi spiego è come sia possibile che, a volte, il consumatore prenda un numero prima che esso venga generato dal produttore xD
    No, quello che hai detto non può succedere (non avrebbe senso). Ti stai basando solo su quello che vedi in output ma la chiamata a put/get è distinta dal println e l'insieme di queste due operazioni NON è atomico! Quindi con tempistiche un po' "sfortunate" (specialmente su macchine multi-core) può assolutamente capitare che a livello temporale le istruzioni vengano "intercalate" nel seguente modo:
    tempo       thread Produttore                                   thread ConsumatoreUno
      |                                                 :
      |  buffer.put(num);                               :
      |                                                 :
      |                                                 :  int num = buffer.get(min,max);
      |                                                 :  System.out.println("Consumatore uno prende "+num);
      |                                                 :
      |  System.out.println("Produttore genera "+num);  :
      |                                                 :
      ?
    Ma il get è comunque subordinato (quindi dopo) al put, questo non può cambiare (se è fatto tutto correttamente).
  • Re: Esercizio Thread - Produttore/Consumatore

    Ti ringrazio molto della pazienza e della chiarezza

    Lezioni private -ovviamente dietro compenso- ne dai, per caso? xD
Devi accedere o registrarti per scrivere nel forum
14 risposte