...
| 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.
...