Programs start, execute a series of instructions, and end.

 

·        During the runtime of the program, there is a single point of execution.

 

·        The execution sequence is a thread.

 

·        Definition:  A thread is a single sequential flow of control within a program.

 

 

 

 

Java provides for

 

·        multiple threads in a single program,

·        running at the same time and

·        performing different tasks.

 

By using threads a program can perform tasks concurrently. 

 

 

 

 

 

 

Each program thread

 

·        operates within the context of the  program and

·        takes advantages of the resources allocated for that program..

 

 

 

 

Timers are used to start a task at some later time or after some delay.

 

·         Subclass TimerTask and implement the run method that contains the code that performs the task.

·         Create a thread by instantiating the Timer class.

·         Instantiate the timer task object new RemindTask().

·         Schedule the timer task for execution. This example uses the schedule method, timer task is the first argument and delay in milliseconds (5000) the second argument.

 

 

import java.util.Timer;
import java.util.TimerTask;
 
/**
 * Use java.util.Timer to schedule a task to execute
 * once 5 seconds have passed.
 */
 
public class Reminder {
    Timer timer;
 
    public Reminder(int seconds) {
        timer = new Timer();       // Creates a thread
        timer.schedule(new RemindTask(), seconds*1000);  
    }
 
    class RemindTask extends TimerTask {
        public void run() {
            System.out.println("Time's up!");
            timer.cancel(); //Terminate the timer thread
        }
    }
 
    public static void main(String args[]) {
        System.out.println("About to schedule task.");
        new Reminder(5);
        System.out.println("Task scheduled.");
    }
}

 

Stopping Timer Threads

By default, a program keeps running as long as its timer threads are running. Terminate a timer thread by:

·         Invoking cancel on the timer from anywhere in the program, such as from a timer task’s run method.

·         Make the timer’s thread a “daemon” by creating the timer like this: new Timer(true). If the only threads left in the program are daemon threads, the program exits.

·         Remove all references to the Timer object. Eventually, the timer’s thread will terminate.

·         Invoke the System.exit method -- the entire program (and all its threads) exit.

 

 

 

Starting Multiple Threads

public class StartMultipleThreadsMain {

    public static void main (String[] args) {

        new SubClassOfThread("Tortoise").start();

        new SubClassOfThread("Hare").start();    }

}

 

·  Although the threads are started sequentially, they will be executed concurrently.

 

·  This program now has multiple execution points.  (The Tortoise may or may not finish before the Hare)

 

 

 

 

 

Subclassing Thread

 

·        is one method of creating Thread code

·        override its empty run method so that it does something.

 

 

public class SubClassOfThread extends Thread {

    public SubClassOfThread(String str) {

        super(str);  // set the name of the thread

    }

    public void run() {

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

            System.out.println(i + " " + super.getName());

            try {

                sleep((long)(Math.random() * 1000));

            } catch (InterruptedException e) {}

        }

        System.out.println("DONE! " + super.getName());

    }

}

 

 

 

Implement the Runnable interface
 
·        is another method of creating thread code

·        is by using the Runnable interface. 

 

 

The program calling the runnable class has the following calls.

 

public class StartMultipleThreadsMain {

    public static void main (String[] args) {

        new ImplementRunnable("Tortoise");

        new ImplementRunnable("Hare");

    }

}

 

 

To implement the Runnable interface

 

o       provide itself as an argument to the Thread's constructor.

 

When created in this way, the Thread gets its run method from the object passed into the constructor.

 

public class ImplementRunnable implements Runnable {

    private Thread runMe = null;

    private String name = null;

    public ImplementRunnable (String str) {

        if (runMe == null) {

            name = str;

            runMe = new Thread(this, str);

            runMe.start();

        }

    }

 

    public void run() {

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

            System.out.println(i + " " + getName());

            try {

                Thread.sleep((long)(Math.random() * 1000));

            } catch (InterruptedException e) {}

        }

        System.out.println("DONE! " + getName());

    }

 

    public String getName() {

        return name;

    }

}

 

 

Both types of threads can be called from within a single program

 

 

public class StartMultipleThreadsMain {

    public static void main (String[] args) {

        new SubClassOfThread("Tortoise").start();

        new ImplementRunnable("Hare");

    }

}


 

