Multithreading e concorrenza in Java

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Abbiamo visto quanto sia facile creare applicazioni multithreading in Java, ma questo non ci libera dai problemi intrinseci a questo modello di sviluppo.

Un problema tipico si ha quando due diversi attori competono su di un unica risorsa. Nell'esempio che vediamo ci sono due utenti che cercano di accedere allo stesso conto corrente per fare un prelievo. Una progettazione poco attenta dello scenario può portare al codice sottostante che, come vedremo, causa grossi problemi.

La classe RMAccount definisce il conto corrente che viene acceduto da Ryan e Monica. Parte con un valore di 100 e su di esso possono essere operati dei prelievi. Il nostro scopo sarebbe quello di evitare che il conto vada in rosso.

package Chap15;

public class RMAccount {
private int balance = 100;

public int getBalance() {
return balance;
}

public void withdraw(int amount) {
balance -= amount;
}
}


La classe RMJob definisce la classe runnable. Il main crea una istanza di RMJob e due thread (Ryan e Monica) che operano sullo stesso oggetto. L'oggetto RMJob ha un solo account, definito come variabile di istanza, il metodo run() itera per dieci volte un prelievo chiamando il metodo withdrawl() che dovrebbe operare il prelievo solo se il bilancio del conto lo permette.

Si controlla dunque che il bilancio sia maggiore della somma che si vuole prelevare e, solo se é il caso, si effettua il prelievo. Per rendere evidente la debolezza di questo approccio mettiamo in pausa il thread per mezzo secondo tra il test sul bilancio e l'effettivo prelievo.

package Chap15;

public class RMJob implements Runnable {

private RMAccount account = new RMAccount();

private boolean withdrawl(int amount) {
if(account.getBalance() >= amount) {
System.out.println(Thread.currentThread().getName() + ": withdrawing");
try {
System.out.println(Thread.currentThread().getName() + ": sleeping a bit");
Thread.sleep(500);
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": up again");
account.withdraw(amount);
System.out.println(Thread.currentThread().getName() + ": withdrawl done");
return true;
}

// can't withdraw
System.out.println(Thread.currentThread().getName() + ": out of money");
return false;
}

public void run() {
for (int i = 1; i < 10; ++i) {
if(withdrawl(10)) {
if(account.getBalance() < 0) {
System.out.println("Negative balance! ");
}
}
}
}

public static void main(String[] args) {
RMJob job = new RMJob();

new Thread(null, job, "Ryan").start();
new Thread(null, job, "Monica").start();
}
}

L'aver aggiunto lo sleep() rende il problema evidente. Un thread esegue il controllo, che riesce, ma prima che possa eseguire il prelievo, l'altro thread esegue nuovamente il controllo con successo. A questo punto solo uno dei due potrà prelevare correttamente i soldi dal conto, l'altro lo manderà inopinatamente in rosso.

Il nostro codice va ridisegnato: la risorsa condivisa deve essere protetta.

Nessun commento:

Posta un commento