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.
·
multiple
threads in a single program,
·
running
at the same time and
·
performing
different tasks.
By using threads a program can perform tasks
concurrently.
·
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.");
}
}
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)
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());
}
}
·
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;
}
}
public class
StartMultipleThreadsMain {
public static void main (String[] args) {
new SubClassOfThread("Tortoise").start();
new ImplementRunnable("Hare");
}
}
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: |
|
Specified
time has elapsed |
|
The
condition is changed and the object calls |
I/O is needed |
I/O is completed |
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);
}
}
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.
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);
}
}
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.
Critical sections are
objects that are updated from multiple and separate, concurrent threads.
Identify critical sections with the synchronized
keyword.
·
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;
}
}