Det var länge sedan jag postade den här frågan, och jag vill posta ett svar som beskriver det exakta scenariot som ledde till detta knepiga NullPointerException
.
Jag tror att detta kan hjälpa framtida läsare som stöter på ett så förvirrande undantag att tänka utanför ramarna, eftersom jag hade nästan all anledning att misstänka att detta var en mysql-anslutningsbugg, även om det inte var det trots allt.
När jag undersökte detta undantag var jag säker på att min applikation omöjligen kan stänga DB-anslutningen när jag försöker läsa data från den, eftersom mina DB-anslutningar inte delas över trådar, och om samma tråd stängde anslutningen och sedan försökte komma åt det borde ett annat undantag ha kastats (någon SQLException
). Det var den främsta anledningen till att jag misstänkte ett mysql-anslutningsfel.
Det visade sig att det fanns två trådar som fick åtkomst till samma anslutning trots allt. Anledningen till att det var svårt att ta reda på var att en av dessa trådar var en sopsamlartråd .
Gå tillbaka till koden jag postade:
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();
Problemet ligger i avsnittet "gör någon bearbetning som innebär att ladda andra poster från DB med samma anslutning", som jag tyvärr inte inkluderade i min ursprungliga fråga, eftersom jag inte trodde att problemet fanns där.
När vi zoomar in på det avsnittet har vi:
if (sc != null) {
...
someMethod (conn);
...
}
Och someMethod
ser ut så här:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
SomeOtherClass
ser ut så här (såklart jag förenklar här):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
SomeOtherClass
kan skapa sin egen DB-anslutning i vissa scenarier, men kan acceptera en befintlig anslutning i andra scenarier, som den vi har här.
Som du kan se innehåller det avsnittet ett anrop till someMethod
som accepterar den öppna kopplingen som argument. someMethod
skickar anslutningen till en lokal instans av SomeOtherClass
. SomeOtherClass
hade en finalize
metod som stänger anslutningen.
Nu, efter someMethod
returnerar, instance
blir berättigad till sophämtning. När det samlas in skräp, finalize
det metoden anropas av garbage collector-tråden, som stänger anslutningen.
Nu kommer vi tillbaka till for-loopen, som fortsätter att exekvera SELECT-satser med samma anslutning som kan stängas när som helst av garbage collector-tråden.
Om garbage collector-tråden råkar stänga anslutningen medan programtråden är mitt i någon mysql-anslutningsmetod som förlitar sig på att anslutningen är öppen, visas en NullPointerException
kan inträffa.
Ta bort finalize
metod löste problemet.
Vi åsidosätter inte ofta finalize
metod i våra klasser, vilket gjorde det mycket svårt att hitta felet.