2014-04-15 21 views
19

Sì, questa è una domanda accademica, so che la gente si lamenterà che non sto pubblicando alcun codice ma sono sinceramente colpito da questa domanda, davvero non so dove inizio. Gradirei davvero una spiegazione e magari qualche esempio di codice.thread java che accede all'oggetto esterno prima della sua creazione

Se un costruttore dell'oggetto inizia un nuovo filo che esegue il metodo run di un oggetto classe interna anonima, è possibile che questa nuova thread può accedere suo oggetto esterno circostante prima è stato completamente costruito e la sua campi completamente inizializzati. Come faresti a evitare che ciò accada, ?

+3

Qualsiasi cosa tu faccia prima dell'avvio di una discussione verrà letta come ti saresti aspettato.Creare il thread alla fine del costruttore (o preferibilmente avere il creatore dell'oggetto, il chiamante, avviare una discussione) –

risposta

20

Questo è chiamato "perdita di questo". Qui hai il codice

public class Test { 

    // this is guaranteed to be initialized after the constructor 
    private final int val; 

    public Test(int v) { 
    new Thread(new Runnable() { 
     @Override public void run() { 
     System.out.println("Val is " + val); 
     } 
    }).start(); 
    this.val = v; 
    } 

} 

Indovina quale sarà la stampa (potrebbe trattarsi di un thread). Ho usato un campo final sottolineare che l'oggetto si accede prima che sia stato completamente inizializzato (campi finali devono essere definitivamente assegnato dopo l'ultima riga di ogni costruttore)

Come si fa a recuperare

È non voglio passare this quando sei in un costruttore. Ciò significa anche che non si desidera chiamare metodi virtuali non finalizzati nella stessa classe (non statici, non privati) e non utilizzare le classi interne (le classi anonime sono classi interne), che sono implicitamente collegate all'integrazione istanza, quindi è come potevano accedere this.

+0

+1 Mi è piaciuto il 'finale' dettaglio, molto illustrativo! –

+1

Bella risposta, risponderò se rispondi alla tua stessa domanda "Indovina cosa stamperà". (un valore casuale o 0?) Inoltre non ottengo la parte "non si desidera chiamare metodi virtuali non definitivi nella stessa classe (non statici, non privati)": potresti fornire un esempio di quella? –

+1

@AndreiI stampa 0 o v in base allo scheduler dei thread. I metodi che possono essere sovrascritti dalle sottoclassi (cioè non-non-non-statiche non-private) si dice che siano * virtual * (la JVM invia la chiamata al runtime). Dal momento che accedono all'istanza della sottoclasse, possono essere eseguiti prima del costruttore della classe figlio. Vedi [questo esempio] (http://ideone.com/XnmmuX) – Raffaele

1

Quindi una situazione come questa?

public class MyClass { 
    private Object something; 
    public MyClass() { 
     new Thread() { 
      public void run() { 
       something = new Object(); 
      } 
     }.start(); 
    } 
} 

A seconda del codice effettivo utilizzato, il comportamento potrebbe variare. Questo è il motivo per cui i costruttori dovrebbero essere attentamente realizzati in modo tale che, ad esempio, non chiamino metodi non privati ​​(una sottoclasse potrebbe sovrascriverli, consentendo alla superclasse this di accedere da una sottoclasse prima che la superclasse sia completamente inizializzata). Sebbene questo particolare esempio riguardi una singola classe e un thread, è correlato al problema di perdite di riferimento.

+0

Ah, non stavo leggendo la domanda abbastanza attentamente ... – Kayaman

2

Non sono completamente d'accordo con la risposta di Pablos perché dipende in gran parte dal metodo di inizializzazione.

public class ThreadQuestion { 

    public volatile int number = 0; 

    public static void main(String[] args) { 
     ThreadQuestion q = new ThreadQuestion(); 
    } 

    public ThreadQuestion() { 
     Thread t = new Thread(new Runnable() { 

     @Override 
     public void run() { 
      System.out.println(number);    
     } 
     }); 

     try { 
     Thread.sleep(500); 
     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
     number = 1;  
     t.start(); 
    } 
} 

Quando si

  1. posto t.start() alla fine, i dati corretti viene stampato.
  2. posto t.start() prima del comando del sonno, stamperà 0
  3. rimuovere il comando sonno e posizionare t.start() prima della cessione può stampare 1 (non determinabile)

Gioca un gioco di mente su 3) si posso dire che un "piccolo" compito di 1 tipo di dati semplice funzionerà come previsto ma se si crea una connessione al database non si otterrà un risultato affidabile.

Non esitate a porre qualsiasi domanda.

+0

È possibile ma non accade necessariamente. Devi solo modificare il tempo di sospensione e, più in generale, il codice tra l'inizializzazione di attrubutes e l'avvio del thread –

+0

Ho cercato di evidenziare che alcuni risultati sono ** non determinabili **. La domanda iniziale ci offre molti punti in sospeso su quale direzione dovremmo approfondire. – Markus

+0

È possibile garantire che non si verificherà se il metodo 'start' di thread è l'ultima riga del costruttore. –

2

Vorrei cambiare il titolo della domanda, poiché i thread non accedono a loro stessi, ma il secondo al primo. Intendo: Hai un thread, creando un oggetto. All'interno del costruttore per questo oggetto, si dichiara una classe interna anonima che implementa Runnable. Nello stesso costruttore del primo thread, si avvia un nuovo thread per eseguire la classe interna anonima. Quindi hai due thread. Se si vuole assicurare che il nuovo thread non faccia nulla prima che il costruttore sia "completamente terminato", userei alcuni blocchi nel costruttore. In questo modo, è possibile avviare il secondo thread, ma attenderà fino al termine del primo thread.

public class A { 
    int final number; 

    A() { 
     new Thread(
      new Runnable() { 
       public void run() { 
        System.out.pritnln("Number: " + number); 
       } 
     }).start(); 
    number = 2; 
    } 
} 
+0

Questo non include un 'Runnable' di classe anonima - come richiesto – Markus

+0

A destra, ora lo ho corretto – JavierV

10

pensare alla situazione thread singolo prima:

Ogni volta che si crea un oggetto tramite new, il suo costruttore viene chiamato, che (si spera) inizializza i campi del nuovo oggetto prima di un riferimento a questo oggetto viene restituito. Vale a dire, dal punto di vista del chiamante, questo new è quasi come un'operazione atomica:

Prima di chiamare new, non c'è alcun oggetto. Dopo il ritorno da new, l'oggetto esiste completamente inizializzato.

Quindi tutto va bene.


La situazione cambia leggermente quando entrano in gioco più thread. Ma dobbiamo leggere attentamente la vostra citazione:

... è stato completamente costruito e i suoi campi completamente inizializzati.

Il punto cruciale è fully. L'oggetto della domanda dice "prima della creazione", ma ciò che si intende qui non è prima che l'oggetto sia stato creato, ma tra la creazione dell'oggetto e l'inizializzazione. In una situazione multi-threaded, new non può più essere considerato (pseudo-) atomico a causa di questo (il tempo scorre da sinistra a destra):

Thread1 --> create object --> initialize object --> return from `new` 
         ^
          | 
          | (messing with the object) 
Thread2 ------------------/ 

Così come può Thread2 pasticciare con l'oggetto? Avrebbe bisogno di un riferimento a quell'oggetto ma poiché new restituirà l'oggetto solo dopo che sono stati creati e inizializzati, questo dovrebbe essere impossibile, giusto?

Bene, no, c'è un modo in cui è ancora possibile, vale a dire se viene creato il thread 2 all'interno del costruttore dell'oggetto. Poi la situazione sarebbe simile a questa:

Thread1 --> create object --> create Thread2 --> initialize object --> return from `new` 
             |  ^
             |  | 
             |  | (messing with the object) 
             \-----/ 

Dal Thread2 viene creato dopo l'oggetto è stato creato (ma prima è stato completamente inizializzato), c'è già un riferimento all'oggetto che Thread2 potrebbe ottenere un in possesso di. Un modo è semplicemente se il costruttore di Thread2 prende esplicitamente un riferimento all'oggetto come parametro. Un altro modo è utilizzare una classe interna non statica dell'oggetto per il metodo run di Thread2.

+0

Ma il modo in cui la tua classe non è definitiva non è sicuro! Dal momento che il tuo costruttore potrebbe essere chiamato "super()" da una classe figlio e tu dovresti accedere ai metodi di questo bambino prima che sia completamente costruito ... – Falco

+0

E questo è uno dei motivi per cui non dovresti mai chiamare setVisible (true) all'interno del costruttore di un oggetto SwingUI che non viene chiamato sul thread EventPump ... – Falco

+0

Hai dimenticato di rispondere alla domanda: "Come si impedisce questo ...?" –