CS865 – Distributed Software Development

Threads

 

Based on:

See Also

http://www.particle.kth.se/~lindsey/JavaCourse/Book/courseMap.html

Concurrent Processing

Contemporary OS - multiple processes appear to execute concurrently on a machine via timesharing resources.

 

Concurrent processing within a process

It is often useful for a process to have parallel threads of execution, each of which timeshare the system resources in much the same way as concurrent processes.

 

Java Threads

What are threads?

 

The Java Virtual Machine allows an application to have multiple threads of execution running concurrently.

Java provides a Thread class:

     public class Thread

     extends Object

     implements Runnable

·        When a Java Virtual Machine starts up, there is usually a single thread (which typically calls the method named main of some designated class).

·        The Java Virtual Machine continues to execute threads until either of the following occurs:

o       The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.

o       All threads have terminated, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.

Two ways to create a new thread of execution

1.      Using a subclass of the Thread class

2.      Using a class that implements the Runnable interface

 

Create a class that is a subclass of the Thread class

Declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started:

 

 

Create a class that is a subclass of the Thread class

/******************************************************

 *   A program which makes use of the SomeThread class.

 *   M. Liu       1/8/02

 *****************************************************/

 // import SomeThread;

 

public class RunThreads

{

   public static void main (String[] args)

   {

     SomeThread p1 = new SomeThread(1);

     p1.start();

 

     SomeThread p2 = new SomeThread(2);

     p2.start();

 

     SomeThread p3 = new SomeThread(3);

     p3.start();

   }

}// end class RunThreads

/**************************************************

 *   A class which extends the Java Thread class.

 *   M. Liu       1/8/02

 *************************************************/

 

class SomeThread extends Thread {

   int myID;

   SomeThread(int id) {

      this.myID = id;

   }

 

   public void run() {

      int i;

      for (i = 1; i < 11; i++)

         System.out.println ("Thread"+myID + ": " + i);

   }

} //end class SomeThread

 

Sample Runs

Thread1: 1

Thread1: 2

Thread1: 3

Thread1: 4

Thread1: 5

Thread1: 6

Thread1: 7

Thread1: 8

Thread1: 9

Thread2: 1

Thread1: 10

Thread3: 1

Thread2: 2

Thread3: 2

Thread2: 3

Thread3: 3

Thread2: 4

Thread3: 4

Thread2: 5

Thread3: 5

Thread2: 6

Thread3: 6

Thread2: 7

Thread3: 7

Thread2: 8

Thread2: 9

Thread2: 10

Thread3: 8

Thread3: 9

Thread3: 10

Press any key to continue...

Thread1: 1

Thread3: 1

Thread3: 2

Thread3: 3

Thread3: 4

Thread3: 5

Thread3: 6

Thread3: 7

Thread3: 8

Thread3: 9

Thread2: 1

Thread3: 10

Thread1: 2

Thread2: 2

Thread1: 3

Thread2: 3

Thread1: 4

Thread2: 4

Thread1: 5

Thread2: 5

Thread1: 6

Thread2: 6

Thread1: 7

Thread2: 7

Thread1: 8

Thread1: 9

Thread1: 10

Thread2: 8

Thread2: 9

Thread2: 10

Press any key to continue...

 

Declare a class that implements the Runnable interface.

That class then implements the run method.

An instance of the class can then be allocated, passed as an argument when creating Thread, and started.

 

 

Create a class that implements the Runnable interface

 

public class RunThreads2

{

   public static void main (String[] args)

   {

     Thread p1 = new Thread(new SomeThread2(1));

     p1.start();

 

     Thread p2 = new Thread(new SomeThread2(2));

     p2.start();

 

     Thread p3 = new Thread(new SomeThread2(3));

     p3.start();

   }

} //end class RunThreads2

 

class SomeThread2 implements Runnable {

   int myID;

 

   SomeThread2(int id) {

      this.myID = id;

   }

 

   public void run() {

      int i;

      for (i = 1; i < 11; i++)

        System.out.println ("Thread"+myID + ": " + i);

   }

} //end class SomeThread2

 

Sample Runs

Thread1: 1

Thread1: 2

Thread1: 3

Thread1: 4

Thread1: 5

Thread3: 1

Thread1: 6

Thread2: 1

Thread1: 7

Thread3: 2

Thread1: 8

Thread2: 2

Thread1: 9

Thread3: 3

