• Non ci sono risultati.

Concorrenza e sincronizzazione

N/A
N/A
Protected

Academic year: 2021

Condividi "Concorrenza e sincronizzazione"

Copied!
25
0
0

Testo completo

(1)

Ingegneria del software

Ing. Jody Marca – jody.marca@polimi.it

(2)

Cosa faremo oggi

Concorrenza

Sincronizzazione

(3)

Concorrenza

Nei programmi Java fino ad ora sviluppati è presente solo un flusso di esecuzione sequenziale.

La concorrenza ci permette di gestire nel medesimo programma più flussi di esecuzione

La concorrenza è fondamentale per creare applicazioni interattive o client server multiutente

(4)

I Thread

La concorrenza si può ottenere attraverso una gestione multi-processo o multi-thread

Solitamente si usano i thread poiché sono più leggeri e utilizzano una comunicazione a memoria condivisa

In Java un thread può essere creato utilizzando uno dei seguenti metodi

Estensione della classe java.lang.Thread

Implementazione dell’intefaccia java.lang.Runnable

(5)

Thread – Estensione della classe Thread

Definire una classe che estende la classe Thread e contiene un metodo run()

public class SimpleThreadMode1 extends Thread {

public void run() { //Contiene il codice che eseguirà il thread } }

Istanziare la classe

SimpleThreadMode1 thread = new SimpleThreadMode1();

Avviare il thread chiamando il metodo start() il quale richiamerà il metodo run() definito nel thread

thread.start();

(6)

Thread – Implementazione dell’interfaccia Runnable

Definire una classe che implementa l’interfaccia Runnable e contiene un metodo run()

public class SimpleThreadMode2 implements Runnable {

public void run() { //Contiene il codice che eseguirà il thread } }

Instanziare la classe

SimpleThreadMode2 runTask = new SimpleThreadMode2();

Instanziare un thread

Thread thread = new Thread(runTask);

Avviare il thread chiamando il metodo start() il quale richiamerà il metodo run() definito nel thread

thread.start();

(7)

Aspettare il termine di un thread

Alcune azioni necessitano una certa serializzazione nell’avvio dei thread.

public class SimpleThreadJoin implements Runnable {

public void run() {

System.out.println("Ciao sono il thread" );

for(int i=0; i<10; i++){

System.out.println("thread - stampa numero: "+(i+1));

try { Thread.sleep(500); }

catch (InterruptedException e) {}

System.out.println("thread - Termino");

}

(8)

Aspettare il termine di un thread / 2

public static void main(String[] args) {

SimpleThreadJoin threadTask = new SimpleThreadJoin();

Thread thread = new Thread(threadTask);

thread.start();

try {

thread.join(); //Attendo che termini il thread } catch (InterruptedException e) {}

System.out.println("Creazione dei threads terminata");

} }

(9)

Interrupts

Gli Interrupts sono utilizzati per bloccare alcune azioni che sta eseguendo un thread.

public class SimpleThreadInterrupt implements Runnable {

public void run() {

System.out.println("Ciao sono il thread” );

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

System.out.println("thread - Ricevuto un interrupt");

}

System.out.println("thread - Termino");

}

(10)

Interrupts \ 2

public static void main(String[] args) {

SimpleThreadInterrupt threadTask = new SimpleThreadInterrupt();

Thread thread = new Thread(threadTask);

thread.start();

System.out.println("Creazione del thread terminata");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {}

System.out.println("Lancio interrupt al thread");

thread.interrupt();

}

}

(11)

Sincronizzazione

Sappiamo che l’esecuzione di più threads concorrenti non è deterministica.

Cosa succede se dobbiamo gestire dei dati condivisi tra più thread???

È necessario introdurre un meccanismo di Lock che ci

permetta di essere sicuri che l’accesso alla modifica dei dati condivisi sia effettuato in modo mutuamente

esclusivo

(12)

Sincronizzazione / 2

Dobbiamo implementare un accesso esclusivo ai dati.

Java ci fornisce la possibilità di sincronizzare l’accesso dei thread a dei metodi o a determinate porzioni di codice

Immaginiamo di implementare la cassa del progetto

magazzino implementato nella prima lezione e di gestire i diversi utenti del magazzino utilizzando dei Thread

Dobbiamo assicurare che le operazioni siano di modifica del saldo siano sincronizzate

(13)

Synchronized

Utilizziamo la parola chiave synchronized per sincronizzare l’accesso ai metodi

public class Cassa {

private Double saldo = null;

public Cassa(Double saldo) { this.saldo = saldo; }

public synchronized void vendi(Double daVersare){

saldo = saldo + daVersare;}

public synchronized void acquista(Double daPrelevare){

saldo = saldo – daPrelevare;}

}

(14)

Synchronized /2

La parola chiave synchronized permette un accesso

sincronizzato a tutti e solo i metodi definiti synchronized

Ci garantisce quindi che se un thread sta accedendo a un metodo synchronized nessun altro thread possa

accedere a qualsiasi metodo synchronized della classe stessa. Non ci garantisce pero che non possa accedere in modo concorrente ad altri metodi della classe non definiti synchronized.

I costruttori non possono essere synchronized

(15)

Synchronized Statements

Un altro modo di gestire la sincronizzazione in Java è utilizzare dei Synchronized Statements

In questo caso è necessario dichiarare l’oggetto su si vuole porre il lock.

I Synchronized Statements sono utili per implementare

una gestione della sincronizzazione a granularità più fine Usate i Synchronized Statements con cura dovete essere

assolutamente sicuri di ciò che state facendo

(16)

Synchronized Statements - Esempio 1

Eliminiamo synchronized dai metodi della classe cassa sostituendoli con i Synchronized Statements

public void vendi(Double daVersare){

Double saldoPre = null, saldoPost = null;

synchronized(this) {

saldoPre = saldo;

saldo = saldo + daVersare;

}

saldoPost = saldo;

System.out.println("Venduto "+daVersare+" saldo precedente

"+saldoPre+" attuale "+saldoPost);

}

(17)

Synchronized Statements - Esempio 2

Immaginiamo ora di dover gestire due variabili che

permettano di memorizzare il totale delle vendite e degli acquisti.

Non è necessario porre il lock su tutta la classe ma è

sufficiente sincronizzare le letture e le scritture relative ad ogni singola variabile

(18)

Synchronized Statements - Esempio 2 /2

public class CassaSynchronizedStatements2 {

private Double vendite = null, acquisti = null;

//Definisco i lock da utilizzare per l’aggiornamento delle variabili private Object lockVendite = null, lockAcquisti = null;

public CassaSynchronizedStatements2(Double vendite, Double acquisti) { this.vendite = vendite;

this.acquisti = acquisti;

//Inizializzo gli oggetti da utilizzare come lock

lockVendite = new Object();

lockAcquisti = new Object();

}

(19)

Synchronized Statements - Esempio 2 /3

public void vendi(Double daVersare){

//Sincronizzo le operazioni sulle vendite

synchronized(lockVendite) {

vendite = vendite + daVersare;

} }

public void acquista(Double daPrelevare){

//Sincronizzo le operazioni sugli acquisti

synchronized(lockAcquisti) {

acquisti = acquisti + daPrelevare;

} }

}

(20)

Bloccare e risvegliare una esecuzione

Vogliamo inserire un vincolo sull’acquisto: è possibile acquistare solo se il saldo è positivo

Dobbiamo fare in modo che, qualora il saldo sia negativo, l’azione di acquisto venga temporaneamente sospesa

Per questo scopo si possono utilizzare i metodi:

wait() – interrompe temporaneamente l’esecuzione notify() (o notifyAll() )– risveglia l’esecuzione

(21)

Esempio di Wait – Notify

public class Cassa {

private Double saldo =0.0;

public synchronized void vendi(Double daVersare){

saldo = saldo + daVersare;

notifyAll();

}

public synchronized void acquista(Double daPrelevare) throws Exception{

while((saldo – daPrelevare )<=0.0) wait();

saldo = saldo – daPrelevare;

} }

(22)

© 2015 – Jody Marca – Threads

Problemi di sincronizzazione - Deadlock

Il Deadlock si verifica quando due o più threads restano bloccati per sempre poiché si aspettano a vicenda

Thread 1

public class ObjA{

private ObjB objB = null;

synchronized void call(){

……

objB.call();

……

} }

Thread 2

public class ObjB{

private ObjA objA = null;

synchronized void call(){

……

objA.call();

……

} }

(23)

Locking

Il locking mettere a disposizione uno strumento avanzato e potente che permette di sincronizzare

“l’accesso” in modo esclusivo a porzioni di codice Java

A differenza dei comuni Synchronized statement attraverso l’uso del locking è possibe “tentare” di acquisire il lock in modalità non bloccante attraverso l’operazione tryLock()

(24)

Esempio di Locking

public class SharedClass {

private Lock lock = null; //dichiaro il Lock

public SharedClass(){

this.lock = new ReentrantLock(); //inizializzo il Lock }

public void usaRisorsa() throws InterruptedException {

//provo ad acquisire il lock, se entro 5 secondi non lo ottengo // la funzione tryLock ritorna false e continuo

if(lock.tryLock(5, TimeUnit.SECONDS)){

… frammento di codice da eseguire in mutua esclusione … }

} }

(25)

Riassumendo: ciclo di vita di un thread

born

dead ready

blocked running

waiting wait notify

notifyAll

start

I/O lock

fine I/O fine lock

Riferimenti

Documenti correlati

• Invocando direttamente il metodo run( ) di un oggetto di tipo Runnable, non si attiva alcun thread ma si esegue il task definito dal metodo run( ) nel thread associato

• Il metodo run della classe di libreria Thread definisce l’insieme di statement Java che ogni thread (oggetto della classe) eseguirà.. concorrentemente con gli

Il programma in C riportato in seguito usa funzioni di libreria definite nello standard POSIX (supportato da quasi tutti i sistemi UNIX) per creare più thread e mandarli in

•  Per attivare il thread deve essere chiamato il metodo start() che invoca il metodo run() (il metodo run() non può essere. chiamato direttamente, ma solo

implementazione del concetto di monitor: un oggetto java è associato ad un mutex ricorsivo tramite il quale è possibile disciplinare gli accessi da parte di diversi thread allo

 La funzione DEVE ESSERE CHIAMATA su una variabile mutex che sia già bloccata e di proprietà del thread chiamante. In tal caso la funzione pthread_mutex_unlock sblocca la

▪ Assegnare memoria e risorse per la creazione di nuovi processi è oneroso; poiché i thread condividono le risorse del processo cui appartengono, è molto più con- veniente creare

 Wait (and act) for a single thread within a large group of