THREAD IN JAVA
MULTITHREADING
Il multithreading consiste nell’esecuzione contemporanea (reale o virtuale) di più thread appartenenti allo stesso processo. Il multithreading può essere:
- Collaborativo: i thread rimangono attivi fino a quando non terminano il task o cedono il controllo delle risorse occupate.
- Preventivo: la virtual machine può accedere ad un thread attivo e controllarlo attraverso un altro thread
Le Java Language Specification stabiliscono che la VM debba gestire i thread secondo lo scheduling preemptive (fixed-priority scheduling).
Ad ogni esecuzione della VM corrisponde un processo. Tutto quello che viene eseguito dalla VM corrisponde ad un thread.
Ogni thread si occupa di risolvere un determinato problema. Eseguire le operazioni in parallelo, consente di raggiungere il risultato finale in maniera più rapida. In un software multi-thread, i thread possono scambiarsi informazioni ed accedere a risorse condivise.
Esempio
Dobbiamo sviluppare una chat che funzioni così:
- il server di chat rimane in ascolto dei messaggi che arrivano su una porta
- per ogni nuovo utente connesso, viene creato un thread che si occupa di acquisire i messaggi inviati dall’utente ed inoltrarli agli altri utenti connessi In questo caso avremo un thread per ogni utente connesso. Vediamo un semplice esempio di multithreading.
Questo è il metodo main.
LA CONCORRENZA IN JAVA
La concorrenza è la possibilità di eseguire diverse parti (più task) di un programma in parallelo. Oggi, con i dispositivi dotati di diverse CPU o multicore è possibile parallelizzare i processi. La parallelizzazione consente di:
- utilizzare al meglio l’hardware
- ridurre il tempo di elaborazione
- migliorare l’esecuzione dei software
In java è possibile implementare la concorrenza tramite tre strumenti:
- i Thread: sono stati i primi strumenti messi a disposizione (erano presenti nella versione 1.0), quelli che useremo nel nostro esempio.
- il framework Executor, introdotto dalla versione 1.5.
- il framework Fork/Join, a partire dalla versione 1.7.
Utilizzo dei Thread
Per creare un Thread è necessario creare una classe che estenda la classe Thread o implementi Runnable e che implementi il metodo run(). Per avviare il thread è necessario invocare il metodo start() ed attendere il completamento del task con il metodo join(). Il metodo run() non ha parametri in ingresso e non restituisce valori, pertanto, per passare dei parametri al thread è possibile definire un costruttore personalizzato della nostra classe che riceva in ingresso i parametri necessari.
Vediamo il codice del metodo main.
Ritengo utile non affrontare gli altri due metodi ma passare immediatamente alla sincronizzazione fra Thread, argomento molto importante.
LA SINCRONIZZAZIONE
Cosa accade se due Thread accedono alla stessa istanza di una classe ed invocano i metodi? Cosa accade se invocando un metodo modifichiamo lo stato dell’oggetto?
Esempio
Supponiamo che Mario e Lucia siano contestatari di un conto corrente bancario. Mario e Lucia possono accedere allo stesso conto. Cosa succede se Mario e Lucia effettuano nello stesso momento la visualizzazione dell’estratto conto ed il prelievo da due bancomat diversi? In linea teorica, Mario e Lucia pensano che nel conto ci siano soldi a sufficienza, ma non è detto. Il doppio prelievo potrebbe portare ad un valore negativo del conto. Nell’esempio precedente Mario e Lucia sono due differenti thread che accedono alla stessa risorsa: il conto corrente. Per gestire situazioni in cui più thread accedono allo stessa risorsa è necessario utilizzare apposito meccanismi di sincronizzazione.
LA KEYWORD SYNCHRONIZED
La JVM supporta la sincronizzazione tramite la keyword synchronized In Java è possibile che un thread riesca a mantenere l’accesso esclusivo su una risorsa. In questo modo, nessun altro thread può accedervi fino a quando la risorsa non viene resa disponibile dal thread che l’ha bloccata.
package it.corso.java.syncronized; public class Cliente extends Thread { public static void main(String[] args)throws InterruptedException{ Cliente c1 = new Cliente("Mario", 1200); Cliente c2 = new Cliente("Lucia", 50); // Avvio i Threads c1.start(); c2.start(); // Attendo il completamento c1.join(); c2.join(); } private double sommaDaPrelevare; public Cliente(String nomeCliente, double sommaDaPrelevare) { super(); this.setName(nomeCliente); this.sommaDaPrelevare = sommaDaPrelevare; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " arriva al bancomat"); System.out.println("Quando arriva " + Thread.currentThread().getName() + " il saldo è: " + ContoCorrente.getInstance().getSaldo()); System.out.println("La somma che vuole prelevare " + Thread.currentThread().getName() + " : " + sommaDaPrelevare); try { ContoCorrente.getInstance().prelievo(sommaDaPrelevare); System.out.println(Thread.currentThread().getName() + " TUTTO OK PRELIEVO EFFETTUATO"); } catch (Exception e) { System.out.println(Thread.currentThread().getName() + "NON HAI SOLDI!!!"); String a = e.getMessage(); System.out.println(a); } } }
package it.corso.java.syncronized; import java.io.*; public class ContoCorrente { private static ContoCorrente cc; public static ContoCorrente getInstance() { if(cc == null) cc = new ContoCorrente(); return cc; } public double getSaldo() { double saldo = 0; BufferedReader br = null; try { File fin = new File(new File(".").getCanonicalPath() + File.separator + "db.txt"); br = new BufferedReader(new FileReader(fin)); String line = null; while ((line = br.readLine()) != null) { saldo = Double.parseDouble(line); break; } } catch (IOException e) { e.printStackTrace(); } finally { if(br != null) try { br.close(); } catch (IOException e) { e.printStackTrace(); } } return saldo; } public synchronized void prelievo(double somma) throws Exception { Thread.sleep(5000); BufferedWriter bw = null; FileWriter fw = null; try { double nuovoSaldo = getSaldo() - somma; if(nuovoSaldo > 0) { fw = new FileWriter(new File(".").getCanonicalPath() + File.separator + "db.txt"); bw = new BufferedWriter(fw); bw.write(nuovoSaldo+""); } else throw new Exception("Saldo insufficiente!"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bw != null) bw.close(); if (fw != null) fw.close(); } catch (IOException ex) { ex.printStackTrace(); } } } }
LINK AI POST PRECEDENTI
LINK AL CODICE SU GITHUB
ESECUZIONE DEL CODICE DI ESEMPIO
- Scaricare il codice da GITHUB, lanciare il file JAR con il seguente comando in Visual Studio Code, posizionandosi nella directory contenente il JAR.
java -jar –enable-preview CorsoJava.jar
- Oppure mettere in esecuzione il main che si trova nel file CorsoJava.java.
Scrivi un commento