Newsgroup: it.comp.java
Titolo: ThreadLocal: a che serve??

Ho trovato il seguente codice: è una classe Manager per gestire le
connessioni al database:
public class Manager {
    private static Manager manager_instance = new Manager();
    private static InheritableThreadLocal trans_conn = new
InheritableThreadLocal();
    private DataSource ds = null;
    public static Manager getInstance() {
        return manager_instance;
    }
    public void setDataSource(DataSource ds) {
        this.ds = ds;
    }
    public synchronized Connection getConnection() throws SQLException {
        Connection tc = (Connection) trans_conn.get();
        if (tc != null) {
            return tc;
        }
        if (ds != null) {
            return ds.getConnection();
        } else {
            throw new IllegalStateException("Please set a datasource");
        }
    }
    public synchronized void releaseConnection(Connection c) {
        Connection tc = (Connection) trans_conn.get();
        if (tc != null) {
            return;
        }
        try {
            if (c != null) {
                c.setAutoCommit(true);
                c.close();
            }
        } catch (SQLException x) {
            log("Could not release the connection: " + x.toString());
        }
    }
    public Connection beginTransaction() throws SQLException {
        Connection c = getConnection();
        c.setAutoCommit(false);
        trans_conn.set(c);
        return c;
    }
    public void endTransaction(boolean commit) throws SQLException {
        Connection c = (Connection) trans_conn.get();
        if (c == null) {
            return;
        }
        try {
            if (commit) {
                c.commit();
            } else {
                c.rollback();
            }
        } finally {
            trans_conn.set(null);
            releaseConnection(c);
        }
    }
}
Ecco non capisco assolutamente a cosa serve InheritedThreadLocal, e perchè
lo usa. Non bastava sincornizzare i metodi?

 
Sincronizzare i metodi serve ad evitare che due thread entrino 
contemporaneamente in alcune aree di codice, usare ThreadLocal (e 
InheritableThreadLocal - "Inheritable" non "Inherited" :p ) permette di 
fare in modo che ogni thread (o ThreadGroup) abbia la sua "copia" di una 
variabile.
Ignora per un attimo getConnection() e releaseConnection() (fa' conto 
che siano private) e pensa a cosa capita se:
THREAD 1: beginTransaction()
THREAD 2: beginTransaction()
THREAD 1: endTransaction(true)
THREAD 2: endTransaction(true)
o anche:
THREAD 1: beginTransaction()
THREAD 1: beginTransaction()
THREAD 1: endTransaction(true)
THREAD 1: endTransaction(true)
ThreadLocal e InheritableThreadLocal fanno parte di java.lang
Ti lascio con alcuni quesiti (alcuni banali), giusto per stimolare la 
tua curiosità:
* Perchè trans_conn è dichiarata static?
* Perchè beginTransaction ritorna Connection e non void?
* Perchè releaseConnection() e getConnection() sono synchronized?
   C'era bisogno di sincronizzare tutto il metodo?