Thread1: 10

Thread2: 3

Thread3: 4

Thread2: 4

Thread3: 5

Thread2: 5

Thread3: 6

Thread2: 6

Thread3: 7

Thread2: 7

Thread3: 8

Thread3: 9

Thread3: 10

Thread2: 8

Thread2: 9

Thread2: 10

Press any key to continue...

Thread1: 1

Thread1: 2

Thread1: 3

Thread1: 4

Thread1: 5

Thread1: 6

Thread1: 7

Thread3: 1

Thread2: 1

Thread1: 8

Thread2: 2

Thread3: 2

Thread2: 3

Thread1: 9

Thread2: 4

Thread3: 3

Thread2: 5

Thread1: 10

Thread2: 6

Thread3: 4

Thread2: 7

Thread3: 5

Thread2: 8

Thread3: 6

Thread2: 9

Thread2: 10

Thread3: 7

Thread3: 8

Thread3: 9

Thread3: 10

Press any key to continue...

 

Thread-safe Programming

Ř     When two threads independently access and update the same data object, such as a counter, as part of their code, the updating needs to be synchronized.

Ř     Because the threads are executed concurrently, it is possible for one of the updates to be overwritten by the other due to the sequencing of  the two sets of machine instructions executed in behalf of the two threads. 

Ř     To protect against the possibility, a synchronized method can be used to provide mutual exclusion.

Race Condition

 

Synchronization - the act of serializing access to critical sections of code, at various moments during their executions

·        Sun's Java virtual machine specification states that synchronization is based on monitors

Ř     A monitor is a concurrency construct that encapsulates data and functionality for allocating and releasing shared resources (such as network connections, memory buffers, printers, and so on).

Ř     To accomplish resource allocation or release, a thread calls a monitor entry (a special function or procedure that serves as an entry point into a monitor).

§        If there is no other thread executing code within the monitor, the calling thread is allowed to enter the monitor and execute the monitor entry's code.

§        But if a thread is already inside of the monitor, the monitor makes the calling thread wait outside of the monitor until the other thread leaves the monitor.

·        The monitor then allows the waiting thread to enter.

§        Because synchronization is guaranteed, problems such as data being lost or scrambled are avoided.

·        To learn more about monitors, study Hoare's landmark paper, ""Monitors: An Operating System Structuring Concept," first published by the Communications of the Association for Computing Machinery Inc. in 1974.

 

The JVM specification goes on to state that monitor behavior can be explained in terms of locks.

·        A lock is a token that a thread must acquire before a monitor allows that thread to execute inside of a monitor entry.

o       That token is automatically released when the thread exits the monitor, to give another thread an opportunity to get the token and enter the monitor.

·        Java associates locks with objects: each object is assigned its own lock, and each lock is assigned to one object.

o       A thread acquires an object's lock prior to entering the lock-controlled monitor entry, which Java represents at the source code level as either a synchronized method or a synchronized statement.

 

·        The Java programming language provides two basic synchronization idioms: synchronized methods and synchronized statements.

o       The more complex of the two is synchronized statements

·        To make a method synchronized, simply add the synchronized keyword to its declaration:

public class SynchronizedCounter {
    private int c = 0;
    public synchronized void increment() {
        c++;
    }
    public synchronized void decrement() {
        c--;
    }
    public synchronized int value() {
        return c;
    }
}

·        Making these methods synchronized has two effects:

o       First, it is not possible for two invocations of synchronized methods on the same object to interleave.

§        When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

o       Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object.

§        This guarantees that changes to the state of the object are visible to all threads.

 

·        Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object's variables are done through synchronized methods.

Synchronized method in a thread

class SomeThread3 implements Runnable {

   static int count=0;

   SomeThread3() {

      super();

   }

 

   public void run() {

      update();

   }

   static public synchronized void update( ){

      int myCount = count;

      int second = (int)(Math.random( ) * 500);

      try {

        Thread.sleep(second);

      }

      catch (InterruptedException e) {

      }

      myCount++;

      count = myCount;

      System.out.println("count="+count+

        "; thread count=" + Thread.activeCount( ));

   }

} //end class SomeThread3

// import SomeThread3;

 

public class RunThreads3

{

   public static void main (String[] args)

