2009-11-09 13 views
11

Quando si utilizza una connessione al database locale del thread, la chiusura della connessione è necessaria quando il thread esiste.Come forzare un thread Java per chiudere una connessione al database locale del thread

Ciò che posso fare solo se riesco a sovrascrivere il metodo run() del thread chiamante. Anche quella non è una soluzione eccezionale, dal momento che al momento dell'uscita, non so se una connessione è mai stata aperta da quel thread.

Il problema è in realtà più generale: come forzare un thread per chiamare un metodo di finalizzazione di un oggetto thread-locale quando esce.

ho guardato le fonti di Java 1.5 e ha scoperto che la cartina di filo viene impostato su null, che finirà per causare la garbage collection di chiamare finalizzare(), ma io non voglio contare sulla spazzatura collettore.

seguito di esclusione sembra inevitabile fare in modo che una connessione al database viene chiusa:

@Override 
public void remove() { 
    get().release(); 
    super.remove(); 
} 

dove release() chiude la connessione al database, se è stato aperto. Ma non sappiamo se il thread abbia mai usato questo thread-local. Se get() non è mai stato chiamato da questa discussione, poi c'è tutto uno spreco di energie qui: ThreadLocal.initialValue() sarà chiamato, una mappa verrà creato su questo thread, ecc

-

ulteriori chiarimenti e esempio secondo commento di Thorbjørn:

java.lang.ThreadLocal è un tipo di fabbrica per un oggetto che è destinato a un thread. Questo tipo ha un getter per l'oggetto e un metodo factory (tipicamente scritto dall'utente). Quando viene chiamato il getter, chiama il metodo factory solo se non è mai stato chiamato prima da questo thread.

Utilizzo di ThreadLocal consente a uno sviluppatore di associare una risorsa a un thread anche se il codice del thread è stato scritto da una terza parte.

Esempio: Dire che abbiamo una risorsa Tipo chiamato MyType e vogliamo avere uno e solo uno per thread.

Definire nella classe utilizzando: statica ThreadLocal privato resourceFactory = new ThreadLocal() { @Override protetta MyType initialValue() {return new MyType(); }}

Usa nel contesto locale in questa classe: pubblico someMethod void() { risorsa MyType = resourceFactory.get(); resource.useResource(); }

get() può chiamare initialValue() solo una volta nel ciclo di vita del thread chiamante. A questo punto un'istanza di MyType viene creata un'istanza e associata a questo thread. Le chiamate successive a get() da questo thread si riferiscono nuovamente a questo oggetto.

L'esempio classico di utilizzo è quando MyType è un formattatore di testo/data/xml non sicuro per il thread.

Tuttavia, tali formattatori di solito non devono essere rilasciati o chiusi, le connessioni database fanno e io sto usando java.lang.ThreadLocal per avere una connessione database per thread.

Il modo in cui lo vedo, java.lang.ThreadLocal è quasi perfetto per quello. Quasi perché non c'è modo di garantire la chiusura della risorsa se il thread chiamante appartiene a un'applicazione di terze parti.

Ho bisogno del tuo cervello scudieri: estendendo java.lang.ThreadLocal sono riuscito a legare una connessione al database per ogni thread, per il suo uso esclusivo - tra cui le discussioni che non posso modificare o override. Sono riuscito a fare in modo che le connessioni si chiudano nel caso in cui il thread muoia su un'eccezione non rilevata.

In caso di uscita filo normale, il garbage collector chiude la connessione (perché MyType sostituisce il finalize()). In realtà succede abbastanza velocemente, ma questo non è l'ideale.

Se fosse per me, non ci sarebbe stato un altro metodo sul java.lang.ThreadLocal:

protected void release() throws Throwable {} 

Se questo metodo esisteva java.lang.ThreadLocal, chiamato da JVM su qualsiasi thread di uscita/morte, quindi nel mio override di esso potrei chiudere la mia connessione (e il redentore sarebbe arrivato a Sion).