* Perchè beginTransaction() e endTransaction() non hanno bisogno di 
essere sincronizzati?
* Cosa manca alla classe Manager()? [questa richiede una certa dose di 
colpo d'occhio... :o) ]
Saluti,
	Giorgio

Giorgio, 
spero tu ci dia anche le risposte...
Io ho fatto una classe Connessione NON statica
e volutamente voglio che ogni thread che la instanzia, abbia un
proprio canale di comunicazione con il database.
Ora ancora non ho fatto test specifici approfonditi, ma pensi ci siano
problemi ?
Esempio (con classe di connessione non statica):
T1 Thread1: SELECT campo1 FROM pippo FOR UPDATE
T2 Thread2: SELECT campo1 FROM pippo FOR UPDATE
T3 Thread1: ....COMMIT 
il thread2 al T2 resta inattesa ? (spero di si..)
grazie ciao


Non saprei proprio! Posso avere un aiutino?
Grazie mille!
Alberto.

 
l'ho provato ed il Thread2 al tempo T2 resta sempre in attesa 
(i due thread aprivano due doversi oggetti connessione non statici)
questo vale sia se i thread fanno parte di oggetti differenti, che se
fanno parte dello stesso oggetto runnable
oggettoRun = new Thread(this);
oggettoRun.start();
quindi per ora tutto bene
Giorgio, aspettiamo tue indicazione
grazie ciao

alberto wrote:
 >  Ok e perchè qui usa InheritableThreadLocal?
Onestamente non ne ho idea... :D
Cioè: con InheritableThreadLocal ha la stessa connessione per un thread
e i suoi figli, però onestamente non ne vedo il vantaggio...
Anzi: potrebbe capitare che più thread usino la stessa connessione in
maniera concorrente: boh?
Magari c'è qulche motivo nell'architettura dell'applicazione per cui serve?
 >>* Perchè trans_conn è dichiarata static?
 >
 > Perchè così la variabile che porta è uguale per tutte le istanze di 
Manager
 > dello stesso thread, giusto?
E' la stessa cosa che avevo pensato io...
Però la classe sembra fatta in modo da essere un singleton (vedi
getInstance()), il che renderebbe inutile lo static... (ma comunque non
lo renderebbe dannoso)
 >>* Perchè beginTransaction ritorna Connection e non void?
 >
 >  Per permettere letture sporche ad altri Thread? (eheh questa l'ho 
pensata
 > perchè il codice aveva un commentino sull'isoltaion level, ma cmq non 
sono
 > sicuro di aver capito bene).
Io pensavo che fosse banalmente per evitarsi di dover chiamare
getConnection() dopo... :p
Mi spigheresti questa cosa dell'isolation level?
 >>* Perchè releaseConnection() e getConnection() sono synchronized?
 >>   C'era bisogno di sincronizzare tutto il metodo?
 >
 >  Perchè il datasource è condiviso giusto? Ma non bastava 
sincronizzare solo
 > ds.getConnection()?
Neanch'io ho capito il senso di fare synchronized releaseConnection().
Riguardo getConnection() venerdì avevo pensato lo fosse per via di:
if (ds != null) {
	return ds.getConnection();
}
che potrebbe lanciare NullPointerException se un altro thread settasse
ds a null dopo la valutazione dell'if e prima del getConnection()...
però mi sono accorto che setDataSource() non è synchronized e quindi
questo richio resta.
Che li abbiano messi a caso? improbabile...
Per me resta un mistero...
Tre l'altro non capisco che senso ha che releaseConnection() si rifiuti
di chiudere qualunque connessione se c'è una transazione in corso... non
era meglio che si rifiutasse di chiudere solo quella dentro trans_conn?
 >>* Perchè beginTransaction() e endTransaction() non hanno bisogno di
 >>essere sincronizzati?
 >>
 >
 > perchè l'unico accessso condiviso è alla variabile trans_conn che 
porta una
 > variabile è privata al thread stesso (ovviamente con la solita dose di
 > indecisione!).
E'lo stesso mio pensiero! :D
 >>* Cosa manca alla classe Manager()? [questa richiede una certa dose di
 >>colpo d'occhio... :o) ]
 >>
 >
 > Non saprei proprio! Posso avere un aiutino?
 >
Se effettivamente Manager deve essere un singleton (cosa di cui venerdì 
non dubitavo) manca un costruttore private (o protected) in modo che non 
si possano ottenere sue istanze se non con getInstance()
Però può anche darsi che si desiderasse un comportamento "misto" con
un'istanza di "default" (manager_instance) e la possibilità di averne
altre ove necessario
Sebbene quest'ultima ipotesi mi paia un po' azzardata (dopotutto
getIstance() non si chiama getDefaultInstance()), se intendesse essere
un singleton ds sarebbe stato static...
Io credo avrei messo ugualmente un costruttore private e fatto un
secondo metodo
public static Manager getInstance(DataSource ds){
	if(ds == null) throw new NullPointerException();
	Manager m = new Manager();
	m.ds = ds;
	return m;
}
in modo da minimizzare le possibilità di una IllegalStateException in
getConnection()
magari si poteva anche elimiare del tutto setDataSource() e fare un
nuovo metodo
public static synchronized void setDefaultDataSource(DataSource ds){
	if(ds == null) throw new NullPointerException();
	if(manager_instance.ds == null) manager_instance.ds = ds;
	else throw new IllegalStateException();
}
il che avrebbe permesso di inizializzare l'istanza di default in maniera
statica e di assicurarsi che l'inizializzazione avvenisse una sola volta
Alla fine ci sono un sacco di cose che non ho capito di questa classe! :D
 >
 > Grazie mille!
 > Alberto.
Ma grazie a te!
Dal conto mio trovo molto divertente questa "vivisezione" collettiva per 
capire il senso di una classe: è un po' come una partita a master mind :D
(soprattutto se non sono io quello che ha scritto la classe :p )

 grazie ciao
ma di cosa...
ciao!

 un singleton ds sarebbe stato static...
Forse per poter usare contemporaneamente diversi Manager con diverse
proprietà, ad esempio per accedere a database diversi. Cmq non sembra
robusto sto codice!  Però mi sembra strano perchè non è stato fatto da uno
qualsiasi ma esce da un progetto sourceforge (sql2java).
Cmq alla fine io non penso che userò la transazioni, quindi raderò al suolo
LocalThread e tutto il resto mantenendo il metoodo getConnection e poco
altro! Poi penso che cercherò di creare un singleton come dicevi tu, e
siconronizzare meglio l'accesso al datasource.

 siconronizzare meglio l'accesso al datasource.
se ti può essere utile,ti dico quello che faccio io:
isolatione level=Repetibel Read 
tabelle=InnoDB
autocommit=NO
le contese sono:
La select normale non aspetta mai; gli vengono tornati i dati della
vecchia immagine a prima delle eventuali update in corso non concluse
(mai dati sporchi quindi).
una select for update si blocca se incontra un altro for update
for update si blocca se incontra uno share mode (anche se non ha
ancora fatto aggiornamenti)
 
in share mode non si blocca se incontra un altro share mode
in share mode si blocca se incontra un update (anche se dopo uno share
mode)
in share mode  si blocca se incontra una select for update (anche se
non ha fatto ancora aggiornamenti)
Questa tabella vale sia se il thread è di un altro oggetto (due mappe
diverse), che sia se il thread è dello stesso oggetto (stessa mappa a
cui ho tolto il glass e fatto partire più task assieme)
la connessione la apro solo quando mi serve e poi la chiudo, usando
sempre una semplice classe non statica e non sincronizzata (una nuova
istanza ogni volta che mi serve; ma ovviamente se aggiorno con più
classi gli passo la connessione già aperta)
in linea di massima il dialogo utente lo faccio così:
T1:
leggo con select normale (non si blocca mai e non prende dati sporchi)
mostro la mappa ed attendo azioni utente (il db è libero)
T2:
rileggo for update i dati che devo aggiornare (per la perfezione qui
dovrei contralle che quanto ho mostrato non è cambiato; ovvero che
siano ancora valide le condizioni sulle quali l'utente ha preso delle
decisioni)
aggiorno
commit