...
Code Block | ||
---|---|---|
| ||
public final class Lazy { private static Connection conndbConnection; static { Thread tdbInitializerThread = new Thread(new Runnable() { public void run() { // Initialize athe database connection try { conndbConnection = DriverManager.getConnection("connectionstring"); } catch (SQLException e) { conndbConnection = null; } } }); tdbInitializerThread.start(); try { tdbInitializerThread.join(); } catch(InterruptedException ie) { throw new AssertionError(ie); } // Other initialization } public static Connection getConnection() { if(conndbConnection == null) { throw new IllegalStateException("Connection not initialized"); } return conndbConnection; } public static void main(String[] args) { // ... Connection connection = Lazy.getConnection(); } } |
The code in the static
block is responsible for initialization, and starts a background thread. The background thread attempts to initialize a database connection but needs to wait until initialization of all members of the Lazy
class, including dbConnection
,has finished.
Wiki Markup |
---|
Recall that statically-initialized fields are guaranteed to be fully constructed before becoming visible to other threads (see [CON26-J. Do not publish partially initialized objects] for more information). Consequently, the background thread must wait for the foreground thread to finish initialization before it can proceed. However, the {{Lazy}} class's main thread invokes the {{join()}} method which waits for the background thread to finish. This interdependency causes a class initialization cycle that results in a deadlock situation. \[[Bloch 05b|AA. Java References#Bloch 05b]\] |
...
Code Block | ||
---|---|---|
| ||
public final class Lazy { private static Connection conndbConnection; static { // Initialize a database connection try { conndbConnection = DriverManager.getConnection("connectionstring"); } catch (SQLException e) { conndbConnection = null; } // Other initialization } // ... } |
...
This compliant solution uses a ThreadLocal
object to initialize the database connection so that every thread can obtain its own instance of the connection.
Code Block | ||
---|---|---|
| ||
public final class Lazy { private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { try { Connection conndbConnection = DriverManager.getConnection("connectionstring"); return conndbConnection; } catch (SQLException e) { return null; } } }; public static Connection getConnection() { Connection connection = connectionHolder.get(); if(connection == null) { throw new IllegalStateException("Connection not initialized"); } return connection; } public static void main(String[] args) { // ... Connection connconnection = getConnection(); } } |
It is also safe to set other shared class variables from the initialValue()
method.
...
Code Block | ||
---|---|---|
| ||
public final class ObjectPreserver implements Runnable { private static ObjectPreserver lifeLine = new ObjectPreserver(); private ObjectPreserver() { Thread thread = new Thread(this); thread.setDaemon(true); thread.start(); // keepKeep this object alive } // Neither this class, nor HashSet will be garbage collected. // References from HashMap to other objects will also exhibit this property private static final ConcurrentHashMap<Integer,Object> protectedMap = new ConcurrentHashMap<Integer,Object>(); public synchronized void run() { try { wait(); } catch(InterruptedException e) { // Forward to handler Thread.currentThread().interrupt(); // Reset interrupted status } } // Objects passed to this method will be preserved until // the unpreserveObject method is called public static void preserveObject(Object obj) { protectedMap.put(0, obj); } // Returns the same instance every time public static Object getObject() { return protectedMap.get(0); } // Unprotect the objects so that they can be garbage collected public static void unpreserveObject() { protectedMap.remove(0); } } |
This is a singleton class (see CON23-J. Address the shortcomings of the Singleton design pattern for more information on how to properly handle defensively code singleton classes). The initialization creates a background thread referencing the object, and the thread itself waits foreverindefinitely. Consequently, this object exists for the remainder of the JVM's lifetime; however, as it is managed by a daemon thread, the thread (and object) will do not hinder a normal shutdown of the JVM.
While the initialization does involve a background thread, the background thread accesses no fields and so creates no deadlockdoes not access any fields, and creates no liveness or safety issues. Consequently this code is a safe and useful exception to this ruleguideline.
Risk Assessment
Starting and using background threads during class initialization can result in deadlock conditions.
...