Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: renamed variables + minor edits

...

Code Block
bgColor#FFcccc
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
bgColor#ccccff
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
bgColor#ccccff
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
bgColor#ccccff
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.

...