9 Exceptions, Threads, and Garbage Collectors

Proper error handling is a feature found in every well-designed system. A good system not only checks for and reports the obvious errors, but it also plans for and properly manages the unexpected errors. The general rules for good error handling in any language might be stated this way:

  • Make sure to check for all possible error conditions.

  • If your program can handle the error properly (for example, ask the operator for correction or assume a default processing condition), then do so.

  • If your program cannot handle the error, then report the error to the calling program as an error condition as soon as possible.

  • Include as much contextual information as possible (or practical) in the error. This likely means that the code that detected the error must provide this information.

  • Serious processing errors, or unmanaged errors, should be detected as quickly as possible. In general, these types of errors should not allow processing to continue.

  • Errors that cause abnormal termination should be logged to a file with complete contextual information for convenient postmortem analysis.

COBOL is very good at managing simple error conditions. For example, most COBOL programs validate the end user’s input and report back any invalid input to the end user. However, processing-type errors (file I/O errors, for example) can be much more cumbersome. If you have been involved with any significant COBOL project, you know already how difficult good error handling can be in some cases. Let us explore the following example.

Suppose your system includes a currency rate calculation subroutine, and your program uses that subroutine to perform currency rate conversions. What should happen if you call the subroutine and that subroutine cannot read the record in the database required for proper currency rate conversion? Clearly, the answer depends on several factors, including whether your application is a batch or an interactive one, and whether or not your application can continue without the rate.

It is a complex task to design and document a currency conversion subroutine interface that supports each of these technical scenarios in addition to the business interface requirements. The technical interface requirements (such as batch versus online mode, calling program error processing capabilities, potential error conditions of the subroutine, and error details in message form) will likely overwhelm, or at least confuse, the business application interface requirements.

Java has the advantage of being designed more than 30 years after COBOL, and so its architects have come up with a built-in solution for this type of requirement. This solution is, naturally enough, based on the use of objects. Java defines a special object type called Exception. Classes can describe which exception objects they will create, and which ones they can handle. Moreover, the compiler checks class definitions to make sure that your classes contain appropriate logic for any declared exceptions.

One important design objective of Java exceptions is that standard or business logic should be separated from exception-processing logic as much as possible. At the same time, exceptions, when they do happen, should be dealt with as soon as possible and by the class that is closest to the problem.

EXCEPTION CLASS HIERARCHY

The basic class Java provides to assist in error processing is the Exception class. The Java Exception class inherits from the Throwable class. The Throwable class is also the superclass for the Error class used by the Java runtime processor to report serious runtime errors (you should not normally have occasion to use the Error class directly).

The Exception class comes with many prebuilt subclasses. Two subclasses of particular interest are RuntimeException and IOException. As a general rule, RuntimeExceptions are created as a result of logic errors in your program. Most often, effort should be spent in correcting and preventing this type of error instead of handling it. Most Java applications attempt only to manage the IOException class of errors, or some subclass (that is, specialization) of this error type. Figure 9.1 shows the class hierarchy for the standard Exception classes.

Image

FIGURE 9.1
Exception class hierarchy.

CREATING EXCEPTIONS

A Java class can use any of the predefined Exception objects. These are listed in the SDK documentation set on the CD-ROM and are also available at www.java.sun.com.

Image

If the standard exception definitions do not meet your requirements, you can create new ones. Simply extend either the Exception or (preferably) the IOException class.

Sun follows certain conventions for naming Exception classes, and you should follow this convention as well. In particular, classes that inherit from Exception or RuntimeException should end with Exception. Also, quite a number of standard Exception classes are available on the Internet, so always be sure to check if an Exception class exists before making your own.

This exception will be used when no database is available for the application to connect to:

class NoDatabaseAvailableException extends IOException {
     ...
}

By convention, Exception classes define a constructor that accepts a String parameter. This allows the class that created the exception to place some contextual information in the Exception. In addition, Exception classes normally support a method named getMessage(). This method returns a string that contains the contextual information and perhaps more detailed information about the error condition.

class NoDatabaseAvailableException extends IOException {

      public NoDatabaseAvailableException (String message) {
             super (message);
      }
...

      public String getMessage () {
             return "Unable to access the database for" +
             super.getMessage ();
      }
}

Your new exception is now ready for use.

You can envision an Exception object as an optional return value from a method. You already know that methods can return values as defined in the method interface. In addition, a method can create an Exception object and return this object instead of the normal value.

