Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Technical Tips

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button
 
Tech Tips index

Tech Tips
September 15, 1998

This issue presents tips, techniques, and sample code for the following topics:

Using Synchronized Statements
Suppose that you're experimenting with JavaTM language threads, and you write a simple program such as the following:

    public class sync extends Thread {
        static int n = 1;
    
        public void run()
        {
            for (int i = 1; i <= 10; i++) {
                System.out.println(n);
                n++;
            }
        }
    
        public static void main(String args[])
        {
            Thread thr1 = new sync();
            Thread thr2 = new sync();
    
            thr1.start();
            thr2.start();
        }
    }

When you run this program, you get output like the following:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    9
    10
    11
    13
    14
    15
    16
    17
    18
    19
    20

Two instances of "9" occur, and none of "12" occur. Your results may vary from this output, which is the whole point of this example.

Such results are "impossible," because "n" is incremented immediately after printing its value, isn't it? How can the same value be printed twice, or another value omitted altogether?

The reason that this type of output can occur has to do with the nature of thread programming. In the following sequence:

    System.out.println(n);
    n++;

with two threads executing, nothing says that these two statements must be executed atomically (that is, without any interruption between statements) within one thread, without the other thread gaining control. For example, a sequence such as the following:

    System.out.println(n);
    System.out.println(n);
    n++;
    n++;
is quite possible, leading to the results show above. This problem is basic to multithreaded programming, system-level programming in operating systems, and so on.

To solve this problem, the example can be rewritten as follows:

    static Object critsect = new Object();

    public void run()
    {
        for (int i = 1; i <= 10; i++) {
            synchronized (critsect) {
                System.out.println(n);
                n++;
            }
        }
    }

This technique typically goes by the name of "critical sections," that is, regions of a program that can be safely executed by only one thread at a time. In this example, a class variable named "critsect" is set up, and used together with the "synchronized" statement to lock the region of code within the { } against simultaneous access. This procedure guarantees that the I/O statement and the following increment are performed atomically for a particular thread, before another thread is allowed to perform the same operations.

Also, another type of synchronization is the synchronized instance method. In this method, the whole method is protected against simultaneous access, based on obtaining a lock on the "this" reference to the particular class instance being operated upon by the method.

Finally Clauses
You may have seen the try...catch statement used in exception handling. With such a statement, a block of code is tried (executed), and any exceptions thrown as a result are directed to the various catch clauses. This type of statement also supports the use of a "finally" clause, which is a block of code that is executed whether or not the try block completes normally, results in an exception, or exits the containing method. A finally clause is therefore useful as a cleanup mechanism, and try...finally can be used without catch, and without specifically using exceptions.

To see how all this works, consider the construction of a simple method, one that copies one file to another. The method should accept two file names, and propagate out any IOException that occurs (rather than trapping it internally). Here is some code that implements this idea, along with a driver program:

import java.io.*;
    
public class FileCopy {
  public static void copy(String from, String to)
    throws IOException
    {
      FileInputStream fis = null;
      FileOutputStream fos = null;
    
      try {
        fis = new FileInputStream(from);
        fos = new FileOutputStream(to);
    
        final int BUFSIZ = 1024;
        byte buf[] = new byte[BUFSIZ];
        int len = 0;
    
        while ((len = fis.read(buf)) > 0)
          fos.write(buf, 0, len);
      }
      finally {
        if (fis != null)
          fis.close();
        if (fos != null)
          fos.close();
      }
    }
    
    public static void main(String args[])
    {
// do many invalid copy() operations

     for (int i = 1; i <= 5000; i++) {
       try {
         copy("xxx.txt", ".");
       }
       catch (IOException e) {
     }
   }
    
// do a valid operation

    try {
      copy("xxx.txt", "yyy.txt");
    }
    catch (IOException e) {
      System.err.println(e);
    }
  }
}

The driver exercises a particular aspect of this programming problem, that of gracefully handling repeated errors. In this example, the error is an invalid output file for the copy (an attempt is made to copy to ".").

The copy method does not require a try...catch statement, because it propagates out the exception, and declares it in a throws clause. But what happens if copy is repeatedly called, with an invalid output file argument, after the FileInputStream for the input file has been successfully established? What can happen is a resource leak, with operating system file descriptors being allocated and eventually depleted. This situation is not the same as a memory leak, but instead simply reflects a failure to call close on the FileInputStream. Garbage collection will result in the finalize method being called for FileInputStream, and it calls close, but there's no guarantee that garbage collection will be invoked in a timely way. So the second copy invocation in the example above, one that is supplied valid arguments, fails without the try...finally.

To fix this problem, a try...finally statement is used. No matter what happens in copy, the finally clause will be invoked, and it will close any open input stream. In this way, a finally clause can be used as a general cleanup mechanism.


Print Button
[ This page was updated: 21-Sep-2000 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2000 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.