In assenza di tale metodo, sto cercando un altro modo per confermare la chiusura. Un modo che non si baserà sulla garbage collection JVM.

Grazie

+0

Si prega di aggiungere un codice minimale che mostri ciò che descrivi. Preferibilmente eseguibile. –

+0

Aggiunto un po 'di codice. Spero che questo spieghi uno dei problemi. –

+0

Bene, no. Si prega di creare un esempio funzionale minimo. –

risposta

11

Se sei di una disposizione sensibile, guarda lontano ora.

Non mi aspetto che questo si riduca molto bene; raddoppia efficacemente il numero di thread nel sistema. Ci possono essere alcuni casi d'uso in cui è accettabile.

public class Estragon { 
    public static class Vladimir { 
    Vladimir() { System.out.println("Open"); } 
    public void close() { System.out.println("Close");} 
    } 

    private static ThreadLocal<Vladimir> HOLDER = new ThreadLocal<Vladimir>() { 
    @Override protected Vladimir initialValue() { 
     return createResource(); 
    } 
    }; 

    private static Vladimir createResource() { 
    final Vladimir resource = new Vladimir(); 
    final Thread godot = Thread.currentThread(); 
    new Thread() { 
     @Override public void run() { 
     try { 
      godot.join(); 
     } catch (InterruptedException e) { 
      // thread dying; ignore 
     } finally { 
      resource.close(); 
     } 
     } 
    }.start(); 
    return resource; 
    } 

    public static Vladimir getResource() { 
    return HOLDER.get(); 
    } 
} 

Una migliore gestione degli errori e così via viene lasciata come esercizio per l'implementatore.

Si potrebbe anche dare un'occhiata al rilevamento dei thread/risorse in un ConcurrentHashMap con un altro thread polling isAlive. Ma quella soluzione è l'ultima risorsa del disperato - gli oggetti probabilmente finiranno per essere controllati troppo spesso o troppo di rado.

Non riesco a pensare a nient'altro che non coinvolga la strumentazione. AOP potrebbe funzionare.

Il pool di connessioni sarebbe la mia opzione preferita.

+0

Buona idea! Raddoppierà il numero di thread ma credo che possiamo gestirlo. Polling dei thread è fuori questione. Se tengo una mappa dei thread, potrei anche eliminare del tutto ThreadLocal. Grazie –

+0

Potrei discutere del pool di connessioni. Tuttavia, la connessione al database è solo un esempio. Il problema più generale è che il sistema ThreadLocal di Java è paralizzato dalla sua incapacità di garantire un rilascio immediato di risorse quando si esce da un thread. Per questo motivo è meno che perfetto per le risorse che devono essere rilasciate. –

+0

Capisco, ma lasciare le risorse aperte fino alla morte del thread è anche non ottimale nella (quasi) maggior parte delle situazioni. Ad esempio, su un server Java EE, è possibile che i thread vengano raggruppati e riutilizzati per molte unità di lavoro (richieste servlet, transazioni EJB, ecc.) Che potrebbero essere utilizzate raramente o incidentalmente dal percorso del codice. Quindi, la VM può accumulare molte connessioni aperte e inattive. Questa soluzione dovrebbe essere utilizzata raramente e solo quando si conoscono o si controllano i dettagli di creazione/utilizzo del thread. – McDowell

6

avvolgere il Runnable con un nuovo Runnable con una costruzione

try { 
    wrappedRunnable.run(); 
} finally { 
    doMandatoryStuff(); 
} 

, e lasciare che sia eseguito, invece.

Si potrebbe anche fare in un metodo, ad esempio:

Runnable closingRunnable(Runnable wrappedRunnable) { 
    return new Runnable() { 
     @Override 
     public void run() { 
     try { 
      wrappedRunnable.run(); 
     } finally { 
      doMandatoryStuff(); 
     } 
     } 
    }; 
    } 

e chiamare quel metodo passando il eseguibile che stai cercando.

Si consiglia inoltre di prendere in considerazione l'utilizzo di un Executor. Rende molto più facile la gestione di Runable e Callables.

