Return to the Java Hall of Shame
/* * This program demonstrates a legal but very bogus interaction * between finalizers and java's recursive monitors that * breaks synchronization, even across module boundaries. */ // A completely innocuous class that uses synchronization, but makes the // (understandable) mistake of creating new objects inside a monitor class helper_class { // Sanity check synchronization by keeping track of the number // of times we are in baz() concurrently private int recursion_level = 0; private void check_recursion(int expected) { if (recursion_level != expected) { System.out.println(" * WHOA - baz() is a synchronized routine." + " I never expect to call myself, so\n" + " * recursion_level should be " + expected + " but it's actually " + recursion_level + "! - EXITING"); Thread.currentThread().dumpStack(); System.exit(-1); } } // This routine is going to do some nasty timing sensitive stuff. // Make it synchronized so that we don't have to worry about it. // Since we don't call ourselves recursively, there should be // at most one copy of this routine going at a time. RIGHT? public synchronized void baz() { recursion_level++; check_recursion(1); /* Alloc lots to force a gc and hopefully to cause finalizers * to run (explicit invocation via Runtime.runFinalizers() also * works) */ Integer a[][] = new Integer[1000000][]; for (int i = 0; i < 1000000; i++) a[i] = new Integer[1000000]; recursion_level--; check_recursion(0); } }; class finalizable_class { static helper_class global = null; public void finalize() { System.out.println("In finalizer from thread " + Thread.currentThread().getName()); global.baz(); System.out.println("Leaving finalizer from thread " + Thread.currentThread().getName()); } } class TestThread extends Thread { public void run() { /* Make some garbage in a completely separate thread */ new finalizable_class(); } }; class tester_class { public static void main (String args[]) throws InterruptedException { helper_class h = new helper_class(); finalizable_class.global = h; /* Create garbage in a separate thread, and then join with * that thread */ TestThread t = new TestThread(); t.start(); t.join(); System.out.println("Calling baz from the main thread."); h.baz(); System.out.println("Exiting from the main thread."); } }And the output from running this program is:
> javac exa.java > java tester_class Calling baz from the main thread. In finalizer from thread main * WHOA - baz() is a synchronized routine. I never expect to call myself, so * recursion_level should be 1 but it's actually 2! - EXITING java.lang.Exception: Stack trace at java.lang.Thread.dumpStack(Thread.java) at helper_class.check_recursion(exa.java:21) at helper_class.baz(exa.java:33) at finalizable_class.finalize(exa.java:54) at helper_class.baz(exa.java:33) at tester_class.main(exa.java:79)Since finalizers can be (and are) run from any thread, and thread locks are recursive, if a thread runs a finalizer while it already holds a monitor lock, we potentially break synchronization in a completely unsuspecting class. If thread locks weren't recursive, this would cause deadlock instead.
The real culprit is the potential running of finalizers by any thread. Allowing threads that potentially hold locks to run finalizers that may also need to synchronize on monitor locks is a very bad thing, potentially violating synchronization assumptions or causing deadlock. Return to the Java Hall of Shame