   {

     int originalThreadCount = Thread.activeCount( );

     for (int i=0; i<10; i++) {

          Thread p = new Thread(new SomeThread3());

          p.start();

        System.out.println("thread count=" + Thread.activeCount( ));

     }

 

     while (Thread.activeCount() > originalThreadCount ){

         // loop until all child threads have exited.

     }

     System.out.println("finally, Count = " + SomeThread3.count);

   }

 }//end class RunThreads3

 

Sample Run

thread count=2

thread count=3

thread count=4

thread count=5

thread count=6

thread count=7

thread count=8

thread count=9

thread count=10

thread count=11

count=1; thread count=11

count=2; thread count=10

count=3; thread count=9

count=4; thread count=8

count=5; thread count=7

count=6; thread count=6

count=7; thread count=5

count=8; thread count=4

count=9; thread count=3

count=10; thread count=2

finally, Count = 10

Press any key to continue...

 

Issues 

Liveness

A concurrent application's ability to execute in a timely manner is known as its liveness.

 

Deadlock

v    Deadlock describes a situation where two or more threads are blocked forever, waiting for each other.

v    Example.

·        Alphonse and Gaston are friends, and great believers in courtesy.

·        A strict rule of courtesy is that when you bow to a friend, you must remain bowed until your friend has a chance to return the bow.

·        Unfortunately, this rule does not account for the possibility that two friends might bow to each other at the same time.

·        This example application, Deadlock, models this possibility:

 

public class Deadlock {

    static class Friend {

        private final String name;

        public Friend(String name) {

            this.name = name;

        }

        public String getName() {

            return this.name;

        }

        public synchronized void bow(Friend bower) {

            System.out.format("%s: %s has bowed to me!%n",

                    this.name, bower.getName());

            bower.bowBack(this);

        }

        public synchronized void bowBack(Friend bower) {

            System.out.format("%s: %s has bowed back to me!%n",

                    this.name, bower.getName());

        }

    }

    public static void main(String[] args) {

        final Friend alphonse = new Friend("Alphonse");

        final Friend gaston = new Friend("Gaston");

        new Thread(new Runnable() {

            public void run() { alphonse.bow(gaston); }

        }).start();

        new Thread(new Runnable() {

            public void run() { gaston.bow(alphonse); }

        }).start();

    }

}

·        When Deadlock runs, it's extremely likely that both threads will block when they attempt to invoke bowBack.

·        Neither block will ever end, because each thread is waiting for the other to exit bow.

Starvation

·        A thread is unable to gain regular access to shared resources and is unable to make progress.

·        Happens when shared resources are made unavailable for long periods by "greedy" threads.

Livelock

·        A thread often acts in response to the action of another thread.

·        If the other thread's action is also a response to the action of another thread, then livelock may result.

·        As with deadlock, livelocked threads are unable to make further progress.

·        However, the threads are not blocked — they are simply too busy responding to each other to resume work.

 

Waiting and Notification

·        When coupled with Java's waiting and notification mechanism, synchronized methods and/or synchronized statements allow threads to actively communicate, to cooperate on common goals.

 

·        Waiting and notification mechanism supports communication between threads, as follows:

o       A thread voluntarily waits until some condition (a prerequisite for continued execution) occurs.

o       At that time, another thread notifies the waiting thread, to continue its execution.

 

·        That communication is made possible by five methods implemented in the Object class:

 

 

Guarded Blocks

·        Threads often have to coordinate their actions.

·        Most common coordination idiom is the guarded block.

·        Such a block begins by polling a condition that must be true before the block can proceed.

 

Approach…

·        Invoke Object.wait() to suspend the current thread.

·        The invocation of wait does not return until another thread has issued a notification that some special event may have occurred — though not necessarily the event this thread is waiting for:

 

·        When wait() is invoked, the thread releases the lock and suspends execution.

·        At some future time, another thread will acquire the same lock and invoke Object.notifyAll(), informing all threads waiting on that lock that something important has happened.

·        Some time after the second thread has released the lock, the first thread reacquires the lock and resumes by returning from the invocation of wait.

 

Example: Producer-Consumer Application

·        This kind of application shares data between two threads: the producer, that creates the data, and the consumer, that does something with it.

·        The two threads communicate using a shared object.

·        Coordination is essential: the consumer thread must not attempt to retrieve the data before the producer thread has delivered it, and the producer thread must not attempt to deliver new data if the consumer hasn't retrieved the old data.

 

Data is a series of text messages, which are shared through an object of type Drop:

 

public class Drop {

    //Message sent from producer to consumer.

