7

Sto guardando alcuni esempi di notifica/attesa e mi sono imbattuto in questo. Capisco che un blocco sincronizzato definisce essenzialmente una sezione critica, ma non presenta una condizione di competizione? Niente specifica quale blocco sincronizzato viene inserito per primo.Esiste una condizione di competizione in questo esempio? Se è così, come potrebbe essere evitato?

public class ThreadA { 
    public static void main(String[] args){ 
     ThreadB b = new ThreadB(); 
     b.start(); 

     synchronized(b){ 
      try{ 
       System.out.println("Waiting for b to complete..."); 
       b.wait(); 
      }catch(InterruptedException e){ 
       e.printStackTrace(); 
      } 

     System.out.println("Total is: " + b.total); 
     } 
    } 
} 

class ThreadB extends Thread { 
    int total; 

    @Override 
    public void run(){ 
     synchronized(this){ 
      for(int i=0; i<100 ; i++){ 
       total += i; 
      } 
      notify(); 
     } 
    } 
} 

uscita per il sito:

In attesa di b per completare ...

totale è: 4950

+0

Sì, in teoria il nuovo thread potrebbe 'notify()' prima del thread principale chiama 'wait()'. – overthink

+1

Non pensare a un blocco 'sincronizzato' come _preventing_ una gara. Limita semplicemente lo _scope_ della gara. È come una sezione di un corso automobilistico in cui la pista è un'auto troppo stretta per sorpassarsi a vicenda. Stanno ancora correndo, ma devono passare attraverso quella singola sezione. Lo stesso vale per il blocco 'sincronizzato': i thread non stanno correndo _in qui_, ma corrono per _get qui_, e corrono ovunque. –

+1

@jameslarge è stata un'analogia sorprendente! Non ci sono due thread nel blocco sincronizzato contemporaneamente, ma se non specificato esplicitamente non si può dire chi potrebbe arrivare prima, correggere? – trevalexandro

risposta

5

A destra, non è garantito quale thread verrà eseguito per primo. Il thread b potrebbe fare la sua notifica prima che il thread principale inizi ad aspettare.

In aggiunta a ciò, un thread può tornare in attesa senza essere stato notificato, quindi impostare un flag e verificarlo prima di entrare in attesa tecnicamente non è abbastanza buono. Si potrebbe riscrivere a qualcosa come

public class ThreadA { 
    public static void main(String[] args) throws InterruptedException { 
     ThreadB b = new ThreadB(); 
     b.start(); 

     synchronized(b){ 
      while (!b.isDone()) { 
       System.out.println("Waiting for b to complete..."); 
       b.wait(); 
      } 
      System.out.println("Total is: " + b.total); 
     } 
    } 
} 

class ThreadB extends Thread { 
    int total; 
    private boolean done = false; 

    @Override 
    public void run(){ 
     synchronized(this){ 
      for(int i=0; i<100 ; i++){ 
       total += i; 
      } 
      done = true; 
      notify(); 
     } 
    } 

    public boolean isDone() {return done;} 
} 

in modo che il thread principale attenderà b è fatto con il suo calcolo, indipendentemente da chi inizia per primo.

A proposito, la documentazione API consiglia di non eseguire la sincronizzazione sui thread. Il JDK si sincronizza sui thread per implementare il join di Thread #.Un thread che termina invia un notifyAll che riceve tutto ciò che si unisce a esso. Se dovessi chiamare notificare o notificareAll da una discussione su cui hai acquisito il blocco, è possibile che qualcosa su di esso possa tornare presto. Un effetto collaterale di questo è che se si rimuove la notifica il codice funziona allo stesso modo.

+0

rimuovere 'notify()' da ThreadB, e 'wait()' si sveglia ancora ...: D – ZhongYu

+0

@ bayou.io: sì, perché il thread invia una notifica quando finisce. –

+0

L'ho visto di nuovo, in teoria non potrei b informare prima che il thread principale sia in attesa? So che le probabilità sono ridotte a causa della concorrenza, ma ero curioso di pensare a questo errore @NathanHughes – trevalexandro

2

Sì, è una condizione di competizione. Niente impedisce a ThreadB di avviarsi, immettere il suo metodo di esecuzione e sincronizzarsi su se stesso prima che ThreadA inserisca il blocco sincronizzato (quindi in attesa indefinitamente). Tuttavia, è molto improbabile che accada mai, considerando il tempo necessario per l'avvio di una nuova discussione.

Il modo più semplice e più consigliato per gestire questo tipo di situazione è non scrivere la propria implementazione, ma scegliere di utilizzare un callable/futuro fornito da un Executor.

Per fissare questo caso particolare, senza seguenti norme:

  • impostare un 'finito' valore booleano impostato alla fine del blocco sincronizzato di ThreadB.
  • Se il valore booleano "terminato" è vero dopo aver inserito il blocco sincronizzato, non è necessario chiamare l'attesa.
2

Sì - è una gara su quale thread immette quale blocco sincronizzato per primo. Per la maggior parte degli scenari della gara, l'output e la risposta saranno gli stessi. Per uno, tuttavia, il programma si bloccherà:

  1. Principale inizia le chiamate b.start() e pianifica immediatamente.
  2. Il thread B si avvia, entra sincronizzato, chiama notify().
  3. principale entra nel suo blocco sincronizzato, chiamate attendere()

In questo caso, principale attenderà sempre dal filo b chiamato notificare prima principale bloccato wait().

Detto questo, questo è improbabile, ma con tutto il threading è necessario concludere che accadrà e quindi nel peggior momento possibile.

Problemi correlati