Tuesday, August 14, 2007

Multithreading

Multithreading is one of the most important concepts of the Java language. You simply cannot do without multithreading in real-world programming. Multithreading basically enables your program to do more than one task at once and also to synchronize the various tasks. But before we launch into multithreading we will briefly summarize the points about threads.


Brief Recapitulation of threads

There are two ways to create threads.

  • Subclass Thread and override run()
  • Implement Runnable and override run()

Either of these two approaches may be used. Since multiple inheritance doesn't allow us to extend more than one class at a time, implementing the Runnable interface may help us in this situation.

You call a thread by the start() method. And start calls the run() method. You never call run() directly. The stop() method is now deprecated and should be avoided. Threads have priorities between 1-10, the default being 5 i.e normal priority.

A daemon thread is a thread that has no other role other than to serve other threads. When only daemon threads remain, the program exits. When a new thread object is created, the new thread has priority equal to the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

When the JVM starts, there is usually a single non-daemon thread which typically calls the main() method of the class.

Threads can be in one of four states.

  • New Threads
    When a thread is first created, the thread is not yet running.
  • Runnable Threads
    Once the start() method is invoked the thread is runnable and starts to run only when the code inside the run() method begins executing.
  • Blocked Threads
    Threads can enter the blocked state when any of these four conditions occur.
    When sleep() is called.
    When suspend() is called.
    When wait() is called.
    The thread calls an operation e.g. during input/output, which will not return until reading/writing is complete.
  • Dead Threads
    A thread dies because of two reasons.
    It dies a natural death when the run() method exits.
    It is killed because its stop() method was invoked.

Now it is time for some examples. Take a look at two examples below for creating more than one thread.

 
class NewThread extends Thread {
String name;
Thread t;
        
NewThread(String threadname) {
name=threadname;
t=new Thread(this, name);
System.out.println("New Thread: " + t );
t.start();
}
 
public void run() {
try {
for(int i=5; i>0;i--) {
System.out.println(name + ":" + i) ;
Thread.sleep(1000);
}
}
catch (InterruptedException e) {
System.out.println(name + " Interrupted. ");
}
System.out.println(name + " Exiting.");
}
}
 
class MultiThreadDemo {
        
public static void main (String args[]) {
new NewThread("One");
new NewThread("Two");
new NewThread("Three");
               
try {
Thread.sleep(10000);
}
catch (InterruptedException e) {
System.out.println("Main Thread Interrupted.");
}
System.out.println("main Thread Exiting.");
}
}

And the second one.

Note: Suspend and resume are deprecated methods.

 
class NewThread implements Runnable {
String name;
Thread t;
        
NewThread(String threadname) {
name=threadname;       
t=new Thread(this, name);
System.out.println("New Thread: " + t);
t.start();
}
 
public void run() {
try {
for(int i=5;i>0;i--) {
System.out.println(name + ":" + i);
Thread.sleep(200);
}
}
catch (InterruptedException e) {
System.out.println(name + "Interrupted. ");
}
 
System.out.println(name + " Exiting.");
}
}
 
class SuspendResume {  
 
public static void main(String args[]) {
NewThread ob1 = new NewThread("One");
NewThread ob2 = new NewThread("Two");
 
try {
Thread.sleep(1000);
System.out.println("Suspending thread One");
Thread.sleep(1000);
ob1.t.suspend();
System.out.println("Resuming thread One");    
ob1.t.resume();
 
System.out.println("Suspending thread Two");
Thread.sleep(1000);
ob2.t.suspend();
System.out.println("Resuming thread Two");
ob2.t.resume();
}
catch (InterruptedException e) {
System.out.println("main thread interrupted." );
}
 
try  {
ob1.t.join();
ob2.t.join();
}
catch (InterruptedException e) {
System.out.println("main thread interrupted.");
}
 
System.out.println("Main thread Exiting.");
}
}       

Back to TOP


Synchronization

When two or more threads need access to a shared resource, they need some way to ensure that the resource will be used by only one thread at a time. The process by which this is achieved is synchronization.

Key to synchronization is the concept of the monitor. A monitor is an object that is used as a mutually exclusive lock. Only one thread can own the monitor at a given time. When a thread acquires a lock, it is said to have entered the monitor. The other threads attempting to enter the locked monitor will be suspended until the first exits the monitor.

There are two ways you can synchronize your code.

  • synchronized methods.
  • synchronized statement

Both involve the use of the synchronized keyword. See below for an example.

 
import java.io.*;
 
