2015-06-03 22 views
6

Immaginiamo Ho prossimi corsi:blocco sincronizzato annidati

public class Service { 
    public void transferMoney(Account fromAcct, Account toAcct, int amount) { 
     synchronized (fromAcct) { 
     synchronized (toAccount) { // could we use here only one synchronized block? 
      fromAcct.credit(amount); 
      toAccount.debit(amount); 
     } 
     } 
    } 
} 

class Account { 
    private int amount = 0; 

    public void credit(int sum) { 
    amount = amount + sum; 
    } 

    public void debit(int sum) { 
    amount = amount - sum; 
    } 
} 

Per esempio so che siamo riusciti a cambiare lo stato di fromAcct e toAcct oggetti solo in transferMoney metodo. Quindi potremmo riscrivere il nostro metodo con un blocco sincronizzato?

public class Service { 
private final Object mux = new Object(); 

public void transferMoney(Account fromAcct, Account toAcct, int amount) { 
     synchronized(mux) { 
     fromAcct.credit(amount); 
     toAcct.debit(amount); 
     } 
} 
} 
+3

A) No. B) Questo non è sicuro. http://stackoverflow.com/q/17268485/34397 – SLaks

+0

Cosa succede se hai due istanze 'Service'? – immibis

+0

Se si sta tentando di effettuare le transazioni 2 (credito + debito) transazionale, la sincronizzazione non è il modo per farlo. Se il tuo obiettivo è quello di assicurarti di non danneggiare il saldo di un account se più thread spostano denaro nello stesso account nello stesso momento, allora la sincronizzazione dovrebbe avvenire all'interno della classe Account. – sstan

risposta

4

A meno che tu non abbia una necessità molto particolare e insolita che non riesco a capire, mi sembra che il tuo obiettivo dovrebbe essere quello di proteggere il saldo del conto da eventuali corruzioni da parte di più thread che tentano di accreditare o addebitare un account allo stesso tempo.

Il modo per farlo sarebbe come questo:

public class Service { 
    public void transferMoney(Account fromAcct, Account toAcct, int amount) { 
     fromAcct.credit(amount); 
     toAccount.debit(amount); 
    } 
} 

class Account { 
    private final object syncObject = new Object(); 
    private int amount = 0; 

    public void credit(int sum) { 
     synchronized(syncObject) { 
      amount = amount + sum; 
     } 
    } 

    public void debit(int sum) { 
     synchronized(syncObject) { 
      amount = amount - sum; 
     } 
    } 
} 

Se il vostro obiettivo durante il trasferimento di denaro è quello di garantire sempre che si verifichino azioni sia del credito e di debito come una transazione, o atomico, quindi utilizzando sincronizzazione non è l'approccio giusto Anche in un blocco sincronizzato, se dovesse verificarsi un'eccezione, si perde la garanzia che entrambe le azioni si verifichino atomicamente.

L'implementazione delle transazioni è un argomento molto complesso, motivo per cui utilizziamo normalmente i database per farlo per noi.

EDIT: OP chiesto: Qual è la differenza tra il mio esempio (un blocco mux sincronizzato) e il vostro sincronizzato in classe Account?

Questa è una domanda giusta. Ci sono alcune differenze. Ma direi che la differenza principale è che, ironia della sorte, il tuo esempio su -synchronizes. In altre parole, anche se ora utilizzi un singolo blocco sincronizzato, le tue prestazioni saranno in effetti molto peggiori, potenzialmente.

consideri il seguente esempio: Hai 4 conti bancari diversi: Diamo loro un nome, B, C, D. E ora avete 2 trasferimenti di denaro che vengono avviate al tempo stesso esatto:

  1. trasferire denaro dal conto a a B. conto
  2. trasferimento
  3. di denaro dal conto C per tenere conto D.

penso che sarete d'accordo che, poiché i trasferimenti di denaro 2 si verificano a causa completamente separate s, che non ci dovrebbe essere alcun danno (nessun rischio di corruzione) nell'esecuzione di entrambi i trasferimenti di denaro allo stesso tempo, giusto?

Tuttavia, con il tuo esempio, i trasferimenti di denaro possono essere eseguiti solo uno dopo l'altro.Con il mio, entrambi i trasferimenti di denaro avvengono contemporaneamente, ma anche in sicurezza. Bloccherò solo se entrambi i trasferimenti di denaro tentano di "toccare" gli stessi account.

Ora immagina se usi questo codice per elaborare centinaia, migliaia o più trasferimenti di denaro simultanei. Quindi non c'è dubbio che il mio esempio eseguirà MOLTO meglio del tuo, pur mantenendo la sicurezza del thread e proteggendo la correttezza del saldo di un account.

In effetti, la mia versione del codice si comporta concettualmente molto più come il vostro codice di blocco sincronizzato 2 originale. Tranne i seguenti miglioramenti:

  • Corregge il potenziale scenario di deadlock.
  • L'intento è più chiaro.
  • Fornisce un migliore incapsulamento. (Il che significa che anche se qualche altro codice al di fuori del metodo transferMoney provasse ad addebitare o accreditare alcuni importi, conserverei comunque la sicurezza del thread, mentre non lo faresti. So che hai detto che non è il tuo caso, ma con la mia versione, il design lo garantisce assolutamente)
+0

credito prima di addebito? :) È colpa dell'OP se confondere credito e debito. – ZhongYu

+0

@SamStanojevic grazie per la risposta! Mi spiace non essere stato chiaro con te. Sì, il mio obiettivo era quello di proteggere il saldo del conto da essere danneggiato da più thread in esecuzione. Quindi, per questo scopo, come hai detto prima, dovrei usare sincronizzato nei metodi dell'account. Ma se uso sincronizzato nel metodo transferMoney, dovrebbe proteggere il mio codice da più thread in esecuzione? – Iurii

+0

@lurii: sì, se si utilizza sincronizzato nel metodo transferMoney, questo proteggerà il codice da più thread in esecuzione in quel blocco di codice allo stesso tempo. Ma non ti darà alcuna garanzia transazionale. È molto importante capire la fondamentale differenza tra sicurezza del filo e transazioni. Sono 2 cose completamente diverse, che sono anche realizzate in modo molto diverso. – sstan

2

Sembra che si desideri implementare la transazione tramite sincronizzazione. Non c'è niente di comune tra loro. La transazione garantisce l'integrità dell'operazione: esegue tutte le azioni o ripristina tutte le operazioni. La sincronizzazione assicura che i dati vengano modificati da un solo thread alla volta. Ad esempio, la transazione assicura che se prendi denaro da un account e non li metti in un altro, allora la prima azione viene annullata - i soldi non vengono ritirati. La sincronizzazione verifica che se due tizi diversi mettono 2 penny alla banca nello stesso momento, allora il banco avrà 4 penny e non solo 2 perché il tuo programma aggiunge soldi allo stesso account in base al valore precedente.