This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
java911@microsoft.com Download the code (7KB) |
The War of the Condiments
|
public static void dumpStack()
{
new Exception("Stack trace").printStackTrace();
}
You may have missed the overload of Throwable.printStackTrace (Throwable is the base class for Exception) that lets you print a stack trace to a java.io.PrintStream or java.io.PrintWriter. I'll use the PrintWriter overload since PrintWriters are the more modern of the two classes. (Character-based output streams were added in JDK 1.1.) A PrintWriter is a subclass of java.io.Writer, and a Writer is simply a Unicode, character-based (as opposed to byte-based) output stream.
Java Tip of the Month As you may know, Java Class objects for Java classes can be found by name using the Class.forName method, like this: |
Class c = Class.forName("java.lang.String");
c.newInstance();
But did you know about the new class literals feature introduced in JDK 1.1? You can write the
same code like this:
Class c = java.lang.String.class;
c.newInstance();
class A
{
public A()
{
// do whatever
}
public A(String s)
{
this.A();
// do whatever
}
}
|
The purpose of this special method is to allow objects that are holding references to native resources to free them before being garbage collected. Pure Java classes (for example, classes that don't involve native code) generally don't need finalizers; the VM's garbage collector is sufficient to automatically clean up after them because they only consume resources from the Java garbage-collected heap. This makes finalizers very different from (and much rarer than) C++ destructors, which manually take on a lot of the work that a garbage collector would otherwise perform. An example of a nonpure Java class that does have a finalizer method is the java.awt.Graphics class. The Graphics.finalize method calls Graphics.dispose, which is ultimately implemented in native code. The native dispose method frees any C-allocated data structures and/or operating system resources associated with the Graphics object. There are a few gotchas to be aware of with finalizers. For one thing, finalizers don't chain the way constructors do. So you should get in the habit of manually making every finalizer call its superclass's finalize method, like so: |
|
This will ensure that any finalization code in the superclass, or further up the inheritance chain, will be run. A couple of more subtle gotchas are that the order of object finalization and the thread that does the job are both undefined. This may not sound like a big deal since the object is unreachable, but just sprinkle a few synchronized keywords around and have your finalizer do something significantyou may wind up singing a different tune! Figure 3 demonstrates finalization. A new FinalizeMe object is created and assigned to the stack variable f. The garbage collector and finalization process are invoked through System.gc and System.runFinalization to demonstrate that f's finalizer is not run (because the f stack variable still references it). Next, I remove the only reference to the object by setting f to null. Then, I run the garbage collector and finalization process again. This time the finalizer is called. The output from running FinalizeMe looks like this: |
|
Java Resource of the Month The Stedelijk Museum of Modern Art in Amsterdam has Java search and timeline applets on their hyper-modern Web site that help you locate artists and art works. A URL well worth the visit: http://www.stedelijk.nl/eng/index.html |
In Figure 4 , each person is represented by a thread, and the salt and pepper shakers are represented by object instances (don't forget that all objects can potentially have Java synchronization monitors associated with them). The main thread of execution is a referee thread, which initializes the wantsPepper and wantsSalt threads, and waits for both to obtain locks on their objects. After each thread starts up and acquires its object, it waits for the referee to perform a notifyAll on the go object. Once the referee says go, each thread tries to take the opposing thread's object while still holding onto the lock for its own object (if you give me the pepper, then I'll give you the salt and vice versa). This results in a deadlock because neither thread can acquire the lock that the opposite thread still holds. Stepping through this code in a multithreaded Java debugger like Visual J++® can be quite enlightening. You would get a race condition if you took the referee out of Figure 4 . There are two possible outcomes depending on thread scheduling. The first outcome is that one thread manages to acquire and release both locks before the other thread has a chance to get its first lock. In this case, the code doesn't deadlock at all and both threads get what they want. The other outcome is that thread scheduling is even enough so that each thread acquires one lock. In this case, the code does deadlock. As you can see, race conditions are intermittent by nature because they depend on the relative speed of execution of two or more threads (thus the name race condition). This can make them especially hard to debug. At least with deadlocks, you can hit Ctrl+Break and look at which threads are holding which monitor locks. |