class Deposit {
static int balance = 1000;
 
public static void main(String args[]) {
PrintWriter out = new PrintWriter(System.out, true);
Account account = new Account(out);
DepositThread first, second;
 
first = new DepositThread(account, 1000, "#1");
second=new DepositThread(account, 1000, "\t\t\t\t#2");
 
first.start();
second.start();
 
try {
first.join();
second.join();
}
catch (InterruptedException e) {  }
 
out.println("*** Final balance is  "  + balance);
}
}
 
class Account {
PrintWriter out;
        
Account(PrintWriter out) {
this.out=out;
}
 
synchronized  void deposit(int amount, String name ) {
int balance;
               
out.println(name + "  trying to deposit " + amount);
out.println(name + "  getting balance... " );
balance=getBalance();
out.println(name + "  balance got is  " + balance);
 
balance += amount;
 
out.println(name + "  setting balance...");
setBalance(balance);
out.println(name + "  balance set to  "  + Deposit.balance);
}
        
int getBalance()  {
 
try  {
Thread.sleep(1000);
}
catch (InterruptedException e) {  }
                       
return Deposit.balance;
}
 
void setBalance(int balance)  {
try {
Thread.sleep(1000);
}
catch(InterruptedException e) {  }
               
Deposit.balance = balance;
}
}
 
class DepositThread extends Thread {
Account account;
int deposit_amount;
String message;
 
DepositThread(Account account, int amount, String message) {
this.message=message;
this.account=account;
this.deposit_amount=amount;
}
public void run() {
account.deposit(deposit_amount, message);
}
}       
 

Note: What will happen if the synchronized keyword is removed in the preceding example ?

Back to TOP


Inter-thread Communication

Java's inter-thread communication process involves the use of wait(), notify() and notifyall() methods. These methods are implemented as final methods in Object, so all classes have them. These methods can only be called from within synchronized code.

Rules for using these methods:

  • wait() tells the calling thread to give up the monitor and go to sleep until some other thread enters the same monitor and calls notify().
  • notify() wakes up the first thread that called wait() on the object.
  • notifyall() wakes up all the threads waiting on the object. The highest priority thread will run first.

See below for an incorrect implementation of a producer/consumer example.

 
//An  incorrect implementation of a producer and consumer.
 
class Q {
 
int n;
               
synchronized int get() {
System.out.println("Got: " + n);
return n;
}
synchronized void put(int n) {
this.n=n;
System.out.println("Put: " + n);
               
}
}
 
class Producer implements Runnable {
        Q q;
        
        Producer(Q q) {
               this.q=q;
               new Thread(this, "Producer").start();
        }
        public void run() {
               int i=0;
               
               while(true) {
                       q.put(i++);
               }
        }
}
 
class Consumer implements Runnable {
        Q q;
        
        Consumer(Q q) {
               this.q=q;
               new Thread(this, "Consumer").start();
        }
 
        public void run() {
               while(true) {
                       q.get();
               }
        }
}
 
class PC  {
        public static void main(String args[]) {
               Q q = new Q();
 
               new Producer(q);
               new Consumer(q);
               System.out.println("Press Control-C to stop");
        }
}
        

The correct way would be using wait() and notify() as shown here.

 
//A  correct implementation of a producer and consumer.
 
class Q {
 
        int n;
        boolean valueset = false;
        
        synchronized int get() {
   if (!valueset) 
        try {
               wait();
               }
        catch (InterruptedException e) {
        System.out.println("InterruptedException caught");
               }
        
               System.out.println("Got: " + n);
               valueset=false;
               notify();
 
               return n;
        }
        synchronized void put(int n) {
        if (valueset)
        try {
               wait();
               }
        catch(InterruptedException e) {
        System.out.println("InterruptedException caught");
               }              
               this.n=n;
               valueset=true;
               System.out.println("Put: " + n);
               notify();
        }
}
 
class Producer implements Runnable {
        Q q;
        
        Producer(Q q) {
               this.q=q;
               new Thread(this, "Producer").start();
        }
        public void run() {
               int i=0;
               
               while(true) {
                       q.put(i++);
               }
        }
}
 
class Consumer implements Runnable {
        Q q;
        
        Consumer(Q q) {
               this.q=q;
               new Thread(this, "Consumer").start();
        }
 
        public void run() {
               while(true) {
                       q.get();
               }
        }
}
 
class PCFixed  {
        public static void main(String args[]) {
               Q q = new Q();
 
               new Producer(q);
               new Consumer(q);
               System.out.println("Press Control-C to stop");
        }
}
        

Now to summarize the points about multithreading: thread synchronization, inter-thread communication, thread priorities, thread scheduling, and daemon threads.

No comments:

Topics