HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

I don't have to argue the point that the Java language's multi-threading capabilities are great. They're simple to use and generic enough to work on a variety of different implementations. Whether a VM is made to operate using a single operating system thread, like Microsoft's VM, or using native operating system threads for Java threads, like Sun's native threading VM for Solaris, your Java programs will work. I think that's a quiet but powerful feature of Java.

Perhaps it is almost too easy to create and run background threads in Java. Perhaps the designers of Java could have thrown a couple of snafus in there so we programmers didn't go quite so hog-wild sometimes. It can be a problem because a Java VM can't support infinite running threads. In fact, creating a Thread object and starting it running can be very costly in terms of memory.

Don't believe me? Take a look at Listing 1. It's a simple program that just creates as many background Threads as you tell it to on the command-line. Try typing in that program and running it with 1000, or even 10,000, threads. What you should see is an OutOfMemoryError appear after not too long. That's because each background thread requires a lot of memory to run. OutOfMemoryErrors are particularly nasty because there's no verifiable way to recover from them. A program that creates one too many background threads will find itself unable to run and basically will just have to quit.

I really like multi-threaded programs, to the point that I sometimes (not too often) run into this problem. For example, if I use three or four different multi-threaded packages I've created in the past, none of which had an OutOfMemoryError-type problem on their own, I may find the combination of the threads created by the different sub-systems is just too much for a particular VM to handle.

The idea of thread pooling should immediately jump to mind. Thread pooling is when you create a fixed number of background threads and allow your program to use just those finite number of threads, instead of allowing my program objects to create an infinite number of background threads willy-nilly. Usually, thread pooling is used in a client/servant situation. That is, where a servant object (or collection of objects) fulfills any number of simultaneous client requests.

An unconstrained design would have each client request handled by an individual background thread. Listing 2 is an example of an Internet-based service. Each request comes in the form of a client connection to a particular port on the server machine (in this case, port 8888). Listing 2 shows what a main server thread would do: creating a new ClientHandler object and a new Thread for each client request. This main thread is susceptible to being overwhelmed by too many simultaneous client requests. Too many requests and the nasty OutOfMemoryError will appear, probably shutting down the entire server program.

Listing 3 is a ThreadPoolManager class. This class is used to manage a constrained number of threads. Each thread managed by the ThreadPoolManager has a potentially infinite lifetime. Each Runnable object passed to the ThreadPoolManager is queued, and when a thread becomes available to handle the Runnable's task, then that thread is used to run the Runnable's run() method. This way, a huge number of Runnable objects can each have their tasks eventually run without overwhelming the VM by having too many simultaneous running threads. Listing 4 is a replacement for the main thread routine of the server shown in Listing 2. The only change is that the new server uses a ThreadPoolManager's threads instead of creating new threads for each client request.

About the Author
Brian Maso is a programming consultant working out of California. He is the co-author of The Waite Group Press's, "The Java API SuperBible." Before Java, he spent five years corralled in the MS Windows branch of programming, working for such notables as the Hearst Corp., first DataBank, and Intel. Readers are encouraged to contact Brian via e-mail with any comments or questions at [email protected]

	

Listing 1: UnconstrainedThreadCreator program creates as many 
background threads as indicated on the command-line.
 
public class UnconstrainedThreadCreator 
  implements Runnable { 
 private int m_id; 
  private static int m_nextId = 1; 

  public static void main(String[] astrArgs) { 
    try { 
      int nThreads = Integer.parseInt(astrArgs[1]); 
      for(int ii=0 ; ii<nThreads ; ii++) 
        new UnconstrainedThreadCreator(); 
      return; 
    } catch (Exception e) { 
      System.err.println(e); 
      e.printStackTrace(System.err); 
    } 
  } 

  public UnconstrainedThreadCreator() { 
    synchronized(getClass()) { 
      m_id = m_nextId++; 
    } 

    (new Thread(this)).start(); 
  } 

  public void run() { 
    Thread self = Thread.currentThread(); 
    try { 
      while(true) { 
        self.sleep(100); 
        System.out.println("Thread " + m_id + " looping..."); 
      } 
    } catch (InterruptedException ie) { 
    } 
  } 
} 
       

Listing 2: Internet server class with unconstrained thread creation; 
one thread is created per client request. 
(ClientHandler class omited for brevity.)
 
public class INetServer { 

  public static void main(String[] astrArgs) { 
    try { 
      ServerSocket ss = new ServerSocket(8888); 

      while(true) { 
        Socket s = ss.accept(); 
        Client Handler ch = new ClientHandler( 
            s.getInputStream(), s.getOutputStream()); 
        (new Thread(ch)).start(); 
      } 
    } catch (Exception e) { 
       System.err.println(e); 
       e.printStackTrace(System.err); 
    } 
  } 

} 

Listing 3: ThreadPoolManager class and ManagedThread class.
 
public class ThreadPoolManager { 
  private Vector m_runnableVector; 

  public ThreadPoolManager(int threadPoolSize) { 
    m_runnableVector = new Vector(1, 10); 

    for(int ii=0 ; ii<threadPoolSize ; ii++) 
      new ManagedThread(this); 
  } 

  synchronized void threadWaiting(ManagedThread mt) 
      throws InterruptedException { 
    while(0 == m_runnableVector.size()) 
      wait(); 

    Runnable r = (Runnable)m_runnableVector.elementAt(0); 
    m_runnableVector.removeElementAt(0); 
    mt.startRunnable(r); 
  } 

  public synchronized void start(Runnable r) { 
    m_runnableVector.addElement(r); 
    notify(); 
  } 
} 

class ManagedThread extends Thread { 
  private ThreadPoolManager m_manager; 

  ManagedThread(ThreadPoolManager manager) { 
    m_manager = manager; 
    start(); 
  } 

  public void run() { 
    try { 
      while(true) 
        m_manager.threadWaiting(this); 
    } catch (InterruptedException ie) { 
      // just quit. 
    } 
  } 

  void startRunnable(Runnable r) { 
    try { 
      r.run(); 
    } catch (Exception e) { 
      // Print out exceptions thrown by Runnables, 
      // but return normally to allow thread to 
      // continue handling cliuent requests. 
      System.err.println("Error running " + r); 
      System.err.println(e); 
      e.printStackTrace(System.err); 
    } 
  } 
} 

Listing 4: The Internet server program using thread pooling to handle client requests.
 
public class INetServer { 

  public static void main(String[] astrArgs) { 
    try { 
      ThreadPoolManager tpm = new ThreadPoolManager(100); 
      ServerSocket ss = new ServerSocket(8888); 

      while(true) { 
        Socket s = ss.accept(); 
        Client Handler ch = new ClientHandler( 
            s.getInputStream(), s.getOutputStream()); 
> 
        /* HERE'S THE ONLY DIFFERENCE */ 
        tpm.start(ch); 
      } 
    } catch (Exception e) { 
       System.err.println(e); 
       e.printStackTrace(System.err); 
    } 
  } 

}
 
  
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.