Life cycle of a Thread

 

 

The start() creates the system resources necessary to run the thread, schedules the thread to run, and calls the thread's run method.

 

When the start method completes, the thread is in the Runnable state.

 

Threads become “not Runnable” when:

The Thread becomes “Runnable” when:

sleep()  is invoked

 

Specified time has elapsed

wait() is called for a certain condition

The condition is changed and the object calls notify () or notifyAll()

 

I/O is needed

I/O is completed

 

 


Stopping a Thread

 

A thread arranges for its own death by having a run method that terminates naturally. For example, the while loop in this run method is a finite loop-- it will iterate 1000 times and then exit:

public void run() {
    int i = 0;
    while (i < 1000) {
        i++;
        System.out.println("i = " + i);
    }
}

isAlive( ) Method

returns true if the thread has been started and not stopped.

§        false, means that the thread either is a New Thread or is Dead.

§        true, means either Runnable or Not Runnable.


Scheduling and Priority

A single processor, cannot run all "running" threads at the same time. The processor has to schedule all "running" threads. So at any given time, a "running" thread actually may be waiting for its turn in the CPU.

Java runtime supports fixed preemptive priority scheduling.

Threads are scheduled according based on their priority relative to other runnable threads.

When multiple threads are read for execution, the Thread with the highest priority will be chosen by the runtime system.

·        Java threads inherit the priority of their creator.

·        The priority can be modified after creation by the setPriority (int priorityLevel) method.

·        The priorityLevel depends on the value of the integer -- the higher the integer, the higher the priority.

·        The priorityLevel must between within MIN_PRIORITY and MAX_PRIORITY (1 to 10). 

 

Scheduler rules of thumb:

The thread must be runnable (not waiting, sleeping, I/O bound).

If there is more than 1 thread available, the one with the highest priority is chosen.

If two threads have the same priority, they are selected in a round-robin fashion. First one is chosen, then the other and so on.

The chosen thread will run until one of the following conditions is true:

·         A higher priority thread becomes runnable.

·         It yields, or its run method exits.

·         On systems that support time-slicing, its time allotment has expired.

Note: It is not guaranteed that at any given time, the highest priority thread is running.  A lower priority thread may be scheduled so it avoids starvation.

If a running thread does not voluntarily relinquish control of the CPU (a selfish thread ) – it runs until it terminates naturally or until the thread is preempted by a higher priority thread or needs I/O.

 

public void run() {
    while (tick < 400000) {
        tick++;
        if ((tick % 50000) == 0)
            System.out.println("Thread #" + num
                               + ", tick = " + tick);
    }
}    

 

 

Time-Slicing

Some systems control selfish thread behavior with a strategy known as time-slicing.

The CPU is divided into time slots

·        each of the equal-and-highest priority threads gets a time slot in which to run.

·        the system allows each thread a bit of time to run,

o       until one or more of them finishes or

o       until a higher priority thread preempts them.

There is no guarantee as to how often or in what order threads are scheduled to run.

Note:  The Java runtime does not implement time-slicing. However, Java runs on some systems which do support time-slicing.

 

Two concurrent threads updating the same object

A Counter

 

class Counter {

  Counter(int v, int mod) {

    value = v;

    modulus = mod;

  }

  int modulus; // max value is modulus - 1

  int value;                      //0 to modulus-1

  void reset() { value = 0; }

  int get()    { return value;}   //current value

  void click() { value = (value + 1) % modulus;}

}

 

 

A Racer

 

//Racer.java - click the counter 100000 times

class Racer extends Thread {

  Racer(int id, Counter counter) {

    this.id = id;

    this.counter = counter;

  }

  public void run() {  // this run has no sleep() call

    System.out.println("Thread" + id + " started.");

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

      counter.click();   

    }

    System.out.println("Thread" + id +

            " finished counter is " + counter.get());

  }   

  private int id;

  private Counter counter;

}

Driving One Racer

class OneRacerUpdatingCounter {

  public static void main(String[] args) {

    Counter counter = new Counter(0, 1000000);

    Racer t = new Racer(1, counter);

    t.start();

  }

}

Driving Two Racers

class TwoRacersUpdatingCounter {

