Visualizzazione dei risultati da 1 a 7 su 7

Discussione: synchronized

  1. #1
    Utente di HTML.it
    Registrato dal
    Jul 2013
    Messaggi
    11

    synchronized

    ciao, ho problemi a capire se ho capito bene la parola chiave synchronized in java

    vi spiego meglio, ho questa classe
    codice:
    public class BankAccount { // Classe dell’oggetto condiviso
    	private double balance;
    
    	public BankAccount(double balance) {
    		this.balance = balance;
    	}
    
    	public synchronized double getBalance() {
    		return balance;
    	}
    
    	public synchronized void deposit(double amount) {
    		balance += amount;
    	}
    
    	public synchronized void withdraw(double amount) throws RuntimeException {
    		if (amount > balance) {
    			throw new RuntimeException("Overdraft");
    		}
    		balance -= amount;
    	}
    
    	public synchronized void transfer(double amount, BankAccount destination) {
    		this.withdraw(amount);
    		destination.deposit(amount);
    	}
    }
    che gestisce un conto bancario con tutti i metodi sincronizzati

    questa invece è la classe che gestisce un thread che si occupa di fare operazioni di scambio tra 2 conti ( per testare)


    codice:
    package esercizio1;
    
    public class Transfer extends Thread {
    
    	private BankAccount a;
    	private BankAccount b;
    	private double d;
    
    	public Transfer(BankAccount a, BankAccount b, double d) {
    		this.setA(a);
    		this.setB(b);
    		this.d = d;
    	}
    
    	@Override
    	public void run() {
    		a.transfer(d, b);
    	}
    
    	public BankAccount getA() {
    		return a;
    	}
    
    	public void setA(BankAccount a) {
    		this.a = a;
    	}
    
    	public BankAccount getB() {
    		return b;
    	}
    
    	public void setB(BankAccount b) {
    		this.b = b;
    	}
    
    }
    questo invece è il main
    codice:
    public class Main {
    	public static void main(String[] args) {
    		BankAccount a = new BankAccount(100.0);
    		BankAccount b = new BankAccount(100.0);
    		Transfer x = new Transfer(a, b, 50.0);
    		Thread t = new Thread(x);
    		Transfer y = new Transfer(b, a, 10.0);
    		Thread t2 = new Thread(y);
    		t.start();
    		t2.start();
    		try {
    			Thread.sleep(500);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		System.out.println("Account a has $" + x.getA().getBalance());
    		System.out.println("Account b has $" + x.getB().getBalance());
    	}
    }

    Le mie domande sono le seguenti:

    nel main ho messo uno sleep dopo lo start dei thread e il print, l' output è sempre lo stesso ed è quello corretto mentre senza lo sleep a volte mi da risultati errati: è causato dal fatto che magari la stampa viene fatta prima che il thread finisca di lavorare?

    Se è come ho detto sopra allora per stare al sicuro dovrei usare il metodo join() al posto dello sleep() giusto? Oltre il fatto che il join libera eventuali lock sul thread main giusto?

    Ora la domanda che mi pone più dubbi:
    Il metodo transfer chiama a sua volta 2 metodi sincronizzati invocati su istanze diverse.
    io so che synchronized è re-entrant cioè che se un metodo sincronizzato chiama un altro metodo sincronizzato sullo stesso lock può andare avanti senza condizioni di stallo quindi mi chiedo io eseguendo i 2 metodi sincronizzati dentro il metodo transfer implicitamente chiedo già di avere entrambi i lock sui due conti quindi è inutile mettere la parola chiave synchronized sul metodo transfer? così facendo il thread che esegue transfer si blocca solo dentro il metodo transfer quando arriva a chiedere i lock per invocare gli altri due sottometodi giusto?

    E' giusto/meglio togliere synchronized dal metodo transfer? Quello che penso è che senza dichiarare esplicitamente transfer come synchronized viene sempre eseguito e si può bloccare a
    codice:
    this.withdraw(amount);
    se non si ha il lock su l' istanza this come anche si può bloccare solo dopo quando viene eseguito
    codice:
    destination.deposit(amount);
    e non si ha il lock sull' altra istanza... Insomma non viene bloccato a priori ma può iniziare a lavorare per bloccarsi poi...
    Se ho ragione visto che i metodo contengono poche righe dichiare metodi synchronized o il codice del metodo synchronized sui conti è uguale?

    Come avete capito ho un pò di confusione, potete illuminarmi su cosa cambia nei diversi casi?
    Grazie in anticipo,
    Luca

  2. #2
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284

    Re: synchronized

    Originariamente inviato da sirluca
    nel main ho messo uno sleep dopo lo start dei thread e il print, l' output è sempre lo stesso ed è quello corretto mentre senza lo sleep a volte mi da risultati errati: è causato dal fatto che magari la stampa viene fatta prima che il thread finisca di lavorare?
    Sì, e anzi, è anche possibile (senza sleep o join) che si arrivi ai 2 println finali prima ancora che il run() di uno (o entrambi) i thread inizi la sua esecuzione!

    start() su Thread fa solo una cosa: mette il thread in stato "runnable", non fa eseguire, di per sé, il run! È lo scheduler dei thread (in genere è lo scheduler del S.O.) che decide quando far partire effettivamente il thread.

    Originariamente inviato da sirluca
    Se è come ho detto sopra allora per stare al sicuro dovrei usare il metodo join() al posto dello sleep() giusto?
    Sì, join blocca il thread corrente (nel tuo caso sarebbe il "main" thread) in attesa della terminazione di un altro thread.

    Originariamente inviato da sirluca
    Oltre il fatto che il join libera eventuali lock sul thread main giusto?
    Che io sappia, join non rilascia lock (di questa "finezza", in questo momento non ne sono sicuro al 100% però ... verificherò magari sul "Java Concurrency in Practice" di Brian Goetz se dice qualcosa a riguardo).

    Originariamente inviato da sirluca
    Il metodo transfer chiama a sua volta 2 metodi sincronizzati invocati su istanze diverse.
    io so che synchronized è re-entrant cioè che se un metodo sincronizzato chiama un altro metodo sincronizzato sullo stesso lock può andare avanti senza condizioni di stallo
    Esatto, se ha già acquisito quel lock, non attende o perde tempo ulteriore.

    Originariamente inviato da sirluca
    quindi mi chiedo io eseguendo i 2 metodi sincronizzati dentro il metodo transfer implicitamente chiedo già di avere entrambi i lock sui due conti quindi è inutile mettere la parola chiave synchronized sul metodo transfer? così facendo il thread che esegue transfer si blocca solo dentro il metodo transfer quando arriva a chiedere i lock per invocare gli altri due sottometodi giusto?
    deposit/withdraw devono sicuramente, per forza essere, synchronized, perché le operazioni += e -= non sono "atomiche", sono composte di 3 sotto-operazioni in sequenza: leggi, somma/sottrai, scrivi. Se deposit/withdraw non fossero synchronized, ci sarebbe il pericolo di una "race condition" che potrebbe sballare il valore di balance.

    Originariamente inviato da sirluca
    E' giusto/meglio togliere synchronized dal metodo transfer?
    Il punto debole, come ho detto prima, sono i +=/-=. Vediamo invece transfer:

    Il thread x fa:
    a.transfer(50.0, b);

    e sostituendo i reference e valori, in pratica esegue:
    a.withdraw(50.0);
    b.deposit(50.0);


    Il thread y fa:
    b.transfer(10.0, a);

    e sostituendo i reference e valori, in pratica esegue:
    b.withdraw(10.0);
    a.deposit(10.0);



    Se transfer non fosse synchronized, potrebbero avvenire sequenze "intercalate" del tipo:

    a.withdraw(50.0); // thread x
    b.withdraw(10.0); // thread y
    b.deposit(50.0); // thread x
    a.deposit(10.0); // thread y


    oppure

    b.withdraw(10.0); // thread y
    a.withdraw(50.0); // thread x
    a.deposit(10.0); // thread y
    b.deposit(50.0); // thread x


    oppure ancora

    a.withdraw(50.0); // thread x
    b.withdraw(10.0); // thread y
    a.deposit(10.0); // thread y
    b.deposit(50.0); // thread x


    oppure ancora

    b.withdraw(10.0); // thread y
    a.withdraw(50.0); // thread x
    b.deposit(50.0); // thread x
    a.deposit(10.0); // thread y


    Ci sarebbero problemi? Se withdraw fallisce con RuntimeException, il balance non viene toccato e nemmeno il deposit successivo nel thread viene eseguito. E questo è quello che deve accadere se qualcosa non va.
    Prendiamo il 1° scenario: siccome x acquisisce il lock su a e y acquisisce il lock su b, sono oggetti/lock distinti e questo vuol dire che i due withdraw possono anche essere eseguiti in parallelo. Ma non si danno fastidio (ripeto: oggetti distinti)
    È chiaro che x per poter fare b.deposit(50.0); deve comunque aspettare che y rilasci il lock su b ... ma anche questo è ok.

    Invece se transfer è synchronized c'è un grosso potenziale problema: una situazione di "dead-lock" e tutto si pianterebbe.

    (questo è lo scenario 1)
    a.withdraw(50.0); // thread x acquisce lock su 'a' grazie al a.transfer
    b.withdraw(10.0); // thread y acquisce lock su 'b' grazie al b.transfer
    b.deposit(50.0); // thread x vorrebbe acquisire il lock su 'b' ma ora ce l'ha il thread y
    a.deposit(10.0); // thread y vorrebbe acquisire il lock su 'a' ma ora ce l'ha il thread x


    Morale: transfer NON deve essere synchronized.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  3. #3
    Utente di HTML.it
    Registrato dal
    Jul 2013
    Messaggi
    11
    Ti ringranzio per la risposta, hai detto in parole migliori quello che volevo chiedere e spero sia proprio così! Tra la tua analisi cmq ti voglio chiedere una cosa, se come diciamo noi si potrebbe togliere synchronized dal metodo transfer non sarebbe anche migliore? Nel tuo stesso esempio il thread potrebbe partire eseguendo i 2 withdraw , per poi bloccarsi solo al primo deposit, così facendo è più efficiente/veloce no?

  4. #4
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    ho fatto ragionamenti ulteriori, rileggi la mia risposta sopra
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  5. #5
    Utente di HTML.it
    Registrato dal
    Jul 2013
    Messaggi
    11
    Ho fatto il tuo stesso ragionamento e lo stavo per scrivere! Grazie ancora per la risposta!

    p.s. ragionando mi è anche venuto in mente che join deve per forza far rilasciare i lock acquisiti dal thread in cui è chiamato il join sennò supponendo che il main ha il lock sull' oggetto "a" e il thread pincopallo su cui è eseguito il join ha bisogno del lock su "a" si avrebbe un' attessa circolare causata dal fatto che il thread chiede il lock su "a" che è tenuto dal main che non lo rilascerà mai perchè aspetta che il thread pincopallo finisca..
    Spero di essermi spiegato bene

  6. #6
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Originariamente inviato da sirluca
    Ho fatto il tuo stesso ragionamento e lo stavo per scrivere! Grazie ancora per la risposta!


    Originariamente inviato da sirluca
    p.s. ragionando mi è anche venuto in mente che join deve per forza far rilasciare i lock acquisiti dal thread in cui è chiamato
    No, join non rilascia lock.

    Originariamente inviato da sirluca
    il join sennò supponendo che il main ha il lock sull' oggetto "a" e il thread pincopallo su cui è eseguito il join ha bisogno del lock su "a" si avrebbe un' attessa circolare causata dal fatto che il thread chiede il lock su "a" che è tenuto dal main che non lo rilascerà mai perchè aspetta che il thread pincopallo finisca..
    Se nel tuo main invece del

    Thread.sleep(500);

    metti:

    codice:
    synchronized(a) {
        t.join();
        t2.join();
    }
    Succede che il main-thread "tiene" il lock su 'a'. Ma il main-thread attende che t/t2 finiscano mentre ... i thread t/t2 attendono di acquisire il lock su 'a'.
    Risultato: dead-lock!

    Nota: in realtà racchiudendo con synchronized solo i join, è possibile che t/t2 riescano a terminare prima che il main-thread entri in questo blocco synchronized, è una ipotesi rara ma possibile.
    Se racchiudi con synchronized(a) { } sia start che join, il dead-lock è sicuro al 100%.

    Se succedono queste cose non è che join, secondo te, dovrebbe rilasciare i lock .... la colpa è del programmatore ...
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  7. #7
    Utente di HTML.it
    Registrato dal
    Jul 2013
    Messaggi
    11
    Ok grazie mille per le delucidazioni!

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.