Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Understanding MIDP System Threads

 
By Eric Giguere, December 2004  

Concurrency - multiple threads executing simultaneously within a single application - is a complex topic. Although Java technology has always included extensive built-in support for multithreading, writing correct concurrent code is seldom as simple as it seems. To give you some welcome help J2SE 5.0 (also called Java 1.5) adds a number of concurrency utilities that are based on work by Doug Lea, author of Concurrent Programming in Java - an excellent reference, well worth reading. These new classes are not available to J2ME applications, unfortunately, so the basic techniques discussed in the article "Using Threads in J2ME Applications" still apply. This tech tip focuses on dealing with system threads, an aspect of MIDP programming that trips up many a beginner.

A system thread is any thread not started and managed by an application. In a MIDP environment, system threads are created by the application management software (AMS) that controls MIDlet execution. Each active application has at least one system thread and zero or more application threads.

Notice that "at least"! An implementation is free to use multiple system threads for different operations or, if necessary, to share system threads among multiple MIDlets - while respecting the MIDlet sandbox and other restrictions. The MIDP specification makes no guarantees as to which threads the AMS uses to perform particular operations.

Consider the act of creating a MIDlet. The AMS might use code like this to load and run it:

	// Load and start a MIDlet
	String name = readMIDletName();
	MIDlet m = null;

	try {
		m = (MIDlet) Class.forName( name ).newInstance();
		registerMIDlet( m );
	}
	catch( Throwable e ){
		abortMIDlet( name, m );
	}

The problem with this approach is that if the MIDlet deadlocks during construction or startup, the AMS will be blocked too. A more paranoid AMS spawns a new thread to load and start the MIDlet. It then waits for initialization to complete, or for a reasonable timeout period to elapse. The MIDlet itself has no control over the startup procedure, so the best strategy for the MIDlet developer is to ensure that the constructor and the startApp() method run quickly and don't block.

MIDlets are more likely to deadlock when they're already up and running. The AMS uses system threads to deliver events and notifications to the MIDlet. Again, there's no guarantee that these are delivered by the same thread, though in practice they usually are. The AMS delivers them directly to the MIDlet by invoking methods of various objects. For example, the AMS calls the paint() method of a Canvas instance whenever the canvas needs to be repainted.

The AMS effectively loses control of the thread making the delivery. To ensure that the system keeps running smoothly the MIDlet's behavior is subject to an implicit contract between it and the AMS. The contract has two simple parts, equally important: run quickly; don't block. The first part tells the MIDlet to process the event or notification as quickly as possible and thus return control of the thread promptly. The second part dictates that the MIDlet not block the system thread making the delivery.

Processing events quickly helps keep the system responsive. Normally, a single event thread delivers events to an application, because most events make sense only when they're delivered in a serial fashion - you want to receive a pointer-release event after the corresponding pointer-press event. If performing a long operation in response to one event causes later events to queue up for processing, an application can feel sluggish.

Not blocking is really just a special case of processing events quickly, but it deserves particular attention. Blocking occurs when a thread suspends itself and waits for another thread to complete an operation. From what you just learned, you can imagine that blocking the event thread is bad. Even worse is causing the event thread to deadlock. Two operations can easily wind up waiting endlessly for each other to finish, even in this innocuous-looking piece of code:

	String url = ...
	Connection conn = null;

	try {
		conn = Connector.open( url );
		// do something here
	}
	catch( IOException e ){
		// error
	}

The root of the problem is the blocking nature of the open() call. On some platforms, the system does the actual connection under the covers, on the equivalent of a separate thread. The calling thread blocks until the connection thread makes the connection. At the same time, the security subsystem may require the user to confirm the connection, and the connection thread blocks until the event thread gets confirmation from the user. Deadlock occurs because the event thread is already waiting for the connection thread.

To avoid such problems, the best approach is to move blocking operations to threads created by the application. It complicates application programming, but in the long run it's a safer solution.

Some tools help you out. The J2ME Wireless Toolkit, for example, prints a message on the console whenever it detects a possible deadlock. Keep deadlock risks in mind when you're designing your applications. Threads are useful for many purposes, and easy to start, but they can be harder to use correctly than you think.