Se si utilizza un ExecutorService, è possibile utilizzarlo come executor.submit(closingRunnable(normalRunnable))

Se sai che sarai spegnere il intera ExecutorService e desidera che le connessioni per chiudere a quel punto, è possibile impostare una fabbrica thread che fa anche il vicino "dopo tutti i compiti sono fatto e lo spegnimento invitato l'esecutore", ad esempio:

ExecutorService autoClosingThreadPool(int numThreads) { 
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // same as Executors.newFixedThreadPool 
    threadPool.setThreadFactory(new ThreadFactory() { 
     @Override 
     public Thread newThread(Runnable r) { 
     return new Thread(closingRunnable(r)); // closes it when executor is shutdown 
     } 
    }); 
    return threadPool; 
    } 

In termini di se o non doMandatoryStuff può sapere se il collegamento è stato mai aperto o non precedentemente, una cosa mi viene in mente un secondo ThreadLocal che tiene traccia solo se è stato aperto o non (es: quando la connessione è aperta, ottenere quindi un AtomicInteger su 2, al momento della pulizia, verificare se è ancora al suo valore predefinito, ad esempio 1 ...)

+0

Grazie. Anzi, avvolgo la corsa dove posso. Esistono ancora due problemi: 1. Alcuni thread che utilizzano l'oggetto thread-local sono thread di terze parti e non ho modo di completare la loro esecuzione() 2. La finalizzazione (ciò che si chiama mandatoryStuff) richiede l'accesso al oggetto locale thread. Purtroppo se non è mai stato acceduto da questo thread, quindi l'oggetto deve essere istanziato e inizializzato. In questo caso, verrà aperta una connessione al database ... solo così può essere chiusa. (Ho trovato un modo per aggirarlo - abbastanza brutto però) C'è un modo per verificare se un thread-local è stato inizializzato? –

+0

Non sono sicuro di comprendere appieno ciò che descrivi. Puoi creare un esempio di lavoro minimalista? –

1

È necessario aprire la connessione una volta, quindi devi anche gestire la chiusura nello stesso posto. A seconda del proprio ambiente, i thread possono essere riutilizzati e non è possibile aspettarsi che il thread venga raccolto prima della chiusura dell'applicazione.

+0

Niente di sbagliato con il tuo suggerimento. Tuttavia molto spesso non avrai accesso all'inizio e alla fine di un metodo run(). Molto spesso implementerai un metodo che viene chiamato da una API o frame di terze parti.Questo è un caso in cui ThreadLocal può diventare utile e questo è il caso in cui ti potresti chiedere come assicurarti che una risorsa ThreadLocal non venga lasciata per chiudere GC. –

1

Penso che nel caso generale non ci sia una buona soluzione per questo, oltre al classico: il codice che ottiene la risorsa deve essere responsabile della sua chiusura.

Nel caso specifico, se si richiamano i thread in questo modo, è possibile passare la connessione ai metodi all'inizio del thread, sia con un metodo che utilizza un parametro personalizzato o tramite una forma di Iniezione delle dipendenze. Quindi dal momento che hai il codice che fornisce la connessione, hai il codice che lo rimuove.

Un'iniezione delle dipendenze basata su annotazioni potrebbe funzionare qui, poiché il codice che non richiede la connessione non ne ottiene uno e quindi non dovrebbe essere chiuso, ma sembra che il progetto sia troppo lungo per il retrofit qualcosa del genere.

+0

אחי, se hai familiarità con java.lang.ThreadLocal sai che ti permette di definire l'istanza del tuo oggetto ma non la sua finalizzazione. C'è ovviamente java.lang.Object.finalize() e io lo uso, ma per quello che so, non viene chiamato dal thread in uscita ma solo dopo, dopo la sua morte, dal garbage collector. –

+0

@ Joel, penso che stiamo dicendo la stessa cosa. Il creatore (il primo chiamante del metodo get) deve chiudere la risorsa. Tutto quello che sto dicendo è che puoi disaccoppiare il creatore dal resto del codice, anche se potrebbe non avere senso farlo. – Yishai

