Una soluzione al livelock per java

Ultimo aggiornamento: 09-12-2012

[English version]

Assay

Livelock: A thread often acts in response to the action of another thread. If the other thread’s action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work. This is comparable to two people attempting to pass each other in a corridor: Alphonse moves to his left to let Gaston pass, while Gaston moves to his right to let Alphonse pass. Seeing that they are still blocking each other, Alphone moves to his right, while Gaston moves to his left. They’re still blocking each other, so…

Fonte: Lessons: Concurrency

Java con le API di concorrenza offre un buon metodo per evitare il deadlock: l’utilizzo dei blocchi tramite l’interfaccia java.util.cuncurrent.locks e i metodi che l’implementano come ReentrantLock, mentre per il livelock in giro non è che si trovi molto (scrivo il 05/11/2011), quindi ho deciso di rilasciare un mia piccola soluzione a questo problema.

La soluzione che qui propongo si basa sull’utilizzo dell’interfaccia java.util.concurrent.ExecutorService e di un’ interfaccia creata da me LiveLockSolution con la quale mi ricordo di implementare un metodo contro il livelock, utile in quelle classi usate per la concorrenza dei dati,

public interface LiveLockSolution {
    public void liveLockSolution();
}

Ora vi mostro un esempio di una classe prodotto che utilizza i blocchi per evitare deadlock e che implemente LiveLockSolution

public class Prodotto implements LiveLockSolution{
    private int quantità;
    private boolean endLoop;
    private ReentrantLock lock;
    private Condition cond;

    public Prodotto(ReentrantLock lock) {
        this.endLoop = false;
        this.quantità = 0;
        this.lock = lock;
        this.cond = lock.newCondition();
    }

    /**
     * Produco un prodotto
     */
    public void produci() {
        lock.lock();
        try {
            this.quantità++;
            System.out.println("Q:" + this.quantità);
            cond.signalAll();
        } finally {
            lock.unlock();
        }        
    }

    /**
     * Consumo il prodotto, se questo non è disponibile allora attendo
     */
    public void consuma() {
        lock.lock();
        try {
            while(endLoop == false && quantità == 0) {
                cond.await();
            }
            if(endLoop == false) { //se son stato avvisato non faccio niente
                this.quantità--;
                System.out.println("Q:" + this.quantità);
            }
            cond.signalAll();
        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Faccio in modo da far terminare i vari thread che usano Prodotto e che stanno aspettando 
     */
    @Override
    public void liveLockSolution() {
        lock.lock();
        try {
            this.endLoop = true; //dico a consuma di non aspettare altra produzione
            cond.signalAll();
        } finally {
            lock.unlock();
        }
    }

}

Ora vi lascio immaginare come saranno le classi Produttore e Consumatore, e quindi vi mostro il main:

public static void main(String[] args) {
        try {
            ReentrantLock lock = new ReentrantLock();
            Prodotto obj = new Prodotto(lock);
            Consumatore cons = new Consumatore(obj);
            Consumatore cons2 = new Consumatore(obj);
            Produttore prod = new Produttore(obj);

            System.out.println("Inizio Concorrenza\n\n");

            //Uso l'API concorrente di java come alternativa ai thread
            ExecutorService es = Executors.newCachedThreadPool();
            //eseguo i vari thread
            es.execute(cons);
            es.execute(cons2);
            es.execute(prod);

            //faccio lavorare per un secondo
            Thread.sleep(1000); 

            //fermo tutti i thread
            prod.stop();
            cons.stop();
            cons2.stop();

            //non accetto più nuovi task
            es.shutdown(); 
            //aspetto la terminazione dei task nell'ExecutorService
            while(es.awaitTermination(100, TimeUnit.MILLISECONDS) == false) {
                System.out.println("Aspetto terminazione thread");
                obj.liveLockSolution(); //risolvo il livelock
            }
            System.out.println("Fine Concorrenza");

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch(Exception e) {
            e.printStackTrace();
        }

    }

Come potete vedere il metodo liveLockSolution viene eseguito ogni qual volta la funzione awaitTermination trova un thread attivo nel pool dei thread di ExecutorService.
Per provarlo basta scaricare il sorgente ed eseguirlo.