Esercizio Thread - metodo static

di il
20 risposte

Esercizio Thread - metodo static

Salve a tutti, sono alle prese con un esame all'università sulla programmazione java, e ho un po' di difficoltà con la parte che riguarda i thread, o meglio la gestione di essi per evitare le race condition. Come nell'esercizio che segue:

"Implementare il metodo statico findString che accetta una stringa x e un array di stringhe a che restituisce 'vero' se x è una delle stringhe di a, e 'falso' altrimenti. Per ottenere questo risultato, il metodo usa due tecniche in parallelo: un primo thread confronta x con ciascuna stringa dell'array; un altro thread confronta solo la lunghezza di x con quella di ciascuna stringa dell'array. Il metodo deve restituire il controllo al chiamante appena è in grado di fornire una risposta certa.
Ad esempio, se il secondo thread scopre che nessuna stringa dell'array ha la stessa lunghezza di x, il metodo deve subito restituire 'falso' e terminare il primo thread (se è ancora in esecuzione)".

La prima cosa (sbagliata) che mi è venuta in mente è dichiarare una variabile volatile, e tramite il controllo di essa interrompere l'esecuzione dei thread quando si è trovata o non trovata la stringa. Ma un attimo dopo mi sono resa conto che questa cosa non poteva essere possibile in quando non posso dichiarare una variabile locale volatile. E quindi mi sono bloccata completamente.
Cerco qualche consiglio che mi possa aiutare a ragionare per la risoluzione dell'esercizio.
Grazie mille a chi mi aiuterà!

