This section describes the threading feature on Series 40 and Nokia Asha software platform devices.
Threading does not act the same on different platforms unlike most of other Java aspects. Two main areas of difference are thread scheduling and thread priorities.
In Series 40, all Java threads run in an operating system task. The number of threads is effectively limited by Java heap memory. Java threads cannot run at higher priority than native code in Series 40.
Threading change is transparent for MIDlets and does not cause any issues for correctly implemented MIDlets.
For thread handling in MIDlets:
The choice of which thread the Thread.notify
method wakes is arbitrary.
Java VM process can have maximum of 128 threads. The actual number of available threads is usually less as the VM implementation itself and stack size changes may reserve some threads.
Thread priorities should not be modified because this may not result in performance gains and may have adverse effects.
Thread.yield()
Do not use the Thread.yield()
method because it can lead to busy loop-like behavior as yielding
thread can be scheduled to run immediately again if there are no other
eligible threads to be run.
Thread.sleep()
Do not
use the Thread.sleep()
method because it can
lead to busy loop-like behaviour that can affect the battery life
and other applications.
Wait-notify should also be always preferred
over Thread.sleep
looping, as Thread.sleep
does not react to events immediately (even a small sleep value takes
at least that long to react).
The methods notify and wait are used often when two or more threads need to access the same operation or method and they need to prioritize their execution order. This type of coordination, typically involves one thread accessing the method first, after which the resource becomes blocked. The second thread waits until the active thread completes its operation, releases the block and notifies the waiting thread. The wait method saves processing time, which would otherwise have been wasted, if a simple while loop had been used:
//Avoid a simple while loop while (isDownloading) { //repeat } //Use wait instead if (isDownloading) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
The keyword synchronized needs to be used in front of the method, whose resources, the threads are attempting to access.
The following example demonstrates how two threads
can perform a download and upload operation while accessing the same
Form instance in order to display their activity progress. The Downloader
thread needs to access the Form first and perform the download operation.
The Uploader thread is waiting for Downloader to complete. As soon
as this is done, the Downloader thread notifies the waiting Uploader
thread, which then initiates its operations. The views for the downloading
and uploading operations are put within synchronized methods. A Boolean
variable, isDownloading
is used to toggle between
the views. This is a custom Form implementation:
import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.Gauge; public class CustomForm extends Form implements CommandListener { private boolean isDownloading = true; private Gauge downGauge; private Gauge upGauge; private Command exit; private WaitNotifyExampleMIDlet midlet; public CustomForm(String title, WaitNotifyExampleMIDlet midlet) { super("Wait Notify"); this.midlet = midlet; //Initialization of display items for the download and upload views downGauge = new Gauge("", false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING); upGauge = new Gauge("", false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING); exit = new Command("Exit", Command.BACK, 1); } public synchronized void displayDownload() { //The Downloader thread needs to access the method first if (isDownloading) { //Display the Download view this.setTitle("Downloading"); this.append("Please wait, for download to complete, before uploading can initiate..."); this.append(downGauge); this.addCommand(exit); this.setCommandListener(this); //Let the indefinite indicator run continuously for 3 seconds try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //toggle the view and notify the waiting thread isDownloading = false; notifyAll(); } } public synchronized void displayUpload(){ //The Uploader thread waits until the Downloader completes if (isDownloading) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //Let the indefinite indicator run continuously for 3 seconds this.deleteAll(); this.setTitle("Uploading"); this.append("Download completed! Uploading..."); this.append(upGauge); this.addCommand(exit); this.setCommandListener(this); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //toggle to the Done view isDownloading = false; displayDone(); } //The Done view is dislayed after both the Downloader and Uploader threads have completed their operations public void displayDone() { this.deleteAll(); this.setTitle("Done"); this.append("Both download and upload are now completed!"); this.addCommand(exit); } public void commandAction(Command c, Displayable d) { if(c == exit) { midlet.notifyDestroyed(); } } }
The Donwloader and Uploader threads are simple implementations that call the synchronized displayDownload and displayUpload methods, respectively:
import java.util.Random; import javax.microedition.lcdui.Form; public class Downloader implements Runnable{ private CustomForm form; public Downloader(CustomForm form) { this.form = form; } public void run() { form.displayDownload(); } }
import java.util.Random; import javax.microedition.lcdui.Form; public class Uploader implements Runnable{ private CustomForm form; public Uploader(CustomForm form) { this.form = form; } public void run() { form.displayUpload(); } }
The order in which the Uploader and Downloader methods will access the Form is controlled by the toggle variable and does not depend on the order in which the threads are started. The main MIDlet class below, initiates first the Uploader and then the Downloader, even though, the Downloader thread is executed first:
import javax.microedition.lcdui.Display; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; public class WaitNotifyExampleMIDlet extends MIDlet { Display display; CustomForm form; private Thread downThread; private Thread upThread; protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { // TODO Auto-generated method stub } protected void pauseApp() { // TODO Auto-generated method stub } protected void startApp() throws MIDletStateChangeException { if(display == null){ display = Display.getDisplay(this); form = new CustomForm("", this); display.setCurrent(form); //The Downloader and Uploader threads are instantiated with reverse order upThread = new Thread(new Uploader(form)); downThread = new Thread(new Downloader(form)); downThread.start(); upThread.start(); } } }
Threads
are used extensively on Asha software platform to wrap protected method
calls that are initiated from the CommandAction()
method. While operations that require the approval of the user,
can be executed directly from the push of a Command on Series 40,
the same operations need to run inside a Thread, on Asha software
platform. For more information see Protected calls from
Commands.