  public static void main(String[] args) {

    Counter counter = new Counter(0, 1000000);

    Racer t1 = new Racer(1, counter);

    Racer t2 = new Racer(2, counter);

    t1.start();

    t2.start();

  }

}

 

Locking an Object

Critical sections are objects that are updated from multiple and separate, concurrent threads. Identify critical sections with the synchronized keyword.  

 

Avoid Racing – Synchronize the Racer when it is critical

//Racer2.java - assume click() is not synchronized

class Racer2 extends Thread {

  Racer2(int id, Counter counter) {

    this.id = id;

    this.counter = counter;

  }

  public void run() {

    System.out.println("Thread" + id + " started.");

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

      synchronized(counter) {

        counter.click();

      }

    }

    System.out.println("Thread" + id +

            " finished counter is " + counter.get());

  }

  private int id;

  private Counter counter;

}

 

 

Compare synchronization to non-synchronized for effects on speed, and accuracy)

 

 

class FourRacersUpdating2Counters {

  public static void main(String[] args) {

    Counter counter = new Counter(0, 1000000);

    Racer t1 = new Racer(1, counter);

    Racer t2 = new Racer(2, counter);

    t1.start();

    t2.start();

 

    Counter counter2 = new Counter (0, 1000000);

    Racer2 t3 = new Racer2(3, counter2);

    Racer2 t4 = new Racer2(4, counter2);

    t3.start();

    t4.start();

  }

}

 

Producer – Consumer (Coordinating the production and consumption of information)   

public class CubbyHole   {

    private int x;

    public CubbyHole() {

        x = -1;

    }

   

    public void put(int y) {

        x = y;

    }

 

    public int get() {

        return x;

    }

}

 

Consumer

 

public class Consumer extends Thread {

    private CubbyHole cubbyhole;

    private int number;

 

    public Consumer(CubbyHole c, int number) {

        cubbyhole = c;

        this.number = number;

    }

 

    public void run() {

        int value = 0;

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

            value = cubbyhole.get();

            System.out.println("Consumer #" + this.number

                               + " got: " + value);

        }

    }

}


 

Producer

 

public class Producer extends Thread {

    private CubbyHole cubbyhole;

    private int number;

 

    public Producer(CubbyHole c, int number) {

        cubbyhole = c;

        this.number = number;

    }

 

    public void run() {

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

            cubbyhole.put(i);

            System.out.println("Producer #" + this.number

                               + " put: " + i);

            try {

                sleep((int)(Math.random() * 10));

            } catch (InterruptedException e) { }

        }

    }

}

 

Running the Producer and Consumer

 

public class ProducerConsumerTest {

    public static void main(String[] args) {

        CubbyHole c = new CubbyHole();

        Producer p1 = new Producer(c, 1);

        Consumer c1 = new Consumer(c, 1);

        p1.start();

        c1.start();

    }

}

 

·        The Consumer should not access the CubbyHole when the Producer is changing it. 

·        The Producer should not modify it when the Consumer is getting the value. 

·        The put and get in the CubbyHole class can be marked with the synchronized keyword.

·        Whenever control enters a synchronized method, the thread that called the method locks the object.

·        Other threads cannot call a synchronized method on the same object until the object is unlocked.

·        When the Producer calls CubbyHole's put method, it locks the CubbyHole, thereby preventing the Consumer from calling the CubbyHole's get method:

·        boolean. available is true when the value has just been put( ) but not yet gotten and is false when the value has been gotten but not yet put( ).

·        Consumer should wait until the Producer puts something in the CubbyHole and the Producer must notify the Consumer when it's done so. Similarly, the Producer must wait until the Consumer takes a value (and notifies the Producer of its activities) before replacing it with a new value

public class CubbyHole2   {

    private int x;

    private boolean available = false;

 

    public CubbyHole2() {

        x = -1;

    }

   

    public synchronized void put(int y) {

        while (available == true) {

            try { wait(); // wait for Consumer to get value

            } catch (InterruptedException e) { }

        }

        x  = y;

        available = true;

        notifyAll();// notify Consumer that value has been set

}

    public synchronized int get() {

        while (available == false) {

            try { wait();// wait for Producer to put value

            } catch (InterruptedException e) { }

        }

        available = false;

        notifyAll(); // notify others value was retrieved

        return x;

        }

    }