20 Risposte

  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    La prima cosa (sbagliata) che mi è venuta in mente è dichiarare una variabile volatile, e tramite il controllo di essa interrompere l'esecuzione dei thread quando si è trovata o non trovata la stringa. Ma un attimo dopo mi sono resa conto che questa cosa non poteva essere possibile in quando non posso dichiarare una variabile locale volatile.
    Infatti. Una variabile locale non può essere volatile. Siccome è appunto locale al metodo, allora NON è condivisa di certo tra thread, quindi volatile non avrebbe proprio senso.

    violet_prog ha scritto:


    Cerco qualche consiglio che mi possa aiutare a ragionare per la risoluzione dell'esercizio.
    Vediamo intanto di cosa c'è bisogno. Devi gestire due thread, quindi sicuramente dovrai definire due classi che implementano Runnable (tecnicamente si può estendere Thread ma è meno "pulito" dal punto di vista concettuale).
    Le due classi Runnable saranno ovviamente diverse, i loro run() dovranno contenere una logica di ricerca differente. Le due classi dovranno avere accesso entrambe all'array e alla stringa. I due thread li useranno solo in lettura (non andranno di certo a modificare l'array).

    Questa è solo la prima questione. La seconda è la sincronizzazione. Appena il findString ha creato e avviato i due thread, dovrà mettersi in "attesa" di una risposta dai due thread. Questo vuol dire che all'interno di findString ci deve essere un meccanismo "bloccante" che si sblocca solo quando uno (il primo) dei due thread determina una risposta utile al risultato finale.

    Come fare questo meccanismo ... dipende. Dipende se puoi utilizzare costrutti di sincronizzazione di "alto livello" (le classi nel package java.util.concurrent e sotto package) oppure se devi fare "a mano" tu con i wait/notify.

    Esiste ancora una terza questione: come terminare i due thread. Se vuoi terminarli prima che essi finiscano "naturalmente" il loro ciclo, allora devi codificare i run() dei due thread in modo che siano "cooperativi" alla terminazione.

    Sono riuscito a dare una visione delle problematiche? Ti dico subito che tutto questo è assolutamente fattibile ma non è "banalissimo". Io riuscirei a farlo bene ma mi ci vorrebbe un pochino ... non sono "esercizi" che faccio tutti i giorni ...
  • Re: Esercizio Thread - metodo static

    andbin ha scritto:


    Infatti. Una variabile locale non può essere volatile. Siccome è appunto locale al metodo, allora NON è condivisa di certo tra thread, quindi volatile non avrebbe proprio senso.
    Sul fatto che una variabile locale non può essere volatile ci sono, in quanto volatile può essere di fatto applicato solo a variabili 'di classe'.
    Sulla tua spiegazione però non ci sono... in quanto il professore ci ha più volte svolto esercizi in cui all'interno di un metodo creavamo vari thread che facessero più cose parallelamente, tramite classi interne**.. ed in effetti è proprio quello che bisogna fare qui. Quindi all'interno del metodo devo creare due thread che appunto nel loro metodo run facciano due ricerche separate.. e il mio problema nasce proprio dalla comunicazione tra essi, che mi è poco chiara.

    **
    Thread t=new Thread(){
                    @Override
                    public void run(){
                        ...
                    }
                };
               ...
               
                t.start();
  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    in quanto volatile può essere di fatto applicato solo a variabili 'di classe'.
    Sì, "di classe" (si intende static) o "di istanza" (non static).

    violet_prog ha scritto:


    il professore ci ha più volte svolto esercizi in cui all'interno di un metodo creavamo vari thread che facessero più cose parallelamente, tramite classi interne**..
    Ok, usare classi interne a metodi specialmente per dei thread non è il massimo della bellezza come design ma non c'è nulla di tecnicamente sbagliato.
    Anche estendere Thread va bene, tecnicamente parlando.

    violet_prog ha scritto:


    il mio problema nasce proprio dalla comunicazione tra essi, che mi è poco chiara.
    Ok, partiamo da un aspetto basilare: l'accesso ad array e stringa. Il tuo metodo findString avrà 2 parametri, appunto l'array e la stringa. Se a questi due parametri applichi la parola chiave final, essi diventano tranquillamente accessibili anche da una anonymous inner class interna al metodo.
    Se usi Java 8, meglio ancora perché il final non è strettamente necessario, purché i due parametri siano effectively final, ovvero non gli sia assegnato altro.

    Riguardo la sincronizzazione tra il findString e i due thread, ripeto la domanda: puoi usare i meccanismi di alto livello offerti dal package java.util.concurrent oppure no? Se no (perché non li conosci o perché ti è stato espressamente chiesto di non usarli), devi per forza usare i wait/notify degli oggetti. Conosci l'utilizzo del lock "intrinseco" degli oggetti?
  • Re: Esercizio Thread - metodo static

    Ok, partiamo da un aspetto basilare: l'accesso ad array e stringa. Il tuo metodo findString avrà 2 parametri, appunto l'array e la stringa. Se a questi due parametri applichi la parola chiave final, essi diventano tranquillamente accessibili anche da una anonymous inner class interna al metodo.
    Se usi Java 8, meglio ancora perché il final non è strettamente necessario, purché i due parametri siano effectively final, ovvero non gli sia assegnato altro.
    ok
    Riguardo la sincronizzazione tra il findString e i due thread, ripeto la domanda: puoi usare i meccanismi di alto livello offerti dal package java.util.concurrent oppure no? Se no (perché non li conosci o perché ti è stato espressamente chiesto di non usarli), devi per forza usare i wait/notify degli oggetti. Conosci l'utilizzo del lock "intrinseco" degli oggetti?
    allora se intendi le blockingqueue, mi è dato utilizzare quelle, ma anche i wait/notify.
    Come puoi vedere sono alle prime armi proprio, e devo ancora acquisire dimestichezza con il linguaggio.
  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    allora se intendi le blockingqueue, mi è dato utilizzare quelle, ma anche i wait/notify.
    Come puoi vedere sono alle prime armi proprio, e devo ancora acquisire dimestichezza con il linguaggio.
    Ahh, se puoi usare un ArrayBlockingQueue tanto di guadagnato! Crea un ArrayBlockingQueue<Boolean> di capacità 2 che sarà condiviso (accessibile) tra il codice del findString e il codice dei due thread.
    Appena dopo aver fatto partire i thread, il findString invoca il take() che è bloccante e quindi sta in attesa di una risposta. Appena uno dei thread ha la risposta (true o false che sia) la butta nella coda con il put() e quindi il take() si sveglia.

    Ho detto prima di capacità 2 per un motivo molto semplice: evita che un put() si blocchi.
  • Re: Esercizio Thread - metodo static

    andbin ha scritto:


    Ahh, se puoi usare un ArrayBlockingQueue tanto di guadagnato! Crea un ArrayBlockingQueue<Boolean> di capacità 2 che sarà condiviso (accessibile) tra il codice del findString e il codice dei due thread.
    Appena dopo aver fatto partire i thread, il findString invoca il take() che è bloccante e quindi sta in attesa di una risposta. Appena uno dei thread ha la risposta (true o false che sia) la butta nella coda con il put() e quindi il take() si sveglia.
    public static boolean findString (String s, String[] a) throws InterruptedException{
            BlockingQueue<Boolean> q=new ArrayBlockingQueue(2);
            Thread t1, t2;
            t1=new Thread(){
                public void run(){
                    for(int i=0;i<a.length;i++){
                        if(s.equals(a[i]))
                            try {
                                q.put(true);
                        } catch (InterruptedException ex) {
                            return;
                        }
                    }
                }
            };
            t2=new Thread(){
                public void run(){
                    for(int i=0;i<a.length;i++){
                        if(s.length()==a[i].length())
                            return;
                    }
                    try {
                        q.put(false);
                    } catch (InterruptedException ex) {
                        return;
                    }
                }
            };
            t1.start();
            t2.start();
            return q.take();
            
        }
    Così va bene?
  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    Così va bene?
    Non l'ho compilato/provato ma a vederlo così mi pare sensato e corretto. Ti manca solo, se vuoi/devi implementarla, la terminazione dei thread prima del loro naturale completamento.

    P.S. vedo che non hai usato final, quindi sicuramente hai almeno Java 8 (il tuo codice non compilerebbe su Java 7 o inferiore).
  • Re: Esercizio Thread - metodo static

    andbin ha scritto:


    Non l'ho compilato/provato ma a vederlo così mi pare sensato e corretto. Ti manca solo, se vuoi/devi implementarla, la terminazione dei thread prima del loro naturale completamento.
    Penso che ai fini della traccia vada bene cosi, ma se volessi implementare la terminazione come mi consiglieresti di farlo?
    P.S. vedo che non hai usato final, quindi sicuramente hai almeno Java 8 (il tuo codice non compilerebbe su Java 7 o inferiore).
    Si infatti a me compila perfettamente. Grazie per l'accorgimento. Dovrei aggiungere final ai parametri del metodo, giusto?
  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    Penso che ai fini della traccia vada bene cosi, ma se volessi implementare la terminazione come mi consiglieresti di farlo?
    Vedi gli interrupt() / interrupted() / isInterrupted() di Thread.
    E magari, come "background" generale, anche Java Thread Primitive Deprecation
    Dovrei aggiungere final ai parametri del metodo, giusto?
    Se vuoi/devi rendere il codice compilabile anche per < Java 8, allora devi mettere final su s/a/q.
    Se ti hanno chiesto espressamente per Java 8 oppure è indifferente, va sicuramente bene come l'hai fatto.
  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    Così va bene?
    Un saluto a tutti, anzitutto, essendo il mio primo post.

    Se s (la stringa da valutare) ha la stessa lunghezza di una o più stringhe nell'array, ma non è presente nell'array, cosa succede?
  • Re: Esercizio Thread - metodo static

    AnMa ha scritto:


    Se s (la stringa da valutare) ha la stessa lunghezza di una o più stringhe nell'array, ma non è presente nell'array, cosa succede?
    Giusto, è un caso in più che violet_prog deve gestire alla fine del Thread 1.
  • Re: Esercizio Thread - metodo static

    AnMa ha scritto:


    Se s (la stringa da valutare) ha la stessa lunghezza di una o più stringhe nell'array, ma non è presente nell'array, cosa succede?
    L'ho modificato per gestire anche questo caso.. Ma non so se c'è un modo più semplice di farlo.
    public static boolean findString (String s, String[] a) throws InterruptedException{
            BlockingQueue<Boolean> q=new ArrayBlockingQueue(2);
            Thread t1, t2;
            t1=new Thread(){
                public void run(){
                    for(int i=0;i<a.length;i++){
                        if(s.equals(a[i]))
                            try {
                                q.put(true);
                                interrupted();
                        } catch (InterruptedException ex) {
                            return;
                        }           
                    }
                    if(q.isEmpty())
                        try {
                            q.put(false);
                        } catch (InterruptedException ex) {
                            return;
                        }
                }
            };
            t2=new Thread(){
                public void run(){
                    for(int i=0;i<a.length;i++){
                        if(s.length()==a[i].length())
                            return;
                    }
                    try {
                        q.put(false);
                        interrupted();
                    } catch (InterruptedException ex) {
                        return;
                    }
                }
            };
            t1.start();
            t2.start();
            
            return q.take();
            
        }

    andbin ha scritto:


    Vedi gli interrupt() / interrupted() / isInterrupted() di Thread.
    Questi sono gli unici tre metodi che fanno parte del programma, cioè per esempio stop() non lo abbiamo studiato.
    Ma in questo caso utilizzare interrupt() modifica l'esecuzione del codice? Non richiamando poi un metodo bloccante mi pare resti invariato..
    Quindi mi chiedo, servirebbe in questo caso, oppure in effetti per quel che deve fare il metodo va bene così.. ?! Forse sì.
  • Re: Esercizio Thread - metodo static

    violet_prog ha scritto:


    L'ho modificato per gestire anche questo caso.. Ma non so se c'è un modo più semplice di farlo.Questi sono gli unici tre metodi che fanno parte del programma, cioè per esempio stop() non lo abbiamo studiato.
    Ma in questo caso utilizzare interrupt() modifica l'esecuzione del codice? Non richiamando poi un metodo bloccante mi pare resti invariato..
    Quindi mi chiedo, servirebbe in questo caso, oppure in effetti per quel che deve fare il metodo va bene così.. ?! Forse sì.
    No, non va bene. Innanzitutto stop() (come anche destroy() e altri) di Thread dimenticalo! È un metodo "deprecato" e NON va mai più usato.
    Il codice comunque non fa la terminazione e non è appropriato.

    Il if(q.isEmpty()) non servirebbe nemmeno, non c'è affatto bisogno di verificare se la coda è vuota.

    Sulla terminazione invece ... non hai terminato un bel nulla. Sei tu che dopo il q.take() devi innanzitutto fare un interrupt() sui due thread. Questo di per sé non "interrompe" un bel nulla, setta solo un "flag" di interruzione. Ci sono dei casi particolari in cui interrupt() causa qualcosa "di più" ma dipende da COSA sta facendo il thread che si sta cercando di interrompere. La documentazione di interrupt() spiega questi casi.

    Poi in ciascun thread, visto che fortunatamente nei tuoi casi non c'è alcun I/O "bloccante", sleep o altro, a te basta che ad ogni ciclo verifichi "sono stato interrotto?" e questo lo fai con interrupted() o isInterrupted() (ci sono differenze fini). Se sì, fai terminare subito il run().
  • Re: Esercizio Thread - metodo static

    andbin ha scritto:


    No, non va bene. Innanzitutto stop() (come anche destroy() e altri) di Thread dimenticalo! È un metodo "deprecato" e NON va mai più usato.
    Infatti come dicevo non l'ho studiati e in realtà non so neanche cosa fanno.
    Il if(q.isEmpty()) non servirebbe nemmeno, non c'è affatto bisogno di verificare se la coda è vuota.
    Questo controllo l'ho aggiunto per trattare il caso suddetto in cui l'array contiene stringhe della lunghezza di s, ma non contiene s stessa. In questo caso ho immaginato che nè il primo thread (che non trova la stringa uguale) e nè il secondo thread (che trova almeno una stringa di uguale lunghezza) aggiungono rispettivamente true o false alla coda, e quindi se alla fine del ciclo del primo thread la coda risulta ancora vuota vuol dire che la stringa non c'è.
    Sulla terminazione invece ... non hai terminato un bel nulla. Sei tu che dopo il q.take() devi innanzitutto fare un interrupt() sui due thread. Questo di per sé non "interrompe" un bel nulla, setta solo un "flag" di interruzione. Ci sono dei casi particolari in cui interrupt() causa qualcosa "di più" ma dipende da COSA sta facendo il thread che si sta cercando di interrompere. La documentazione di interrupt() spiega questi casi.

    Poi in ciascun thread, visto che fortunatamente nei tuoi casi non c'è alcun I/O "bloccante", sleep o altro, a te basta che ad ogni ciclo verifichi "sono stato interrotto?" e questo lo fai con interrupted() o isInterrupted() (ci sono differenze fini). Se sì, fai terminare subito il run().
    Esattamente... Quello che mi chiedevo era appunto se, nonostante in questo caso non richiamo nè sleep, nè wait, fosse comunque necessario chiamare interrupt() che come appunto so non interrompe il thread ma setta la flag di interruzione a true. Quindi la risposta è si. E' comunque sempre necessario, quindi devo sempre considerare questa cosa, giusto?!
    Penso di usare isInterrupted() in quanto, interrupted() oltre al controllo resetta la flag, è corretto?

    Ecco il codice aggiornato, secondo i tuoi consigli
    public static boolean findString (String s, String[] a) throws InterruptedException{
            BlockingQueue<Boolean> q=new ArrayBlockingQueue(2);
            Thread t1, t2;
            t1=new Thread(){
                public void run(){
                    for(int i=0;i<a.length;i++){
                        if(isInterrupted())
                            return;
                        if(s.equals(a[i]))
                            try {
                                q.put(true);
                        } catch (InterruptedException ex) {
                            return;
                        }           
                    }
                    if(q.isEmpty())
                        try {
                            q.put(false);
                        } catch (InterruptedException ex) {
                            return;
                        }
                }
            };
            t2=new Thread(){
                public void run(){
                    for(int i=0;i<a.length;i++){
                        if((s.length()==a[i].length()||isInterrupted()))
                            return;
                    }
                    try {
                        q.put(false);
                    } catch (InterruptedException ex) {
                        return;
                    }
                }
            };
            t1.start();
            t2.start();
            boolean ris=q.take();
            t1.interrupt();
            t2.interrupt();
            return ris;
            
        }
Devi accedere o registrarti per scrivere nel forum
20 risposte