Chapter 20

Introduction to Multi-Threading

In March of 2010 I was interviewed by Janice Heiss from Oracle (http://java.sun.com/developer/technicalArticles/Interviews/community/yakov_qa_2010.html), and she asked me to name the Java class I couldn’t live without. I didn’t think twice: Thread. This is where the power of Java resides. There are books devoted to Java threads, which give you amazing power and flexibility when it comes to building highly available, scalable, and responsive applications capable of processing thousands of concurrent requests of various types.

Up until now you’ve been creating Java programs that were executing code sequentially. But the main power of Java lies in its ability to do things in parallel, or, as they say, to run multiple threads of execution. As always, going through a practical use case is the best way to understand this feature.

Let’s discuss a program that should display market news and information about the user’s stock portfolio in the same window. While the market news feed is coming from a remote computer, stock portfolio data are retrieved from the database and may be located on the local machine.

Suppose it takes five seconds to get the market news and only three seconds to get the portfolio data. If you run these two tasks sequentially (one after another), you need eight seconds to complete the job.

But market news doesn’t depend on your portfolio data and these two tasks can run in parallel. They run on different computers and use different processors. If you can implement parallel processing, the total time should be less than eight seconds (close to five seconds in our use case — the time it takes for the longer task to complete).

A Java program can start multiple threads of execution that will run in parallel. Even if you have only one processor in your computer, it still could be a good idea to run some tasks in parallel. Think of a web browser that allows you to download a file and perform page browsing at the same time. Web browsers maintain two or more connections to any website, and can download multiple resources (text, images, videos, music) at the same time. Any web browser works in a multi-threaded mode.

If these jobs ran sequentially, the browser’s window would be frozen until the download was complete. On a multiprocessor computer, parallel threads can run on different CPUs. On a single-processor computer, threads will take turns getting “slices” of the processor’s time. Because switching CPU cycles between threads happens fast, a user won’t notice the tiny delays in each thread’s execution, and browsing will feel smooth.

In many cases, especially on single CPU computers, the benefit of many threads comes about because there’s a lot of idle time in most operations. In particular, if an operation is I/O bound instead of CPU bound using multiple threads helps take advantage of those otherwise unused blocks of time.

People also can work in a multi-threaded mode. For example, they can drink coffee while talking on a cell phone and driving a car. In the airport you can see people walking on the moving belt — they will reach their gate faster than by standing still.

The Class Thread

If class A needs to initiate some executions in classes B and C, the latter two must declare multi-threading support from the get-go. Each of the classes B and C must either be inherited from the Java class Thread or implement one of the following interfaces: Runnable or Callable (the latter is covered in Lesson 21). If a class is inherited from the class Thread it has to override the method run().

The first version of the market-portfolio example has three classes (see Listings 20-1, -2, and -3). Two of them are subclasses of the class Thread (MarketNews and Portfolio), and the third (TestThreads) is just a testing program that instantiates them and starts the execution of some code in each of them. You must initiate the code that has to work as a thread in the method run().

download.eps

Listing 20-1: Class MarketNews

public class MarketNews extends Thread {
 public MarketNews (String threadName) {
        super(threadName);  // name your thread
    }
 
  public void run() {
      System.out.println( "The stock market is improving!");
     }
}
download.eps

Listing 20-2: Class Portfolio

public class Portfolio extends Thread {
    public Portfolio (String threadName) {
        super(threadName);  
    }
 
    public void run() {
       System.out.println( "You have 500 shares of IBM ");
     }
}
download.eps

Listing 20-3: Class TestThreads starts MarketNews and Portfolio

public class TestThreads {
    public static void main(String args[]){
      MarketNews mn = new MarketNews("Market News");
      mn.start();
         
      Portfolio p = new Portfolio("Portfolio data");
      p.start();
 
      System.out.println( "TestThreads is finished");
    }
}

The method main() in Listing 20-3 instantiates each thread, passing the name for the thread as a constructor argument, and then calls its start()method. Each thread itself invokes internally the code located in its method run(). After calling mn.start(), the program TestThread does not wait for its completion but immediately executes the lines that follow, creating and starting the thread Portfolio. Even if MarketNews will take time, the Portfolio thread will start immediately.

If you run the TestThread program it’ll print the output from threads MarketNews and Portfolio almost simultaneously — there is no lengthy and time-consuming code in their run() methods. A bit later, in the section “Sleeping Threads,” I’ll show you how to emulate a lengthy execution. The output of the TestThread program can vary — it all depends on which thread will finish first.

The Interface Runnable

The second way to create threads is to implement a Runnable interface. In this case your class also has to have business logic in the method run(). The second version of our market-portfolio example (Listings 20-4, -5, and -6) also has three classes, but MarketNew2 and Portfolio2 are not inherited from the class Thread — they implement the Runnable interface.

Creation of a thread in this case is a two-step process: create an instance of a class that implements Runnable and then give it as a constructor argument during instantiation of the class Thread.

download.eps

Listing 20-4: Class MarketNews2

public class MarketNews2 implements Runnable {
     public void run() {
        System.out.println( "The stock market is improving!");
     }
}
download.eps

Listing 20-5: Class Portfolio2

public class Portfolio2 implements Runnable {
    public void run() {
       System.out.println( "You have 500 shares of IBM ");
     }
}
download.eps

Listing 20-6: Class TestThreads2

public class TestThreads2 {
    public static void main(String args[]){
     
     MarketNews2 mn2 = new MarketNews2();
     Thread mn = new Thread(mn2,"Market News");
     mn.start();
         
     Runnable port2 = new Portfolio2();
     Thread p = new Thread(port2, "Portfolio Data");
     p.start();
 
     System.out.println( "TestThreads2 is finished");
    }
}

Note that I’ve declared the variable port2 in Listing 20-6 to be not of type Portfolio2, but of type Runnable. I did it for illustration purposes and to encourage you to use what you’ve learned about casting to interfaces in Lesson 7 (see Listing 7-5). It takes three lines of code in Listing 20-6 to instantiate and start a thread. You can do this in one line, but this syntax won’t give you a reference to the thread object should you need to call any methods on this object in the future (see variables mn and p in Listing 20-6):

(new Thread(new MarketNews2("Market News"))).start();

The Runnable interface provides a more flexible way to use threads, because it allows a class to be inherited from any other class, while having all the features of a thread. For example, a Swing applet must be a subclass of a javax.swing.JApplet, and, because Java doesn’t support multiple inheritance, it should implement Runnable to support multi-threading.

Sleeping Threads

One of the ways to make the processor available to other threads is by using Thread’s method sleep(), which enables you to specify in milliseconds (and nanoseconds) how long the thread has to sleep. For example, the thread MarketNews3 in Listing 20-7 puts itself to sleep for a thousand milliseconds (one second) after each output of the message about market improvements.

download.eps

Listing 20-7: Class MarketNews3

public class MarketNews3 extends Thread {
   public MarketNews3 (String str) {
    super(str);            
   }
 
  public void run() {
    try{
      for (int i=0; i<10;i++){
       sleep (1000);  // sleep for 1 second
       System.out.println( "The market is improving " + i);
      } 
    }catch(InterruptedException e ){
       System.out.println(Thread.currentThread().getName() 
                                        + e.toString());
    }     
  }
}

When MarketNews3 goes to sleep, the thread Portfolio from Listing 20-8 gets the CPU and will print its message and then sleep for 700 milliseconds on each loop iteration. Every second MarketNews3 wakes up and does its job.

download.eps

Listing 20-8: Class Portfolio3

public class Portfolio3 extends Thread {
   public Portfolio3 (String str) {
        super(str); 
   }
 
   public void run() {
     try{
      for (int i=0; i<10;i++){
       sleep (700);    // Sleep for 700 milliseconds 
       System.out.println( "You have " +  (500 + i) +  
                                      " shares of IBM");
      }
    }catch(InterruptedException e ){
      System.out.println(Thread.currentThread().getName() 
                                        + e.toString());
    }     
  }
}

After adding the sleeping part to our thread, the program TestThreads3 (see Listing 20-9) will generate mixed console output about market and portfolio from both threads — this proves that they are taking turns even with the single-processor machine.

download.eps

Listing 20-9: Class TestThreads3

public class TestThreads3 {
 
    public static void main(String args[]){
     
         MarketNews3 mn = new MarketNews3("Market News");
         mn.start();
 
         Portfolio3 p = new Portfolio3("Portfolio data");
         p.start();
         
         System.out.println( "The main method of TestThreads3 is finished");
    }
}

If you need to “wake up” a sleeping thread before its sleeping time is up, use the method interrupt(). Just add mn.interrupt() to the class TestThreads3 right after starting the MarketNews thread. This will trigger InterruptedException, and MarketNews3 will wake up and continue its execution from the operator located below the sleep() method call. The class Thread has a method interrupted() that returns true if the current thread has been interrupted. The output of the program TestThreads3 can look as follows:

The main method of TestThreads3 is finished
You have 500 shares of IBM
The market is improving 0
You have 501 shares of IBM
The market is improving 1
You have 502 shares of IBM
You have 503 shares of IBM
The market is improving 2
You have 504 shares of IBM
The market is improving 3
You have 505 shares of IBM
You have 506 shares of IBM
The market is improving 4
You have 507 shares of IBM
The market is improving 5
You have 508 shares of IBM
The market is improving 6
You have 509 shares of IBM
The market is improving 7
The market is improving 8
The market is improving 9

These days it’s hard to find a single-CPU server machine. Most of the readers of this book have dual-core CPUs — these have two processors in the same chip. Modern JVMs use multiple cores for multi-threaded applications, but you shouldn’t assume that your program will run twice as fast on such hardware. JVM optimization is a complex subject and is out of the scope of this tutorial. You may boost the performance of your system by increasing the number of threads running in parallel, but you should define the right ratio between the number of threads and the number of processors during the performance-tuning phase of application development.

How to Kill a Thread

The class Thread has a method, stop(), that was supposed to know how to kill the current thread. But it was deprecated many years ago because it could bring some of the objects in your program into an inconsistent state caused by improper locking and unlocking of the object instances.

There are different approaches to killing threads. One of them involves creating your own method on the thread, say stopMe(), in which you set your own boolean variable, say stopMe, to false and test its value periodically inside the thread’s method run(). If application code will set the value of stopMe to true, just exit the code execution in the method run(). In Listing 20-10, the loop in the method run checks the value of the variable stopMe, which is initialized with the reference to the current Thread object. As soon as it is changed (set to null in this case), the processing will complete.

download.eps

Listing 20-10: Killing a thread

class KillTheThread{
           public static void main(String args[]){
           Portfolio4 p = new Portfolio4("Portfolio data");
         p.start();
         
         // Some other code goes here,
         // and now it's time to kill the thread
         p.stopMe();
 
           }
}
 
class Portfolio4 extends Thread{
 
private volatile Thread stopMe = Thread.currentThread();
      
    public void stopMe() {
        stopMe = null;
    }
 
    public void run() {
        while (stopMe == Thread.currentThread()) {
          try{
            //Do some portfolio processing here
          }catch(InterruptedException e ){
            System.out.println(Thread.currentThread().getName() 
                                        + e.toString());
        }
 
     }
 }

The variable stopMe has been declared with a volatile keyword, which warns the Java compiler that another thread can modify it and that this variable shouldn’t be cached in registers, so that all threads must always see its fresh value. The class Portfolio4 could be written differently — the variable stopMe could be declared as boolean.

Not every thread can be killed using the code shown in Listing 20-10. What if a thread is not doing any processing at the moment, but is waiting for the user’s input? Call the method interrupt() on such a thread. Killing a thread by interrupting it may be the only technique you need to use in such cases.

If you need to kill a thread that’s busy doing some blocking I/O operations and the preceding methods of killing such a thread don’t work, try closing I/O streams — this will cause IOException during the current read/write operation and the thread will be over.

If you’d like to read more comprehensive coverage of this subject, see Dr. Heinz Kabutz the Java Specialist’s newsletter Issue #56, available at www.javaspecialists.co.za/archive/Issue056.html.

Thread Priorities

Single-processor computers use a special scheduling algorithm that allocates processor time slices to the running threads based on their priorities. If Thread1 is using the processor and the higher-priority Thread2 wakes up, Thread1 is pushed aside and Thread2 gets the CPU. It is said that Thread2 preempts Thread1.

The class Thread has a method, setPriority(), that allows you to control its priority. There are 10 different priorities, which are final integer variables defined in the class Thread. Some of them are named constants MIN_PRIORITY, NORM_PRIORITY, and MAX_PRIORITY. Here’s an example of their usage:

   Thread myThread = new Thread("Portfolio");
   myThread.setPriority(Thread.NORM_PRIORITY + 1);

If two threads with the same priority need the processor, it’ll be given to one of them using an algorithm specific to the computer’s OS.

Thread Synchronization and Race Conditions

During the design stage of a multi-threaded application’s development you should consider the possibility of a so-called race condition, which happens when multiple threads need to modify the same program resource at the same time (concurrently). The classic example is when a husband and wife are trying to withdraw cash from different ATMs at the same time.

Suppose the balance on their joint account is $120. If a Thread class is responsible for the validation and update of the balance of their bank account, there is a slight chance that the husband’s thread will enable him to withdraw $100, but that before the actual withdrawal the wife’s thread will come in trying to validate a $50 withdrawal. She is also enabled to make her withdrawal, because $120 is still there! The couple would successfully withdraw a total of $150, leaving a negative balance in their account. This is an example of a race condition.

A special keyword, synchronized, prevents race conditions from happening. This keyword places a lock (a monitor) on an important object or piece of code to make sure that only one thread at a time will have access. The code in Listing 20-11 locks the entire method withdrawCash()so no other thread will get access to the specified portion of code until the current (locking) thread has finished its execution of withdrawCash().

download.eps

Listing 20-11: Declaring a synchronized method

class ATMProcessor extends Thread{
   ... 
 synchronized withdrawCash(int accountID, int amount){
    // Some thread-safe code goes here, i.e. reading from 
    // a file or a database
      ...
   boolean allowTransaction = validateWithdrawal(accountID, 
                                                   amount);
   if (allowTransaction){
     updateBalance(accountID, amount, "Withdraw");
   }
   else {
    System.out.println("Not enough money on the account"); 
   }
  }
 } 

The locks should be placed for the shortest possible time to avoid slowing down the program: That’s why synchronizing short blocks of code is preferable to synchronizing whole methods. Listing 20-12 shows how to synchronize only the portion of the code that may cause the race condition, rather then locking the entire method withdrawCash().

download.eps

Listing 20-12: Declaring a synchronized block

class ATMProcessor extends Thread{
  ...
 withdrawCash(int accountID, int amount){
    // Some thread-safe code goes here, i.e. reading from 
    // a file or a database
   ...
  synchronized(this) {
   if (allowTransaction){
    updateBalance(accountID, amount, "Withdraw");
   }
   else {
    System.out.println("Not  enough money on the account"); 
   }
  }
 }
}

When a synchronized block is executed, the section of the code in parentheses is locked and can’t be used by any other thread that’s locked on the same section of the code until the lock is released. Listing 20-12 locks the current instance of the class ATMProcessor (represented by the this keyword) only for the duration of the updateBalance() method, which is a shorter period of time than locking withdrawCash() would take.

Java Enumerations

The enum construct is not related to the thread concept and can be used for a variety of reasons, but because this lesson refers to enumerations for the first time in this book, a little sidebar explanation seems appropriate.

If you need to define a group of predefined constants, you can use the final keyword. For example, to define various categories of products in a store you can declare the following constants:

public static final int CATEGORY_BOOKS  = 1;
public static final int CATEGORY _VIDEOS  = 2;
public static final int CATEGORY _ELECTRONICS  = 3; 
public static final int CATEGORY _TOYS  = 4;

But Java offers a special keyword, enum, that enables you to define such constants in a more elegant way:

enum Category {BOOKS,VIDEOS,ELECTRONICS,TOYS}

Not only is this notation more compact, but it also prevents the programmers from having to prefix each constant with CATEGORY_ to stress that these values represent a group, but you can define variables to represent the entire group, pass enum as an argument to a method, and return enum from a method. For example, if you need to write a program that will redirect the store customer to the proper representative, you can write something similar to what’s shown in Listing 20-13.

download.eps

Listing 20-13: enum example

enum Category {BOOKS,VIDEOS,ELECTRONICS,TOYS}
 
public class EnumExample {
      ...
    public static void redirectToRepresentative(Category cat){
          
          switch(os) {
            case BOOKS:
                System.out.println("Get Mary on the phone");
                break;
            case VIDEOS:
                System.out.println("Call Raj");
                break;
            case ELECTRONICS:
                System.out.println("Find Boris");
                break;
            case TOYS:
                System.out.println("Email David");
                break;
            default:
                System.out.println("We don't sell such products.");
                break;
        }
    }
}

All enums implicitly extend java.lang.Enum and implement Comparable and Serializable interfaces. On top of that you can declare enum variables that implement other interfaces, and declare additional methods, for example:

 enum Category implements Sellable {
      BOOKS,VIDEOS,ELECTRONICS,TOYS;
 
    printSalesData(int region){
       // Calculate sales here
    }   
 
}

Thread States

A thread goes through various states during its life span. The class Thread has a method, getState(), that will return one of the values defined in the enumeration Thread.State.

  • BLOCKED: Thread state for a thread that is blocked and waiting to enter or reenter a synchronized method or block of code
  • NEW: Thread state for a thread that has been instantiated but not started yet
  • RUNNABLE: Thread state for a runnable thread
  • TERMINATED: Thread state for a terminated thread
  • TIMED_WAITING: Thread state for a waiting thread with a specified waiting time
  • WAITING: Thread state for a waiting thread

The class Thread has a method, isAlive(), that can help you to find out the status of the thread. If it returns true, the thread has been started and hasn’t died yet. If it returns false, the thread is either new or already dead.

Wait and Notify

The class Object also has some methods relevant to threads: wait(), notify(), and notifyAll(). Because every Java class is inherited from the class Object, these methods can be called on any object.

Let’s revisit our class TestThreads3, which spawns the threads MarketNews3 and Portfolio3. It has the following line at the end of the main() method:

System.out.println("The main method TestThreads3 is finished");

Run the program TestThreads3 and it’ll print on the system console something like this:

The stock market is improving 1
You have  500  shares of IBM
The main method of TestThreads3 is finished
The stock market is improving 2
You have  501  shares of IBM
The stock market is improving! 3
You have  502  shares of IBM
...

Note that the method main() did not wait for the portfolio and market news threads’ completion! But if the main class needs to wait until the threads complete their actions, you can use the method wait():

public static void main(String args[]){
  ... 
 mn.start();
  ...
 p.start();
 
 synchronized (this) {
   try{
     wait(10000);   
   } catch (InterruptedException e){  ...}
 }
  System.out.println("The main method of TestThreads3 is finished");
}

The method call wait(10000) means “wait for up to 10 seconds.” The last println statement will be executed either after 10 seconds or when this thread receives notification of some important event, whichever comes first. Examples of important events are a price drop on the auction for items you’re interested in, the reopening of the airport after freezing rain, and the execution of your order to purchase 100 shares of IBM stock. A thread can notify other thread(s) by calling the method notify() or notifyAll().

Calling sleep(10000) puts a thread into a not-runnable state for exactly 10 seconds, although wait(10000) may mean that it will come back to a runnable state earlier. If the method wait() is called without any arguments, the calling program will wait indefinitely until it receives a notification. The call to wait() makes the current thread give up its lock to another one and puts it to sleep until either the specified time has elapsed or the thread gets a notification from another object. Let’s consider one of the use cases illustrating this.

ClassA spawns a thread, ClassB, and starts waiting. ClassB retrieves some data, and then sends a notification back to the instance of ClassA. The ClassA resumes its processing after notification has been received. Listing 20-14 shows implementation of this scenario. Note that the ClassB gets the reference to the object ClassA so it knows who to notify.

download.eps

Listing 20-14: Notification example

class ClassA  {
 String marketNews = null;
 
 void  someMethod(){
 
   // The ClassB needs a reference to the locked object 
   // to be able to notify it
   
   ClassB myB=new ClassB(this);  
   myB.start();  
   synchronized(this) {
    wait();
   }
 
   // Some further processing of the MarketData goes here...
  }
  
  public void setData (String news){
    marketNews = news;
  }
}
 
class ClassB extends Thread{
 
   ClassA parent = null;
 
   ClassB(ClassA caller){
     parent = caller; // store the reference to the caller 
   }
 
   run(){
    // Get some data, and, when done, notify the parent
    parent.setData("Economy is recovering...");
    ...     
    synchronized (parent){
     parent.notify();  //notification of the caller
    }
   }
}

The method notifyAll()notifies and wakes up all threads waiting for the lock to be released on this object. If several threads are waiting, those that have the highest priority will run first. If several waiting threads have the same priority, the order of awakening is decided by the JVM. While notify() can work a tiny bit faster than notifyAll(), the latter is able to create objects that are loosely coupled — meaning that they don’t know about each other.

In Listing 20-14 simple replacement of parent.notify() with notifyAll() is not enough because ClassB also passes the data about the economy back to ClassA. But if ClassB just needs to notify you about some important application event, notifyAll() can be a better choice.

Try It

Implement and test the example from Listing 20-14 and then rewrite it to reduce the tight object coupling of the objects. ClassB shouldn’t know the exact type of the object to notify (ClassA) — this makes ClassB useful with other objects, too. You’ll need to introduce a new interface, say Updatable, that declares one method, setData(), to make ClassB work not only with ClassA, but with any class that implements Updatable.

Lesson Requirements

You should have Java installed.

note.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson20 folder in the download.

Step-by-Step

1. Create ClassA as in the code shown in Listing 20-14, and add the method main() to it.

2. Create ClassB and test the application to see that ClassA receives the notification about the economy.

3. Declare the new interface, Updatable.

4. Modify the constructor of ClassB to accept any Updatable object.

5. Run and test the new version of the application.

cd.ai

Please select Lesson 20 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

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

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