    private String message;

    //True if consumer should wait for producer to send message, false

    //if producer should wait for consumer to retrieve message.

    private boolean empty = true;

    public synchronized String take() {

        //Wait until message is available.

        while (empty) {

            try {

                wait();

            } catch (InterruptedException e) {}

        }

        //Toggle status.

        empty = true;

        //Notify producer that status has changed.

        notifyAll();

        return message;

    }

    public synchronized void put(String message) {

        //Wait until message has been retrieved.

        while (!empty) {

            try {

                wait();

            } catch (InterruptedException e) {}

        }

        //Toggle status.

        empty = false;

        //Store message.

        this.message = message;

        //Notify consumer that status has changed.

        notifyAll();

    }

}

 

·        The producer thread, defined in Producer, sends a series of familiar messages.

o       The string "DONE" indicates that all messages have been sent.

o       To simulate the unpredictable nature of real-world applications, the producer thread pauses for random intervals between messages.

 

import java.util.Random;

 

public class Producer implements Runnable {

    private Drop drop;

    public Producer(Drop drop) {

        this.drop = drop;

    }

    public void run() {

        String importantInfo[] = {

            "Mares eat oats",

            "Does eat oats",

            "Little lambs eat ivy",

            "A kid will eat ivy too"

        };

        Random random = new Random();

        for (int i = 0; i < importantInfo.length; i++) {

            drop.put(importantInfo[i]);

            System.out.format("MESSAGE SENT: %s%n", importantInfo[i]);

            try {

                Thread.sleep(random.nextInt(5000));

            } catch (InterruptedException e) {}

        }

        drop.put("DONE");

    }

}

 

The consumer thread, defined in Consumer, simply retrieves the messages and prints them out, until it retrieves the "DONE" string.

·        This thread also pauses for random intervals.

import java.util.Random;

 

public class Consumer implements Runnable {

    private Drop drop;

 

    public Consumer(Drop drop) {

        this.drop = drop;

    }

 

    public void run() {

        Random random = new Random();

        for (String message = drop.take(); ! message.equals("DONE");

                message = drop.take()) {

            System.out.format("MESSAGE RECEIVED: %s%n", message);

            try {

                Thread.sleep(random.nextInt(5000));

            } catch (InterruptedException e) {}

        }

    }

}

Finally, the main thread, defined in ProducerConsumerExample, that launches the producer and consumer threads

public class ProducerConsumerExample {

    public static void main(String[] args) {

        Drop drop = new Drop();

        (new Thread(new Producer(drop))).start();

        (new Thread(new Consumer(drop))).start();

    }

}

 

Threads in Applets

Example: Two Digital Clocks


 

//Sample Digital Clock Program showing two threads

 

import java.awt.*;

import java.util.Calendar;

import java.applet.*;

 

public class DigitalClock extends Applet implements Runnable{

 

 Thread clockThread1 = null, clockThread2 = null;

 Font font = new Font("Courier", Font.BOLD, 48);

 Color color = Color.green;

 

 public void start(){

  if (clockThread1 == null){

   clockThread1 = new Thread(this);

   clockThread1.start();

  }

  if (clockThread2 == null){

   clockThread2 = new Thread(this);

   clockThread2.start();

  }

 

 }

 

 public void stop(){

  clockThread1 = null;

  clockThread2 = null;

 }

 

 public void run(){

  while(Thread.currentThread() == clockThread1){

   color = Color.green;

   repaint();

   try{

    Thread.currentThread().sleep(1000);

   } catch (InterruptedException e){}

  }

 

  while(Thread.currentThread() == clockThread2){

   color = Color.blue;

   repaint();

   try{

    Thread.currentThread().sleep(1500);

   } catch (InterruptedException e){}

  }

 

 }

 

 public void paint(Graphics g){

  Calendar calendar = Calendar.getInstance();

  int hour = calendar.get(Calendar.HOUR_OF_DAY);

  int minute =calendar.get(Calendar.MINUTE);

  int second =calendar.get(Calendar.SECOND);

 

  g.setFont(font);

  g.setColor(color);

  g.drawString(hour + ":"+minute/10+minute%10+

            ":"+second/10+second%10, 10,60);

 

 }

}

 

 

High Level Concurrency Objects

High-level concurrency features introduced with version 5.0 of the Java platform

See http://java.sun.com/docs/books/tutorial/essential/concurrency/highlevel.html