Il professor Ricci distingue nettamente tra testing e verification:
| Testing | Verification |
|---|---|
| Attività che cerca failure (rivelando fault) | Verifica se il sistema soddisfa le sue specifiche |
| "Have we built the system right?" | "Have we built the right system?" |
| Verifica che una proprietà valga per qualche scenario selezionato | Verifica che una proprietà valga per tutti gli scenari possibili |
| Rivela la presenza di errori, non la loro assenza | Richiede tecniche formali |
Il test cerca la presenza di bug eseguendo il programma su scenari specifici e osservando i risultati. Riguarda alcuni scenari selezionati. Rivela la presenza di errori, non la loro assenza.
Nel contesto concorrente, il test e particolarmente problematico per il fenomeno degli heisenbug: il codice di test puo introdurre artefatti di temporizzazione che mascherano i bug.
La verifica chiede: "abbiamo costruito il sistema correttamente?" (have we built the system right?). Deve stabilire se una proprieta di correttezza vale in tutti i possibili scenari di esecuzione.
Serve una tecnica formale: o la generazione esaustiva degli stati (model checking) o la dimostrazione induttiva (theorem proving).
La validazione chiede: "abbiamo costruito il sistema giusto?" (have we built the right system?). Verifica che il sistema soddisfi le aspettative dello stakeholder, partendo dai requisiti. E un'attivita diversa dalla verifica: un sistema puo essere verificato come corretto ma non validato perche non fa cio che ci si aspetta.
Fault, error, failure: un fault e la manifestazione di un errore (un'azione umana che produce un risultato incorretto). Un failure si verifica quando un fault viene incontrato durante l'esecuzione. Un failure puo essere causato da molti fault, e un fault puo causare molti failure.
Il testing tradizionale non è sufficiente per i programmi concorrenti. Lo stesso input può produrre output diversi a seconda dello scenario. Non si può "debuggare" un programma concorrente nel modo normale, perché ogni esecuzione produce probabilmente uno scenario diverso. Inoltre, i test stessi possono introdurre artefatti di temporizzazione che mascherano i bug (fenomeno degli heisenbug).
Da qui la necessità di due tecniche formali principali per la verifica, che occupano il resto del capitolo: il model checking (esplorazione esaustiva dello spazio degli stati) e la dimostrazione induttiva di invarianti.
Il professor Ricci apre la lezione di laboratorio riprendendo i concetti fondamentali che costituiscono il ponte fra la teoria e la pratica della verifica. Un programma concorrente non ha un unico comportamento deterministico: lo stesso input puo generare output diversi a seconda dell'interleaving delle istruzioni atomiche dei thread. Per questo il testing tradizionale non basta: eseguire un programma concorrente cento volte con gli stessi dati puo sempre far emergere un nuovo scenario problematico. Il model checking affronta il problema in modo radicale: esplora tutti gli scenari possibili in modo automatico.
Il testing rivela la presenza di errori, non la loro assenza. Nei programmi concorrenti, lo stesso input puo dare output diversi in scenari diversi.
— Prof. Ricci
Un programma concorrente va modellato come un insieme di possibili scenari di esecuzione (computazioni). La correttezza non si dimostra provando qualche input, ma verificando proprieta che devono valere in tutti gli scenari.
Le proprieta di correttezza si dividono in due grandi famiglie:
Proprieta di sicurezza: "le cose cattive non devono mai accadere". Una proprieta P deve essere vera in ogni stato di ogni computazione. Sono tipicamente espresse come invarianti. Esempi fondamentali:
Proprieta di vitalita: "le cose buone devono prima o poi accadere". Per ogni computazione, deve esistere qualche stato in cui P e vera. Esempi:
Fairness: una proprieta di liveness che richiede che "qualcosa di buono accada infinitamente spesso". Dipende dalla politica di scheduling. Tre livelli:
La lezione del 20 marzo riassume le stesse tre famiglie in forma tabellare:
| Proprieta | Descrizione | Esempio |
|---|---|---|
| Safety | "Le cose brutte non accadono mai" — sempre vera in ogni stato | Mutua esclusione: ¬(p3 ∧ q3) |
| Liveness | "Le cose buone accadono prima o poi" — vera in qualche stato futuro | Assenza starvation: p2 → ⋄ p3 |
| Fairness | "Ogni processo ottiene il suo turno infinite volte" | Scheduling: ogni azione eligible viene eseguita |
Distinguere safety da liveness e saper riconoscere esempi concreti: la mutua esclusione e una proprieta di safety; l'assenza di starvation e una proprieta di liveness. La fairness e un'ipotesi sullo scheduler che puo trasformare alcune proprieta da false a vere.
Il professore introduce la Logica Temporale Lineare (LTL), formalizzata da Amir Pnueli nel 1977, come linguaggio per esprimere proprieta di correttezza dei programmi concorrenti. LTL estende la logica proposizionale con operatori temporali che permettono di ragionare sull'evoluzione degli stati nel tempo: proprieta che devono valere in tutti gli stati di tutte le computazioni.
| Operatore | Notazione | Significato |
|---|---|---|
| Always (box) | [] P o G P | P e vera in tutti gli stati futuri (safety) |
| Eventually (diamond) | <> P o F P | P diventa vera prima o poi in qualche stato futuro (liveness) |
| Next | O P o X P | P e vera nel prossimo stato |
| Until | P U Q | P e vera fino a quando Q diventa vera (inclusivo) |
| Weak Until | P W Q | Come Until, ma Q puo anche non diventare mai vera |
La lezione del 13 marzo riprende gli stessi operatori in forma discorsiva, utile per fissarne la semantica:
[] p (box, always, G p): p e vera in tutti gli stati futuri. Specifica proprieta di safety.<> p (diamond, eventually, F p): p diventa vera in qualche stato futuro. Specifica proprieta di liveness.O p (next, X p): p vale nel prossimo stato.p U q (until): prima o poi q diventa vera, e fino a quel momento p e sempre vera.p W q (weak until): come U, ma q non e obbligato a diventare vero. Se non lo diventa, p deve rimanere vero indefinitamente.Il professore mostra come si esprimono le proprieta di correttezza in LTL:
[] !(p3 && q3) — mai entrambi in CS.[](p2 -> <> p3) — ogni volta che P vuole entrare in CS, prima o poi ci riesce.| Proprieta | Formula LTL |
|---|---|
| Mutua esclusione | [] !(p3 ∧ q3) |
| Assenza di starvation per P | [](p2 → <> p3) |
| 1-bounded overtaking | tryp → (!CSq) W (CSq W ((!CSq) W CSp)) |
Saper scrivere in LTL: mutua esclusione []!(p3&&q3), progress/starvation [](p2-><>p3), e capire la differenza fra []<>P (infinitamente spesso) e <>[]P (stabilizzazione). Duality: ![]P = <>!P.
W (weak until). E un esempio di come LTL possa specificare proprieta piu fini della semplice assenza di starvation.
Il model checking e la tecnica piu importante per la verifica automatica di proprieta di correttezza nei sistemi concorrenti. La strategia: esplorare esaustivamente l'intero spazio degli stati del sistema e verificare se certe proprieta (espresse come formule LTL) sono soddisfatte.
Il model checking non si applica solo al software, ma anche all'hardware (Intel lo adotto dopo il Pentium Bug del 1994) e ai sistemi mission-critical (NASA dopo il Mars Polar Lander incident del 1999). Altre applicazioni citate: Amazon Web Services per i propri sistemi cloud.
SPIN e il model checker piu importante storicamente, usato sia in ambito accademico che industriale.
PROMELA e il linguaggio di modellazione per SPIN, con costrutti limitati e pensati per descrivere modelli di sistemi concorrenti.
Esempio di PROMELA per Dekker:
/* Dekker in PROMELA — struttura */
byte turn;
bool wantp, wantq;
active proctype P() {
do :: skip -> /* NCS */
wantp = true;
do :: wantq ->
turn = 1;
wantp = false;
(turn == 1);
wantp = true;
:: else -> break od;
/* CS */
wantp = false
od
}
JPF (Java Path Finder) e un model checker specializzato per la verifica di programmi Java, sviluppato dalla NASA.
E una JVM speciale che esegue i programmi lungo tutti i possibili percorsi di esecuzione, verificando la violazione di proprieta come deadlock, eccezioni non catturate, violazioni di bound.
Se trova un errore, riporta l'intera esecuzione che ha portato all'errore.
TLA+ (Temporal Logic of Actions, Leslie Lamport) e un linguaggio formale di specifica basato su matematica discreta (teoria degli insiemi e predicati).
Usato per specificare e verificare sistemi reali complessi — es.: Amazon Web Services lo ha adottato per i suoi sistemi cloud.
Una specifica TLA+ descrive l'insieme di tutti i possibili comportamenti legali (trace) del sistema.
PlusCal (ex +CAL) e un linguaggio algoritmico basato su TLA+: si scrive un algoritmo come in un linguaggio di programmazione, viene tradotto in TLA+, e poi verificato con il model checker TLC.
Il grande problema del model checking e la dimensione dello spazio degli stati, che cresce esponenzialmente con il numero di processi e azioni: sistemi reali possono avere miliardi o trilioni di stati. Tecniche allo stato dell'arte includono:
Un approccio alternativo al model checking, puramente matematico: invece di esplorare tutti gli stati, si dimostra che una proprieta (invariante) — una formula che deve essere invariabilmente vera in ogni punto di ogni computazione, come [] ¬(p3 ∧ q3) — vale per induzione sugli stati di tutte le computazioni:
La dimostrazione induttiva non richiede di generare tutti gli stati, ma richiede la formulazione di un invariante induttivo sufficientemente forte — un'abilita che il professor Ricci descrive come "un'arte difficile".
Questo approccio e supportato da sistemi deduttivi automatici (theorem provers) e da linguaggi di specifica formale come TLA+ di Leslie Lamport, usato ad esempio da Amazon Web Services per verificare sistemi reali. TLA+ permette di specificare sia le proprieta di correttezza (il "cosa") sia il progetto del sistema (il "come"), e supporta model checking tramite TLC. Include PlusCal, un linguaggio algoritmico che viene tradotto automaticamente in TLA+.
Safety property: basta trovare UNO stato che viola la proprieta per confutarla. Liveness property: non basta controllare stati uno per uno, bisogna analizzare tutti gli scenari completi. La verifica di liveness e piu complessa sia in teoria che in pratica.
Il professore conclude che capire il senso di queste cose — perche un algoritmo funziona, perche e stato progettato in un certo modo — e essenziale per un ingegnere, ancora di piu in un'epoca in cui l'AI genera codice. Saper riconoscere se la soluzione proposta da uno strumento automatico e corretta o meno e una competenza fondamentale.
Il professor Ricci presenta alcuni strumenti concreti per la verifica formale dei programmi concorrenti, inquadrandoli nel panorama piu ampio del model checking.
SPIN e uno dei model checker piu utilizzati, sia in ambito accademico che industriale. PROMELA (Process Meta-Language) e il linguaggio di modellazione per SPIN. Permette di descrivere sistemi concorrenti con un insieme limitato di costrutti, appositamente progettati per costruire modelli astratti. SPIN puo verificare proprieta LTL sullo spazio degli stati del modello PROMELA.
Il professore mostra un esempio di Dekker in PROMELA, dimostrando come si modella un algoritmo di mutua esclusione e lo si verifica con SPIN.
Java PathFinder (JPF) e specializzato per Java: verifica il codice Java reale, non un modello astratto. Funziona come una JVM speciale che esegue il programma lungo tutti i possibili percorsi di esecuzione. Piu lento di una JVM normale, ma in compenso puo trovare bug che sfuggirebbero a qualsiasi test. NASA lo usa per software mission-critical. Il professore passa al laboratorio proprio per far provare JPF agli studenti.
TLA+ (Temporal Logic of Actions), introdotto da Leslie Lamport, e un linguaggio di specifica formale basato su logica temporale e insiemistica. Usato da Amazon Web Services per verificare i propri sistemi distribuiti (es. meccanismi di consenso, protocolli di replicazione). PlusCal e un linguaggio algoritmico basato su TLA+ che viene tradotto automaticamente in specifiche TLA+ verificabili con il model checker TLC.
Il professore mostra un esempio di Peterson in PlusCal.
La scelta fra SPIN/PROMELA, JPF e TLA+ dipende dal contesto: SPIN richiede di modellare il sistema in PROMELA (astrazione), JPF lavora direttamente sul codice Java (nessuna astrazione, ma limitato a Java), TLA+ e per specifiche di piu alto livello (protocolli, architetture).
SPIN è un model checker ampiamente utilizzato sia nella ricerca accademica che nello sviluppo industriale. È estremamente efficiente e viene usato per modellare e analizzare sistemi concorrenti e distribuiti. SPIN e stato usato dalla NASA (dopo l'incidente del Mars Polar Lander del 1999) e da Intel (dopo il bug Pentium del 1994).
PROMELA (Process Meta Language) è il linguaggio usato in SPIN per scrivere modelli di programmi concorrenti. Ha un numero limitato di costrutti, specificamente progettati per costruire modelli di sistemi concorrenti.
Il professor Ricci mostra un esempio: l'algoritmo di Dekker in PROMELA.
/* Dekker's algorithm in PROMELA (esempio dalle slide) */
bool wantp, wantq;
byte turn;
proctype P() {
do :: /* loop forever */
wantp = true;
do :: wantq ->
if :: turn == 2 ->
wantp = false;
(turn == 1); /* await */
wantp = true;
:: else -> skip
fi
:: else -> break
od;
/* critical section */
turn = 2;
wantp = false;
/* non-critical section */
od
}
proctype Q() {
do :: /* loop forever */
wantq = true;
do :: wantp ->
if :: turn == 1 ->
wantq = false;
(turn == 2); /* await */
wantq = true;
:: else -> skip
fi
:: else -> break
od;
/* critical section */
turn = 1;
wantq = false;
/* non-critical section */
od
}
Nella lezione del 16 marzo il professore mostra una variante con active proctype ed etichette esplicite:
// Esempio: algoritmo di Dekker in Promela
bool wantp = false, wantq = false;
byte turn = 1;
active proctype P() {
do :: true ->
wantp = true;
do :: !wantq -> break;
:: else ->
if :: turn == 1 -> skip;
:: else -> wantp = false;
(turn == 1);
wantp = true;
fi;
od;
CS: skip; // sezione critica
turn = 2;
wantp = false;
od
}
JPF è un model checker specializzato per la verifica di programmi Java, sviluppato dalla NASA. Funziona come una JVM speciale che esegue i programmi teoricamente lungo tutti i possibili percorsi di esecuzione (tutti gli scenari), verificando violazioni di proprietà come:
Se trova un errore, JPF riporta l'intera sequenza di esecuzione che porta all'errore. È un progetto open-source (http://javapathfinder.sourceforge.net/).
Il professor Ricci cita un caso storico: l'uso del model checking da parte di Intel dopo il Pentium Bug del 1994, e l'adozione da parte della NASA dopo l'incidente del Mars Polar Lander (1999). Questi eventi hanno dimostrato l'importanza della verifica formale nei sistemi critici.
TLA+ (Temporal Logic of Actions) è un linguaggio formale di specifica introdotto da Leslie Lamport, basato su semplice matematica discreta (teoria degli insiemi e predicati). È usato per specificare e verificare sistemi reali complessi, come ad esempio Amazon Web Services.
PlusCal (ex +CAL) è un linguaggio algoritmico basato su TLA+, pensato per scrivere algoritmi come si farebbe in un linguaggio di programmazione. Un algoritmo PlusCal viene tradotto in una specifica TLA+, che può essere verificata con il model checker TLC.
Il professor Ricci mostra l'esempio dell'algoritmo di Peterson in PlusCal:
-- algorithm Peterson {
variables flag = [i \in {0,1} |-> false], turn = 0;
process (proc \in {0,1})
a0: while (true) {
a1: flag[self] := true;
a2: turn := Not(self);
a3a: if (flag[Not(self)]) { goto a3b } else { goto cs };
a3b: if (turn = Not(self)) { goto a3a } else { goto cs };
cs: skip; \* critical section
a4: flag[self] := false;
}
}
}
Le specifiche TLA+ descrivono l'insieme di tutti i possibili comportamenti legali (tracce di esecuzione) di un sistema. Possono descrivere sia le proprietà di correttezza desiderate (il "cosa") sia il progetto del sistema (il "come"). L'obiettivo è dimostrare che il progetto implementa correttamente le proprietà desiderate.
Esempio di verifica di mutua esclusione in TLA+:
MutualExclusion == (pc[0] /= "cs") \/ (pc[1] /= "cs")
theorem Spec => []MutualExclusion
Il modello TLC verifica che questo invariante sia vero in tutti gli stati raggiungibili. Per l'algoritmo di Peterson, con solo 146 stati raggiungibili, la verifica è immediata. Per algoritmi più complessi, con miliardi di stati, TLC non può più verificare esaustivamente e serve una dimostrazione deduttiva.
Lo stato dell'arte nella verifica, riassunto dal professore: per sistemi piccoli, SPIN o JPF possono esplorare esaustivamente tutti gli stati. Per sistemi grandi si usa il model checking simbolico o la dimostrazione induttiva di invarianti. TLA+ combina entrambi gli approcci.
Una parte significativa del laboratorio e dedicata all'installazione di Java PathFinder (JPF), un model checker sviluppato dalla NASA per la verifica di programmi Java. JPF e una JVM speciale che esegue il programma esplorando tutti i possibili scenari di esecuzione, verificando proprieta come assenza di deadlock, eccezioni non catturate, violazioni di assert.
Il professore mostra il metodo piu agile per installare JPF: tramite Docker. I passaggi descritti nel repo del corso (lab activity 3) prevedono:
labactivity03.eclipse-temurin:11 (JDK 11 gia installato).docker compose build (cioe docker-compose build) per costruire l'immagine.docker compose up per avviare il container.Il comando docker compose build puo richiedere molto tempo (il professore parla di "un quarto d'ora"). Inoltre, durante il laboratorio emergono problemi con gradlew a causa di caratteri di controllo nascosti: se il container da errore, provare a sostituire il file gradlew usando sed per rimuovere caratteri anomali e poi renderlo eseguibile con chmod +x gradlew.
# Struttura dei comandi per JPF via Docker (dal repo del corso)
docker compose build # costruisce l'immagine
docker compose up # avvia il container
# Dentro il container:
./gradlew clean build # compila i sorgenti
# Per eseguire JPF:
./runjpf config-file.jpf
Il Dockerfile di JPF parte da un'immagine base con JDK 11, copia i sorgenti di JPF, li compila e prepara l'ambiente. Il docker-compose.yml monta una directory locale (pcd-jpf) come volume, in modo che i file compilati delle esercitazioni siano accessibili al model checker.
Una volta avviato il container, per eseguire Java PathFinder su un programma serve un file di configurazione con estensione .jpf. Questi file specificano:
target: la classe Java che contiene il main da analizzare.classpath: dove trovare i bytecode compilati (tipicamente target/classes per progetti Maven).sourcepath: la directory dei sorgenti.Il professore mostra un file d'esempio test-sequential.jpf che punta alla classe pcd.lab03.jpf.TestSequential. La struttura tipica e:
# test-sequential.jpf
target = pcd.lab03.jpf.TestSequential
classpath = ${jpf}/build:/path/to/target/classes
sourcepath = /path/to/src
Il comando per lanciare JPF e: runjpf test-sequential.jpf. JPF esegue il programma esplorando sistematicamente tutti gli interleaving, partendo dallo stato iniziale e costruendo incrementalmente il grafo degli stati raggiungibili.
JPF non e un interprete Java normale: e una JVM specializzata per il model checking. Invece di eseguire un singolo percorso, esplora tutti i percorsi possibili, gestendo il nondeterminismo delle schedule dei thread. Se trova una violazione (es. un deadlock), produce l'intera traccia di esecuzione che porta all'errore.
Il professore conclude la parte teorica mostrando come JPF analizza un programma Java. L'esempio e un programma sequenziale semplice (test-sequential) che serve per verificare il setup. Ma l'obiettivo a regime e usare JPF per verificare programmi concorrenti, come quelli della sezione critica.
JPF esplora il grafo degli stati del programma in esecuzione. Per ogni punto di scelta (scheduling point), prova tutte le possibili selezioni del thread da eseguire successivamente. Se durante l'esplorazione trova una violazione (deadlock, eccezione non catturata, asserzione fallita, o violazione di una proprieta LTL specificata), si ferma e produce un controesempio: la sequenza di stati che porta dall'inizio alla violazione.
Il model checking e complementare al testing. Il testing verifica scenari specifici e puo trovare bug, ma non puo dimostrare l'assenza di errori. Il model checking, se puo esplorare l'intero spazio degli stati, fornisce una garanzia formale di correttezza. In pratica, lo spazio degli stati e spesso troppo grande, ma JPF usa tecniche di riduzione (partial order reduction, state matching) per renderlo fattibile.
Il professore anticipa che nei prossimi laboratori si usera JPF per verificare i programmi scritti durante il corso, compresi quelli con synchronized, lock espliciti, e i vari algoritmi di sincronizzazione discussi nella parte di teoria.
I programmi da verificare con JPF devono essere compilati con JDK 11 (o versione supportata). Il professore mostra che nel pom.xml del progetto Maven si specifica javac 11 come target, per generare bytecode compatibile. I file .class finiscono in target/classes (non in build), e questo va specificato nel classpath del file .jpf.
Il professore conclude con una riflessione fondamentale: i programmi concorrenti non si possono debuggare nel modo tradizionale. Ogni esecuzione produce probabilmente uno scenario diverso. Un test che passa oggi potrebbe fallire domani sulla stessa macchina, o su una macchina diversa.
La verifica formale (model checking, dimostrazioni induttive) non e un lusso accademico, ma una necessita ingegneristica. Progetti critici (NASA, Amazon, Intel) usano queste tecniche non solo per trovare bug, ma per certificare l'assenza di determinate classi di errori.
Metodi formali principali:
Safety properties sono piu facili da verificare (basta trovare uno stato che viola la proprieta per dimostrare che non vale). Liveness properties sono piu complesse (bisogna analizzare tutti gli scenari possibili, non solo stati isolati). All'esame, distinguiti sempre tra safety e liveness — e una domanda classica.
Il pattern check-and-act consiste nel controllare una condizione (es. "if (counter > 0)") e poi eseguire un'azione che dipende da quella condizione (es. "counter--"). In ambito concorrente, il check e l'act sono due operazioni distinte: tra la lettura della condizione e l'esecuzione dell'azione, un altro thread puo modificare lo stato, invalidando la condizione. La soluzione e rendere atomico l'intero blocco con synchronized o un lock.
(1) Mutua esclusione: le risorse non sono condivisibili. (2) Hold and wait: un processo tiene una risorsa mentre ne richiede un'altra. (3) No preemption: le risorse non possono essere sequestrate. (4) Attesa circolare: esiste un ciclo di processi che attendono risorse l'uno dall'altro. Tutte e quattro devono valere simultaneamente perche si verifichi un deadlock.
synchronized e ReentrantLock?synchronized e un costrutto intrinseco del linguaggio: il lock viene acquisito e rilasciato automaticamente (anche in caso di eccezioni), e non e possibile dimenticarsi di rilasciarlo. ReentrantLock e un lock esplicito che richiede uno sbLocco esplicito (pattern try/finally obbligatorio). Offre funzionalita aggiuntive: lockInterruptibly() (risponde agli interrupt), tryLock() (non bloccante), tryLock(timeout) (con time-out), e la possibilita di creare lock equi (fair).
Mutua esclusione: [] !(p3 && q3) (mai entrambi in CS). Assenza di starvation (progress): [](p2 -> <> p3) (ogni volta che P vuole entrare in CS, prima o poi ci riesce). La prima e una proprieta di safety (e vera in ogni stato), la seconda di liveness (richiede uno stato futuro in cui la proprieta diventa vera).
JPF e una JVM specializzata per il model checking: invece di eseguire il programma lungo un singolo percorso (come una JVM normale), esplora tutti i possibili percorsi di esecuzione generati dai diversi interleaving dei thread. Per ogni percorso, verifica le proprieta specificate (assenza di deadlock, eccezioni non catturate, invarianti). Se trova una violazione, produce la traccia completa che vi conduce. JPF e piu lento di una JVM normale perche esplora esponenzialmente piu percorsi.
Per m processi ciascuno con n azioni atomiche, il numero di scenari e dato dalla formula (m*n)! / (n!)^m (coefficiente multinomiale). Ad esempio, 2 processi con 4 azioni generano 70 scenari; 3 processi con 4 azioni generano 34.650 scenari. Questa crescita esplosiva e nota come state-space explosion ed e la ragione per cui servono tecniche avanzate (riduzione dell'ordine parziale, hashing degli stati, model checking simbolico) per rendere la verifica fattibile.
Una classe e thread-safe se puo essere usata correttamente in qualsiasi contesto, sequenziale o concorrente, senza che chi la usa debba aggiungere sincronizzazione. La correttezza e garantita internamente dall'implementazione. Le classi della libreria standard Java (es. ArrayList) non sono thread-safe per scelta progettuale: la sincronizzazione ha un costo, e in contesti puramente sequenziali sarebbe sprecata. Classi con stato immutabile (nessun metodo modifica lo stato) sono thread-safe per definizione.
Weak fairness: un'azione condizionale la cui condizione diventa e rimane vera viene prima o poi eseguita. Strong fairness: un'azione condizionale la cui condizione diventa vera infinitamente spesso (anche se non rimane vera) viene prima o poi eseguita. La strong fairness e piu difficile da garantire per lo scheduler. Entrambe presuppongono la fairness incondizionata (ogni azione non condizionale viene prima o poi eseguita).