Utente:LoStrangolatore/corsojava/threads and locks

Da Wikiversità, l'apprendimento libero.

Thread e sincronizzazione[modifica]

Come avviare un thread[modifica]

Una regola fondamentale dei thread in Java è che la macchina virtuale non dà alcuna garanzia sulla tempistica con cui sono eseguite le istruzioni di ogni thread: esso può essere interrotto in ogni momento perché la CPU possa essere impegnata nell'esecuzione delle istruzioni degli altri thread.

Atomicità[modifica]

Problema

Consideriamo il seguente codice:

Cosa succede se i due thread modificano contemporaneamente? Si ottiene un comportamento che ... .

Come risolvere il problema? Semplice: bisogna fare in modo che nessuno dei due thread possa modificare lo stato interno dell'oggetto mentre lo sta facendo l'altro. In altre parole: se uno dei due sta modificando l'oggetto, l'altro deve aspettare finché il primo non ha finito.

Sezioni critiche

Come fare? Il modo migliore è marcare in qualche modo i blocchi di codice che modificano lo stato interno dell'oggetto. In gergo, si dice che i due blocchi di codice sono due sezioni critiche. Questa è, in pratica, l'idea di base viene seguita quando si scrive un programma multi-threaded in alcun linguaggio di programmazione. Java offre un meccanismo per farlo, a livello di linguaggio, tramite la parola-ciave synchronized. Questo è importante perché, in altri linguaggi, non è così; ad esempio, in C e in C++ il programmatore deve affidarsi alle primitive del sistema operativo in uso sia per creare e manipolare i thread, sia per definire le sezioni critiche nel codice.

Più in generale, questi problemi si verificano se più thread nel programma accedono contemporaneamente ad una stessa risorsa, e almeno uno di essi accede in scrittura.[1]

Lock

Quindi, un programma multithreaded bisogna segnalare come sezioni critiche tutti i blocchi di codice che accedono alle risorse condivise. Inoltre, bisogna collegare in qualche modo la sezione critica alla risorsa: bisogna segnalare alla macchina virtuale che risorse diverse coinvolgono sezioni critiche diverse, e quindi insiemi di thread diversi potranno accedere a risorse diverse vietandosi a vicenda l'accesso.

Per far questo bisogna trovare un identificativo per ogni risorsa. In Java si usano i cosiddetti lock: ogni oggetto possiede un lock. I lock obbediscono ad una semplice regola: in ogni momento non più di un thread può possedere (in inglese, hold) il lock. A questo punto è facile: semplicemente basta usare come identificativo per una risorsa condivisa il lock di qualche oggetto.

Mettiamo insieme i pezzi

In Java le sezioni critiche si definiscono marcando un blocco di istruzioni con la parola-chiave synchronized. Essa accetta in ingresso un oggetto.

    synchronized(obj) {
        ...
    }

Quando un thread incontra un blocco synchronized, controlla se ci sono altri thread che hanno già acquisito il lock di quell'oggetto. In caso affermativo, aspetta finché non viene rilasciato. Altrimenti, acquisisce il lock ed entra nel blocco, rilasciandolo quando esce dal blocco.

Deadlock[modifica]

Cosa succede eseguendo questo codice?

In molti casi il programma si blocca. Non è detto, ma è probabile. Perché? Analizziamo il flusso di esecuzione: (...)

Questo significa che ciascuno dei due thread è fermo nel'attesa che l'altro liberi il lock che detiene, e ovviamente questo significa che nessuno dei due può andare avanti. I due thread sono bloccati e non possono proseguire la propria esecuzione.

Questa condizione è nota come deadlock ed è fonte di bug all'interno dei programmi.

Evitare

Per evitare deadlock è possibile seguire dei semplici suggerimenti:

  • evitare, quando possibile, di invocare codice esterno alla classe all'interno di un blocco synchronized. Infatti, questo è un facile modo per prevenire i deadlocks, perché evita che codice definito in moduli diversi acquisisca lock diversi, senza una coordinazione (in quanto l'implementazione interna dei moduli non è documentata). Altrimenti, ogni modulo dovrebbe segnalare se acquisisce dei lock oppure no (ma questo pone dei vincoli alle future implementazioni interne).
  • evitare, quando possibile, di usare blocchi del tipo synchronized(this), preferendo invece oggetti istanziati internamente. Questo perché un altro modulo potrebbe decidere di eseguire blocchi synchronized usando lo stesso oggetto, e questo potrebbe portare a deadlocks.
  • alcuni programmi di analisi del codice alla ricerca di bug. Molti analizzano anche i possibili percorsi che il prgramma atrtraverserà durante la sua esecuzione, e sono in grado di segnalare alcune delle possibili condizioni in cui il programma potrebbe imbattersi in un deadlock.
  • quando possibile, preferire l'uso di oggetti immutabili anziché mutabili. Infatti, gli oggetti immutabili sono automaticamente thread-safe (ma forniscono anche altri vantaggi).

Altri consigli:

  • le Collection di Java tipicamente non sono thread-safe. Ad esempio, ArrayList, LinkedListe, ecc. non lo sono. Per ottenere una collezione thread-safe, si usano i metodi synchronizedList(), synchronizedMap(), ecc. della classe java.util.Collections, che restituiscono una versione thread-safe di una collezione che non lo è.

Inoltre, rendere le sezioni critiche il più piccole possibile. Infatti, le sezioni critiche sono fatte apposta per bloccare gli altri thread, e questo va contro il concetto stesso di thread come mezzo per eseguire il codice in contemporanea. Tutte le elaborazioni non necessarie dovrebbero avvenire al di fuori delle sezioni critiche. Ad esempio: (... codice di esempio)


Riferimenti[modifica]

  • (java language specification ...)
  • (manuale su wikibooks, dove, in aggiunta a tutto questo, si danno le seguenti informazioni:
    • synchronized(null) {...} lancia una NullPointerException
    • alla terminazione di tutti i thread non-daemon, il programma termina
    • ...

mettendoci anche i riferimenti alla JLS )

Note[modifica]

  1. è per questo che gli oggetti immutabili sono automaticamente thread-safe: perché essi garantiscono che nessun thread può mai accedere ai loro dati interni in scrittura. Si veda anche la sezione "Alternative".