È passato molto tempo da quando ho pubblicato questa domanda e voglio pubblicare una risposta che descriva lo scenario esatto che ha portato a questa difficile NullPointerException
.
Penso che questo potrebbe aiutare i futuri lettori che incontrano un'eccezione così confusa a pensare fuori dagli schemi, dal momento che avevo quasi tutte le ragioni per sospettare che si trattasse di un bug del connettore MySQL, anche se dopo tutto non lo era.
Durante l'analisi di questa eccezione ero sicuro che la mia applicazione non potesse chiudere la connessione DB durante il tentativo di leggere i dati da essa, poiché le mie connessioni DB non sono condivise tra i thread e se lo stesso thread ha chiuso la connessione e quindi ha tentato di accedere esso, avrebbe dovuto essere generata un'eccezione diversa (alcuni SQLException
). Questo era il motivo principale per cui sospettavo un bug del connettore MySQL.
Si è scoperto che c'erano due thread che accedevano alla stessa connessione Dopotutto. Il motivo per cui era difficile da capire era che uno di questi thread era un thread di Garbage Collector .
Tornando al codice che ho postato:
Connection conn = ... // the connection is open
...
for (String someID : someIDs) {
SomeClass sc = null;
PreparedStatement
stmt = conn.prepareStatement ("SELECT A, B, C, D, E, F, G, H FROM T WHERE A = ?");
stmt.setString (1, "someID");
ResultSet res = stmt.executeQuery ();
if (res.next ()) {
sc = new SomeClass ();
sc.setA (res.getString (1));
sc.setB (res.getString (2));
sc.setC (res.getString (3));
sc.setD (res.getString (4));
sc.setE (res.getString (5));
sc.setF (res.getInt (6));
sc.setG (res.getString (7));
sc.setH (res.getByte (8)); // the exception is thrown here
}
stmt.close ();
conn.commit ();
if (sc != null) {
// do some processing that involves loading other records from the
// DB using the same connection
}
}
conn.close();
Il problema sta nella sezione "eseguire alcune elaborazioni che comportano il caricamento di altri record dal DB utilizzando la stessa connessione", che, sfortunatamente, non ho incluso nella mia domanda originale, poiché non pensavo che il problema fosse lì.
Zoomando in quella sezione, abbiamo:
if (sc != null) {
...
someMethod (conn);
...
}
E someMethod
assomiglia a questo:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
si presenta così (ovviamente sto semplificando qui):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
può creare la propria connessione DB in alcuni scenari, ma può accettare una connessione esistente in altri scenari, come quello che abbiamo qui.
Come puoi vedere, quella sezione contiene una chiamata a someMethod
che accetta la connessione aperta come argomento. someMethod
passa la connessione a un'istanza locale di SomeOtherClass
. SomeOtherClass
aveva un finalize
metodo che chiude la connessione.
Ora, dopo someMethod
restituisce, instance
diventa idoneo alla raccolta dei rifiuti. Quando viene effettuato il Garbage Collection, il suo finalize
viene chiamato dal thread del Garbage Collector, che chiude la connessione.
Ora torniamo al ciclo for, che continua a eseguire le istruzioni SELECT utilizzando la stessa connessione che può essere chiusa in qualsiasi momento dal thread del Garbage Collector.
Se il thread del Garbage Collector chiude la connessione mentre il thread dell'applicazione si trova nel mezzo di un metodo del connettore mysql che si basa sulla connessione per essere aperta, un NullPointerException
può verificarsi.
Rimozione di finalize
il metodo ha risolto il problema.
Non sostituiamo spesso finalize
metodo nelle nostre classi, il che ha reso molto difficile individuare il bug.