A Java method that may create an exception must publish that property as part of its interface definition. The syntax for this is as follows:

Image

A class defined in this manner may return a NoDatabaseAvailableException instead of the standard String return value. Suppose you want the getTranslation() method to return with an Exception if a translation is requested and no database is available to retrieve the translation. In addition, you would like to return with the current language code and the text of the original message. Based on these requirements, the conversion class throws an exception with the following syntax:

     if (database.connect () = null) {
           throw new NoDatabaseAvailableException ("LANGUAGECODE = " +
               LANGUAGECODE + " text = " + msgText);
}

When the Java runtime processes the throw statement, the method exits immediately to the calling class. Any statements after the throw statement are not executed. This is roughly analogous to the following COBOL syntax. (It is assumed that the required data elements are defined in the subroutine’s CONTROL item, and that these items begin with the prefix SUB-.)

IF DATABASE-NOT-CONNECTED
        STRING "LANGUAGECODE = ", SUB-LANGUAGE-CODE,
        " text = ", SUB-MESSAGE-TEXT DELIMITED BY SIZE INTO
        SUB-ERROR-MSG
        SET SUB-NO-DATABASE-AVAILABLE TO TRUE
        PERFORM EXIT-PROGRAM.
...
 EXIT-PROGRAM.
       EXIT PROGRAM.

USING EXCEPTIONS

Now, for the last piece of the puzzle. If a method can throw an exception, then any classes that call this method must handle the potential exception(s) in some fashion. A class handles an exception from a method by using the try...catch construct:

Image

Statements inside the try code block will stop executing as soon as an exception is created. If no exceptions are created, or if your class has caught and handled the exception, your class will continue with the statement after the try...catch code block.

Your class can handle multiple exceptions that may be created by statements in the try code block simply by adding an additional catch code block after some other catch code block.

      try        {
          ...
      }
      catch (NoDatabaseAvailableExceptione){
          ...
      }

      catch (IOException    e){
// The exception handler logic for all other I/O conditions.
           System.out.println (e.getMessage() );

      }

Notice that the more specific exception is caught first, then the generic exception. Be sure to execute the logic for the more specific class (the subclass) before the more generic superclass. If you reverse the order, your more specific logic won’t be executed.

Once in a while, you may need to catch an exception and perform some logic based on the exception, but still not completely manage the primary reason for the exception condition. For example, suppose that your TextMessage really needs to be translated before the application can continue. In this case, you should certainly write the exception message to a file (to the standard output device in the example), but in addition, you may want to inform the end user. To meet these requirements, you can rethrow the exception and allow the calling class to complete the error-handling logic.

     catch (NoDatabaseAvailableException    e){
// The exception handler logic.
// This will execute only if getTranslate() creates an exception of type
// NoDatabaseAvailableException.
          System.out.println (e.getMessage() );
// Pass the exception to the calling program.
          throw e;
     }

The last Exception structure is the finally operator. This structure provides a mechanism to define statements that should execute even if an exception occurs. Statements in a finally code block will execute after the try block, even if an exception is caught. So, you can place logic that is always required in the finally block, and it will always be executed.

The finally block is often used to make sure external resources, such as a database connection or an open file handle, are in a properly managed condition at all times.

For example, the getTranslate() function in TextMessage reads a database to get the translated error text. Suppose that in order to read the database, you need to get a database connection from a pool of database connections. If you have any problems reading the database in getTranslate(), you can throw the NoDatabaseAvailableException. However, you still have to manage the connection and return it to the pool.

getTranslate (String message) {
     try            {
           // Get a database connection from the connectionManager pool.
           dbConnection = connectionManager.getConnection();
           ...
           // use dbConnection to read the database.
           // This can cause an Exception to be thrown
           messageFound = dbConnection.read();
           ...
     }
     catch (IOException    e){
           // Build a NoDatabaseAvailableException.
           // Add some context information, such as the message
           // you are trying to translate, the language code
           // and the text from the current exception.
           NoDatabaseAvailableException   noDB = new
NoDatabaseAvailableException
                                     (message + languageCode + e.toString());
           throw .noDB;
     }

     finally {
// This statement will always be executed.
           //  Make sure the dbConnection is returned to the
           //  connectionManager pool.
           connectionManager.releaseConnection(dbConnection);
     }
}

