PDA

View Full Version : [JAVA]Copiare/passare il riferimento di un'oggetto è thread safe?


Satshas
05-07-2011, 22:19
Ciao a tutti, nel tempo libero mi sto dilettando a sviluppare una piccola applicazione Client-Server in java servendomi degli strumenti messi a disposizione per il multi-threading.

Sapete dirmi, nel caso l'oggetto in questione non sia final, se di un'oggetto condiviso tra più thread copiare il riferimento o passarlo come parametro sia thread-safe?

Ad esempio ho un'oggetto condiviso in lettura/scrittura oppurtunamente sincronizzata tra più thread:

OggettoA A = new OggettoA();

fare:

OggettoA B = A;

o

FaiQualcosa(A);

sia thred-safe o abbiano bisogno anchessi di sincronizzazione?

banryu79
06-07-2011, 10:14
Ad esempio ho un'oggetto condiviso in lettura/scrittura oppurtunamente sincronizzata tra più thread:

OggettoA A = new OggettoA();

fare:

OggettoA B = A;

o

FaiQualcosa(A);

sia thred-safe o abbiano bisogno anchessi di sincronizzazzione?

Se le letture/scritture sono effettivamente thread-safe rispetto al memory model del linguaggio e quindi entrano in gioco gli effetti delle regole "happens-before" previste per taluni operazioni, allora in teoria non occorre "sincronizzare" (immagino intendi acquisire un lock in via esclusiva).
Ma per essere sicuri bisognerebbe esaminare il codice.

Se ti dovesse servire documentazione di riferimento, ti posto tre risorse utili:
1) trail sulla concurerncy nei Java Tutorials (http://download.oracle.com/javase/tutorial/essential/concurrency/index.html)
2) javadoc del package java.util.concurrent (la parete titolata "Memory Consistency Properties") (http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html)
3) capitolo 17 del Java Language Specifications (Threads and Locks) (http://java.sun.com/docs/books/jls/third_edition/html/memory.html)

Satshas
06-07-2011, 16:08
Grazie della risposta.
Trascendendo la correttezza o meno del codice in materia di sincronizzazione (dove s'intende proprio acquisire un lock in via esclusiva) che si potrebbe porre corretta per ipotesi iniziale, non riesco a trovare riferimenti sulle operazioni sul reference di un'oggetto nei thread, se non quando si parla di oggetti del tipo/o contenenti campi "final" che vanno inizializzati completamente (An object is considered to be completely initialized when its constructor finishes.) prima di essere usati da più threads.
Cosa che ho sempre provveduto a fare per oggetti condivisi non "final" con l'aggiunta dei costrutti della sincronizzazione.

Comunque per curiosità, il problema mi è nato nell'implementazione di un semplice sistema di login in cui un'array di oggetti Login contenenti due string ognuno per memorizzare nome utente e password, dopo essere stato inizializzato e riempito con i dati da un file, viene passato nel metodo init di ogni classe SlotUtente che gestisce a sua volta il ThreadSlotUtente, esplicitando che in questa fase tutti i thread sono fermi.
Dopo avviato i thread del server, nel momento in cui arriva una nuova connessione viene lanciato un ThreadSlotUtente (quando altri ThreadSlotUtente possono essere già attivi ed usare Login) con parametro appunto l'array di Login.
Ogni ThreadSlotUtente, ad un certo punto della sua fase di esecuzione, si occupa di verificare che i dati ricevuti via rete corrispondano ad un'effettivo utente già registrato con un codice del tipo:


for(int C=0;C<NumeroLogins;C++){

synchronized(Login[C]){

if(NomeUtente.equals(Login[C].NomeUtente))
if(Password.equals(Login[C].Password)){
LoginTrovato = true;
break;
}

}

}

ciò poteva anche essere ovviamente implementato scrivendo due metodi synchronized (Set)GetNomeUtente() e (Set)GetPassword() all'interno dell'oggetto Login ma ho preferito così per far sì che a thread fermi se devo effettuare operazioni sull'oggetto Login esse non debbano passare dal lock.

banryu79
07-07-2011, 08:18
Ciao, da questo spezzone di codice:

for(int C=0; C<NumeroLogins; C++) {
synchronized(Login[C]) {
if(NomeUtente.equals(Login[C].NomeUtente))
if(Password.equals(Login[C].Password)) {
LoginTrovato = true;
break;
}
}
}

quello che capisco io è che quando un ThreadSlotUtente vuole verificare se l'accoppiata nome-password che ha ricevuto corrisponda ad un effettivo utente registrato sia neccessario ottenere il lock esclusivo, ad ogni iterazione, di ogni singola istanza in Login (array che immagino sia generato ogni volta come copia dell'unica collezione di Login da condividere?) per assicurare la consistenza delle due letture di quel singolo Login.
Significa forse che un qualsiasi Login potrebbe nel frattempo venire mutato da qualche altro thread?

Intendo dire: se l'array Login non viene più mutato dopo l'inizializzazione iniziale, e se le singole istanze di Login che esso contiene non vengono più mutate a loro volta (nessun thread va a modificare i campi nomeUtente e Password) allora non è neccessario sincronizzare nulla, le letture sono tutte safe.

Satshas
07-07-2011, 11:14
Le assunzioni che hai fatto sono corrette, infatti ho previsto che l'array Login possa cambiare in due scenari:

1)Fermo il server, che corrisponde a fermare tutti i thread, e ho necessità di reinizializzare tutti i dati compresi i Login.In questo caso ovviamente non ho bisogno di sincronizzazione.

2)Un nuovo utente si registra/modifica i propri dati Login mentre il server è attivo, e quindi lo sono tutti i thread tra cui potrebbero esserci N>=0 ThreadSlotUtente che stanno effettuando il login, e qui ho appunto bisogno di lock esclusivo


Comunque ritornando IT sul discorso reference e tralasciando altre scelte implementative in cui potrei evitare di pormi la domanda, io passo al costruttore dei ThreadSlotUtente e copio su un riferimento locale l'array Login che nel frattempo può essere letto e/o modificato da altri ThreadSlotUtente o dal thread che si occupa delle registrazioni.
E' confermato che ciò, o che il concetto in generale dato che può tornare utile, è thread-safe?

banryu79
07-07-2011, 11:59
2)Un nuovo utente si registra/modifica i propri dati Login mentre il server è attivo, e quindi lo sono tutti i thread tra cui potrebbero esserci N>=0 ThreadSlotUtente che stanno effettuando il login, e qui ho appunto bisogno di lock esclusivo

In questo caso hai bisogno di sincronizzare due eventi distinti.
1) l'accesso alla singola istanza Login, a causa di potenziale mutazione concorrente da parte di un altro thread, il che capita quando un utente modifica i propri dati.

2) l'accesso all'array Login, a causa di potenziale mutazione concorrente da parte di un altro thread, il che capita quando un utente nuovo si registra (nuovo Login da aggiungere all'array e quindi nuovo array?)
A questo punto penso ti converrebbe usare un java.util.concurrent.CopyOnWriteArrayList al posto dell'array di Login: è perfetto per condividere una collezione sulla quale si eseguono molti accessi in lettura e pochi/rari accessi in scrittura. In particolare potresti usare l'iterator restituito da questa collezione, che rappresenta lo "snapshot" dello stato della stessa al momento della sua chiamata.
Devi poi comunque sincronizzare l'accesso al singolo Login, se può essere mutato da un'altro thread (punto 1)

Ma forse a questo punto ti converrebbe progettare una classe thread-safe che incapsuli tutto il sistema logico di login (creazioneUtente - modificaUtente - cancellaUtente - utenteRegistrato) al suo interno ed esponga metodi ad-hoc per le varie operazioni.


Comunque ritornando IT sul discorso reference e tralasciando altre scelte implementative in cui potrei evitare di pormi la domanda, io passo al costruttore dei ThreadSlotUtente e copio su un riferimento locale l'array Login che nel frattempo può essere letto e/o modificato da altri ThreadSlotUtente o dal thread che si occupa delle registrazioni.
E' confermato che ciò, o che il concetto in generale dato che può tornare utile, è thread-safe?
Non ho capito cosa intendi con "...e copio su un riferimento locale l'array Login": esegui un semplice assegnamento di reference o crei un nuovo array locale in cui copi gli elementi del Login[] globale?

Ripeto che forse ti faciliti la vita creandoti una classe ad-hoc, come sopra.

Satshas
07-07-2011, 12:25
Discorso ancora da decidere e implementare per quanto riguarda l'aggiunta di un utente perchè nel caso facessi un synchronized(Login) in effetti dovrei farlo anche nel ciclo che controlla nome utente e password.
Comunque proverò a leggere qualcosa per CopyOnWriteArrayList anche se vorrei evitare di fare la copia (se ho capito bene lo snapshot forse lo è) dato che potrebbe in effetti tornare utile.

Per il chiarire sul discorso thread-safe sul reference, effettuo appunto una copia del reference non la copia dell'array.

banryu79
07-07-2011, 12:43
Comunque proverò a leggere qualcosa per CopyOnWriteArrayList anche se vorrei evitare di fare la copia (se ho capito bene lo snapshot forse lo è) dato che potrebbe in effetti tornare utile.

No, la copia dell'array interno viene effettuata solo se viene mutato l'array stesso (aggiunta o rimozione di un elemento). Chiedere l'iteratore non causa nessuna copia, e hai in mano uno "snapshot" della collezione al momento della crezione dell'iteratore (quindi anche se qualcuno rimuove o aggiunge un elemento tu non vedi differenze, per quel ciclo).


Per il chiarire sul discorso thread-safe sul reference, effettuo appunto una copia del reference non la copia dell'array.
Ok, allora non c'è problema: nel costruttore non accedi ai dati dell'array, ergo non hai problemi di consistenza. Li avrai nel ciclo che hai pubbliacato, ma appunto là ti preoccupi di sincronizzare gli accessi.

Io ti consiglio di wrappare tutto in una classe thread-safe, e poi condividerne un'istanza tra i vari thread.

EDIT: non c'e problema prechè suppongo che il thread che esegue il costruttore di un ThreadSlotUtente sia sempre il main thread...