4

La normale procedura JDBC consiste nel chiudere lo Connection (e Statement e ResultSet) nello stesso blocco di metodi in cui lo si era acquisito.

in codice:

Connection connection = null; 

try { 
    connection = getConnectionSomehow(); 
    // Do stuff. 
} finally { 
    if (connection != null) { 
     try { 
      connection.close(); 
     } catch (SQLException e) { 
      ignoreOrLogItButDontThrowIt(e); 
     } 
    } 
} 

Tenendo questo in mente, la sua domanda mi fa pensare che ci sia qualcosa che non va nel vostro progetto. Acquisire e chiudere questo tipo di risorse costose ed esterne nel raggio più breve salverà l'applicazione da potenziali perdite di risorse e arresti anomali.

Se l'intento originale era migliorare le prestazioni di connessione, è necessario dare un'occhiata al pool di connessioni . È possibile utilizzare ad esempio l'APIper questo. Oppure, se si tratta di un'applicazione web, utilizzare le funzionalità di pool di connessioni integrate dell'appserver nel gusto di DataSource. Consultare la documentazione specifica dell'appserver per i dettagli.

+0

L'esempio mostrato sopra - l'apertura di una connessione al volo - è qualcosa che desidero evitare. È possibile solo se puoi fidarti di un pool di connessioni. L'intenzione del mio progetto è di avere una connessione vincolata a un thread - così ogni thread può avere la sua unica connessione esclusiva, se ne ha bisogno. Ora, non creo tutti i thread che potrebbero chiamare le mie query. Pertanto non posso assegnare una connessione al database a un thread a meno che non utilizzi il tipo java.lang.ThreadLocal. Si è rivelata una buona soluzione. Unico problema è chiudere quando un thread (di terze parti) esce da –

+0

Clear ma è ancora rischioso per le perdite di risorse. Un thread potrebbe bloccarsi o funzionare più a lungo di quanto sia permesso alla connessione di rimanere aperto. – BalusC

1

Sovrascrivere il metodo get() in ThreaedLocal in modo che imposti una proprietà List nella sottoclasse. Questa proprietà può essere facilmente interrogata per determinare se il metodo get() è stato chiamato per un determinato thread. Potresti quindi accedere a ThreadLocal per ripulirlo in quel caso.

aggiornata in risposta al commento

+0

Grazie. Dalla tua risposta non mi è chiaro come la nuova proprietà possa stabilire quale thread ha chiamato get() e quale non ha ancora. –

+0

In tal caso, è possibile utilizzare un elenco per archiviare tutti i thread che accedono a ThreadLocal –

3

Io non capisco il motivo per cui non si utilizza il pool di connessioni tradizionali. Ma assumerò che tu abbia le tue ragioni.

Quanta libertà hai? Poiché alcuni framework DI supportano i cicli di vita degli oggetti e le variabili con ambito thread (tutti ben proxy). Potresti usare uno di loro? Penso che Spring avrebbe fatto tutto questo fuori dalla scatola, mentre Guice avrebbe avuto bisogno di una lib di terze parti per gestire i cicli di vita e gli scopi dei thread.

Avanti quanto controllo hai sulla creazione della variabile ThreadLocal o sulla creazione di thread? Immagino che tu abbia il controllo completo su ThreadLocal ma limitato a nessuno sulla creazione di thread?

È possibile utilizzare la programmazione orientata all'aspetto per monitorare il nuovo Runnable o Thread estendendo il metodo run() per includere la pulizia? Sarà inoltre necessario estendere il ThreadLocal in modo che possa registrarsi.

1

Quello che abbiamo fatto era

@Override 
public void run() { 
    try { 
    // ... 
    } finally { 
    resource.close(); 
    } 
} 

pratica solo sempre (eventualmente aperta e quindi) chiuderlo per tutti i percorsi attraverso il filo. Nel caso in cui aiuti qualcuno là fuori :)

Problemi correlati