This finally code block will always be performed. It holds for the following conditions:

  • The try block completes normally.

  • The try block explicitly throws an exception that is caught in a related catch block.

  • The try block explicitly throws an exception that is not caught in a related catch block.

  • The try block executes a return statement.

  • The try block causes an exception to be thrown in some other class, which is caught in a related catch block.

  • The try block causes an exception to be thrown in some other class, which is not caught in a related catch block.

This approach gives the developer complete control over exception processing, yet it still provides excellent default exception-processing mechanisms. A class can demand that control be returned to it whenever exceptions happen through the use of the try...catch...finally structure. The runtime will first perform the try function, and then perform the catch function for matching exceptions. In any event, the finally function will always be performed. Even if an exception is thrown by the method, the runtime will execute the finally code block before it passes control back to any calling class.

A class may not be able to handle the exceptions created by classes that it uses. Or the class may only partially handle the exception. In these cases, the class must pass the exception along to its caller, which must in turn either handle it or pass the exception along as well.

The syntax for defining which exceptions will be passed along is also throws. That is, if a class B uses a class C, and C throws an exception, the consumer class B does not necessarily need to handle the error. It can simply define that exception in its own exception list. At runtime, the exception created by C will be automatically passed to the caller of B, as if B created that exception.

// This class does not handle the NoDatabaseAvailableException exception.
// Instead, it is listed as a possible exception in one of its methods.
     public class TextMessageConsumer
          public void someMethod ()
          throws NoDatabaseAvailableException {
          ...
// Some code that may cause a class to create an exception.
// Note that these statements are not in a try...catch code block.
     TextMessage myTextMessage = new TextMessage;
     myTextMessage.getTranslation ();

In this example, the TextMessageConsumer class does not explicitly handle the NoDatabaseAvailableException in a try...catch code block. If this exception were to occur, the caller of TextMessageConsumer.someMethod() would receive the exception, as if the method TextMessageConsumer.someMethod() had created it.

To review, here is the Java specification for exception processing. Suppose that class C throws some exception. Any Java class B that creates an instance of class C must explicitly handle the exception, either by catching it or by throwing it to the class that created B. The compiler checks this for you. As a result, you know at compile time that a class you are using may create an error condition, and your class must either handle the error or pass control along to a class that can.

EXCEPTION-PROCESSING SUGGESTIONS

Exceptions are a very handy way to describe error conditions and ensure that they are properly managed. As is always true, too much of a good thing is not necessarily a better thing.

One concern is that exception processing is much more expensive than is simple testing of a value. It is generally better (performance-wise) to test for an error condition if you can, instead of creating and catching an exception. As a rule, exceptions should be reserved for unusual error conditions and not as a programming technique to test for anticipated conditions.

For example, the getTranslate() method can throw a NoDatabaseAvailableException if no database is available. However, if you simply cannot find the translated text in the database, you should not treat that condition as an exception. The method getTranslate() should handle this condition in some fashion, perhaps by logging the condition to an audit file and returning the original text as the translated text.

Another concern is that a class that throws an exception forces its subclasses and its consumer classes to handle the exception. A class designer should define and throw exceptions only when it makes sense for a class consumer to be aware of the error condition. Of course, if you define a class that uses other classes, and these other classes throw exceptions, you may have to throw them in the classes you build if your class cannot handle the exception internally. This situation only highlights the potential problems created by the aggressive use of exceptions.

Another related suggestion: Make sure that your base classes that will throw exceptions are defined that way early in the development process. It is extremely frustrating to define a new exception in a low-level class, and then have to edit and recompile all the classes that use this class. You will need to recompile because every class that calls a method that can throw an exception must either handle the exception or throw it.

Make sure that your class is in a proper state if you throw or catch an exception. Since a throw statement effectively acts as a goto statement and abruptly interrupts the normal sequence of statements in your class, it is possible that some variable or object in your object will be in an invalid state the next time your object is accessed. If necessary, place appropriate code in the finally code block to ensure that your object is in good shape for the next call.

Finally, group your exception processing into larger try...catch code blocks. For example, don’t insert a try...catch block around every statement that might create an exception. If a section of code might create several exceptions, it is very likely that the exception-handling process is the same for each exception. If this is the case, group the statements into a single, larger try...catch code block.

Another important consideration is defining which exception types to worry about. Many standard Java statements can actually throw a RuntimeException. As a matter of standard practice, you should ignore those types of exceptions in your code. If they happen, something pretty serious is amiss, and your program will terminate with helpful context information, such as the stack trace and what line in your source code likely caused the error.

However, if you build or use a class that throws a type of IOException, then those types should be managed in some manner by your program.

EXCEPTION-PROCESSING SUMMARY

As you can see, Java’s implementation of exception processing helps complex systems manage error conditions in a standard and predictable manner. The requirements of good error processing (as defined in the beginning of the chapter) are encapsulated in the language definition. Classes can define both their return values and their error conditions. Consumer classes must explicitly define how they wish to handle these error conditions, either with try...catch code blocks or by rethrowing the exception. The finally statement ensures that appropriate state-management functions will be executed, even if exceptions occur. Best of all, the compiler understands this definition and checks to make sure you are following the exception-processing rules.

THREADS

All modern computer operating systems are multitasking systems. A multitasking system is one that can perform several tasks simultaneously. The operating system task manager shares computer resources (disk, memory, I/O paths, but especially the CPU) between the various jobs running on the system. Robust operating systems are preemptive multitasking systems, meaning that the task manager will make sure that no single task hogs a resource and causes other tasks to wait.

Historically, an operating system manages its jobs at the process level. Operating system processes are self-contained execution units (as defined by the operating system). The operating system creates, schedules, and provides system resources to processes. Most programming languages (including COBOL and Java) construct a program that will execute as a process in the host operating system. Inside that process, there will be memory space managed by the operating system (kernel space), and memory space that is specific to the process (user space).

Inside a traditional process, there is only one thread of execution, that is, only one statement can be performed at a time. When a statement is complete, the next statement executes. The developer can control the sequence of statements, but there is no way for the developer to have two statements in a program execute at the same time.

In contrast, Java defines a multithreaded execution model. This model allows any number of instruction streams or lines of execution inside a single process to execute simultaneously. That is, your program can have many threads of execution accomplishing work at the same time. A thread is any asynchronous subprocess inside your main program process.

Java threads allow you to construct applications that do not wait for a function to complete before executing another function. Perhaps your program contains an Account object, which reads transactions from a database based on an end user’s request. At the same time that this function is executing, you may need to perform an interrupt() method on a user interface class, which checks to see if the user wants to cancel the request. Without simultaneous threads, you would have to wait for the Account object to complete its work before you could check the user interface class. As a result, the user would not be able to cancel the request until the request was complete!

INHERITING FROM THREAD

Java provides two mechanisms to create new threads. The simplest mechanism is to inherit from the Thread class. This class contains methods that need to be overridden in order to make your Thread class offer functionality. You need to override the run() method and place your execution logic in here. The statements in the run() method will execute simultaneously with other statements in your program. The following statements define a class with a run() method that will print out a message:

      public class MsgThread extends Thread {
           public MsgThread () {
                super ();
           }

           public void run () {
                    for (;;) {
// Create an infinite loop.
                    System.out.println ("Inside a thread");
                    }
               }
          }

In some other class, you can create several instances of these classes.

MsgThread run1 = new MsgThread ();
MsgThread run2 = new MsgThread ();

The threads will not execute yet. In order to get the logic in the Thread classes going, you have to call their run() method. You do this (indirectly) by calling its start() method.

run1.start();
run2.start();

Now both of these threads will execute simultaneously (and endlessly). What if you want to stop an executing thread before it completes? Generally, you should define a stop processing variable and have your code periodically check that variable. For example, suppose you’ve started a thread, and then want to stop that thread and log this event.

    MsgThread run1 = new MsgThread ();
    run1.start();
// Perform some other logic, then stop the thread.
  ...
    run1.stopThread();

    System.out.println ("Thread cancelled" + run1);

Next, you define a method named stopThread in MessageThread. This method sets the flag timeToStop, which will cause the thread to stop. Notice that timeToStop is defined as volatile. This tells the runtime to disable certain optimizations for this variable, ensuring that when this flag is set to true in stopThread(), that value is seen right away in the run() loop.

public class MsgThread extends Thread {
     private volatile Boolean timeToStop = false;
     public MsgThread () {
          super ();
     }

     public void stopThread () {
          timeToStop = true;
    }

Finally, you change the run() loop in MsgThread to check the value of time-ToStop. You exit the run method and stop the thread when that value is true.

          public void run () {
// Create a loop that runs till timeToStop.
               while (!timeToStop) {
                    System.out.println ("Inside a thread");
               }
               System.out.println ("Exit a thread");
          }
     }

IMPLEMENTING RUNNABLE

One obvious limitation of the first thread-management technique is the fact that your class has to inherit from the Thread class. What if you need to build a threaded class, but it must inherit from some other class? In order to support this requirement, Java provides another mechanism to create in-process threads.

The second approach is to have your class implement the Runnable interface. The class that you want to run as a thread needs to define the run() method from this interface. You also should define a stopThread() method that can be used to stop your class.

     public class MsgThreadRunnable
          implements Runnable {
          private volatile Boolean timeToStop = false;
          public void stop Thread() {
               timeToStop = true;
         }

        public void run () {
// Create a loop that runs till timeToStop.
               while (!timeToStop) {
                    System.out.println ("Inside a thread");
               }
               System.out.println ("Exit a thread");
          }
     }

As a last step, you have to create a new MsgThreadRunnable object and pass your object to the Thread constructor. You then use the Thread object’s start() method to start your thread as follows:

     MsgThreadRunnable msg1 = new  MsgThreadRunnable ();
     MsgThreadRunnable msg2 = new  MsgThreadRunnable ();
     Thread run1 = new Thread (msg1);
     Thread run2 = new Thread (msg2);
     run1.start();
     run2.start();
//  Perform some other logic,      then stop the threads.
  ...
     msg1.stopThread();
     msg2.stopThread();

SYNCHRONIZATION

Java threads execute inside a single process (in this case the JVM) and, therefore, share resources within the JVM. Threads in a process normally share the same class instances and address space. Therefore, all object data members are shared between the various threads.

Since Java threads share resources inside the process, the Java program itself has to manage resource utilization conflicts. For example, it would ordinarily be inappropriate for a function to increment a variable while some other function resets that variable to zero.

The solution for this problem is to synchronize your methods or objects. When you synchronize a method, it waits for all other currently executing instances of this object’s method to complete. When you synchronize on an object, Java makes sure that only one thread at a time modifies that object.

Java’s synchronization coordinates access at the object level (either on a java.lang.Object or on the java.lang.Class object in the case of class methods). This means that two distinct object instances of the same class are not coordinated. Static members can be synchronized, but they do not block access to synchronized object instances. To protect a critical method against concurrent access, you can use the synchronized keyword:

public class ErrorMsg {
     public synchronized String getErrorMsg () {
     }
}

In this case, only one thread at a time in a process can call the getErrorMsg() method in a particular instance of the ErrorMsg class. If other threads attempt to call getErrorMsg() for a particular instance of ErrorMsg, the second thread will wait.

Another way to coordinate activities among threads is to define a synchronization block of code. This is a block of code that has been explicitly marked to synchronize on any object or static class member.

      public class ErrorMsg {
           DatabaseConnection myDB = new DatabaseConnection ()

           public String getErrorMsg () {
                if (ErrorMsgnotRead) {
                     synchronized (myDB.CONNECTION_SYNCH) {

// Read the database, and make sure only one instance of DatabaseConnection
// is doing it at a time. The CONNECTION_SYNCH static class member is
// used to synchronize all instances of DatabaseConnection.
                    ...
                    }
               }
          }
     }

Of course, it is up to the developer to make sure that all relevant access to this object is also synchronized. Sometimes this technique is referred to as manual synchronization.

Java 1.5 adds some additional thread management tools in the concurrency utility library. One popular tool is the semaphore. Using a semaphore, a developer can control access to any arbitrary block of code or resource. Think of a semaphore as a type of check out/check in control applied to a defined set of permits. A thread can ask for a permit (for example, permission to use a resource) using acquire(). The thread will block until a permit is available. When a thread is done with the permit, it returns it to the permission pool using release(). When a permission pool has only one permit available, a semaphore can be used as a mutual exclusion locking mechanism.

For example, a semaphore can be used to synchronize access to the output stream (typically the console). A thread can ask for access to the console by calling the acquire() or acquireUninterruptibly() method of an agreed-upon semaphore object. As long as all threads use this same object, only one thread at a time will get control to that object. The others will wait until the thread with control calls the release() method.

BENEFITS AND CAUTIONS

Java’s ability to create and execute multiple threads allows the developer to build responsive, powerful applications. In particular, applications that perform significant work but still need to respond to user input are good candidates for a multi-threaded design. Another good nominee is an application that needs to respond to end-user input from multiple interactive dialog boxes (the search utility in a word processor, for example).

Other possibilities include applications that contain functions that can be processed in parallel. In today’s environment, production systems often have multiple CPUs. Breaking up your task into multiple functions that can run simultaneously allows the system to assign your work to multiple CPUs. The result will likely be more work done in less time.

Some parallel functions are obvious (perhaps you have a data migration application, and it can be structured to extract data from multiple tables simultaneously). Other times the opportunities require a little more thought (how can I break up a single table extract function into multiple steps?). In any event, the effective use of threads can result in more efficient, more responsive applications.

Finally, some environments effectively define the threading model for you. For example, if you are using a JEE application server (discussed in Chapter 15), it is likely that the code you write will execute in a multi-threaded environment.

Still, it is important not to treat threads like a shiny new tool that should be used with every project. As with any good thing, it is possible to get carried away with using threads.

  • The use of multiple threads in an application can be a very difficult programming model. It is up to you (the developer) to anticipate and prevent resource conflicts. In many cases, you even need to manage (schedule, start, stop, and synchronize) threads so that they execute when required. Furthermore, an application that contains a shared resource problem (for example, one thread updates a counter, while another thread decrements it) is a very difficult application to debug. In fact, the debugger can affect the symptoms of the problem you are analyzing, since it runs in a thread of its own.

  • Thread implementations vary significantly across operating systems and virtual machines. Some VMs use the native operating system thread mechanism, whereas others create their own thread management logic in the VM. In the latter case, multi-threaded performance is likely to be less than what you would expect.

  • Don’t recreate an operating system using threads. It is unlikely that an average developer can write process-management and context-switching logic that is more effective than the process-management and context-switching logic built into the operating system. The major advantage of threads is that the developer has more intimate knowledge of the application implementation. Therefore, the developer can identify which parts of the application can be shared between threads and which should be isolated into distinct threads. But if your application is actually a collection of individual functions that have little in common, you are probably better off implementing this application in separate processes for each function and allowing the operating system to manage them for you.

  • Make sure the algorithm behind the application is multithreaded. It doesn’t do a lot of good to create a thread and then immediately wait for it to complete. For example, if your application needs to collect all the required transactions from the database before it can analyze them, you should probably not create the transaction collection function as a thread. On the other hand, if you can start to analyze the transactions as they are collected, it might be appropriate to implement the collection function as a separate thread from the analysis function.

GARBAGE COLLECTION

At any construction site, there are builders who build the structures as well as garbage collectors. These people (or systems) move around the site and remove any unused materials and equipment. They are important contributors to the efficient and effective operation of the site.

Java’s runtime environment works pretty much the same way. Constructors create new objects, and a built-in garbage collection system removes these objects when they are no longer needed. Java determines whether an object is needed using a complex (and ever-improving) algorithm, but the most important attribute of an object (at least from the garbage collector’s perspective) is whether there are any current references to this object. If an object exists in a Java runtime environment but is not referenced by any other object, it will likely be garbage collected, that is, removed from the memory of the currently executing program.

Java’s runtime system includes a garbage collector thread. This low-priority system thread runs periodically, scanning the modifiable portion of memory, in order to detect any objects that are not currently referenced. These are marked for deletion (actually they are identified as not referenced by the scanning process). These objects are subsequently deleted by the garbage-collection process, and the memory associated with them is made available for other objects.

When an object is deleted, it may be appropriate for that object to first clean up certain resources before it is deleted. For example, an object that contains a database connection should probably close that connection as the last thing it does before it is deleted.

An object that needs to clean up resources as it is deleted can declare a finalize() method (in reality it needs to override the finalize() method in the base java.lang.Object class). The garbage collector will call this method before it deletes the object.

By default, there is no guarantee that the finalize method will be called at all! The Java runtime process could exit without first destroying this object. The only guarantee is that if the object is about to be garbage collected, then this method will be called first. There is no guarantee that this object will be garbage collected before the Java runtime process exits. There is also no guarantee on which thread will call finalize().

In the database connection example, the runtime process may terminate before the object’s finalize() method is called and the database connection is properly closed. The database engine will likely detect this event as an application failure and will roll back any incomplete transactions.

Keep this behavior in mind, and code finalize methods defensively. That is, you should only place process-level housekeeping or clean up code (such as memory management functions) in finalize methods. Do not assume that any code in a finalize method will always or ever be performed.

You can also force the garbage collector to run by explicitly calling the System.gc() method.

And as a last issue to be aware of, finalize() methods do not automatically call finalize() methods in a class’s superclass. This is unlike the behavior of constructors, where the superclass’s constructor is automatically called. Nested finalize() calls must be explicitly coded, as shown here:

protected void finalize() throws Throwable {
  super.finalize();
}

EXERCISES: JAVAS EXCEPTIONS AND THREADS

In this exercise, you are going to create some threads that simply display some messages, sleep, and eventually complete execution.

  1. Using a text editor, create a file named MsgThread.java. Add these lines:

           public class MsgThread extends Thread {
                private volatile Boolean timeToStop = false;
                public MsgThread () {

                    super ();
                }

                public void stopThread () {
                     timeToStop = true;
               }

                public void run () {
                     while (!timeToStop) {
    // Create a loop that runs till timeToStop.
                        System.out.println ("Inside thread " +
                          Thread.currentThread().getName());
                   }
                   System.out.println ("This thread is stopping " +
    Thread.currentThread().getName());
              }
         }

  2. Compile the class in the DOS command window:

    →   javac MsgThread.java

  3. Using a text editor, create a file named MsgThreadRunnable.java. Add these lines:

         public class MsgThreadRunnable
              implements Runnable {
              private volatile Boolean timeToStop = false;
              public void stopThread () {
                  timeToStop = true;
               }

            public void run () {
    // Create a loop that runs till timeToStop.
                   while (!timeToStop) {
                        System.out.println ("Inside thread " +
                          Thread.currentThread().getName());
                   }
                   System.out.println ("This thread is stopping " +
    Thread.currentThread().getName());
              }
         }

  4. Compile the class in the DOS command window:

    →   javac MsgThreadRunnable.java

  5. Edit the HelloWorld.java source file in your java4cobol directory with a text editor. Delete the code from the main() method but not the main() method itself.

  6. Next, add the following code to create and then use MsgThread and MsgThreadRunnable.

    import java.util.*;
    //
    //
    // HelloWorld
    //
    //

    public class HelloWorld
    {
             public static void main(String args[]) {

    //   Create an object that inherits from Thread,
    //    and then start that Thread.
                   MsgThread run1 = new MsgThread ();
                   run1.start();

    //   Create an object that implements Runnable,
    //    and then start the execution of that class.
                   MsgThreadRunnable msg1 = new MsgThreadRunnable ();
              Thread run2 = new Thread (msg1);
              run2.start();

    //   Wait a few milliseconds, then stop those threads.
              try {
                  Thread.currentThread().sleep(100);
              }
              catch (InterruptedException e) {
              }

    //   Use the Thread object in this case.
              run1.stopThread();


    //   Use the MsgThreadRunnable object in this case.
              msg1.stopThread();
              System.out.println ("Thread 1 cancelled" + run1);
              System.out.println ("Thread 2 cancelled" + run2);

          }

    }

  7. After compiling the program, you can execute it. Your results should look like this:

    Inside thread Thread-1
    Inside thread Thread-0
    Inside thread Thread-1
    Inside thread Thread-1
    Inside thread Thread-0
    ...

    This will be followed by multiple displays of the thread execution messages. It will end with the completion messages:

    This thread is stopping Thread-0
    Thread 1 cancelledThread[Thread-0,5,main]
    Thread 2 cancelledThread[Thread-1,5,main]
    This thread is stopping Thread-1

REVIEWING THE EXERCISES

Let’s review the samples you’ve created. Feel free to experiment by yourself.

  • Threads can be created either by extending Thread or implementing the Runnable interface. In the example, you used a class (MsgThread) that inherits from Thread and another class (MsgThreadRunnable) that implements the Runnable interface.

  • The useful work in these two classes is performed in the run() method. In the examples, you simply write to System.out, but in practice, a thread will perform some useful task, such as printing a report or waiting for a user response.

  • The threads are stopped when the main class (HelloWorld) calls the stopThread() method.

  • The static method currentThread() always refers to the current thread at whatever point it is called. Each thread has a name assigned by the VM, but that name can be overridden programmatically.

  • The sleep() method causes a thread to stop running and yields control to other threads that might be able to execute.

  • The sleep() method can throw an InterruptedException, so code that includes it must be wrapped in a try...catch block.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset