notes ()

advertisement
Java Threads and higher level
constructs for doing concurrent
programming
Background
How to create/run threads
interrupt(), join()
Thread priorities/states
Synchronized blocks of code
wait(),notify()/notifyAll




Like several concurrent subprograms running
within the same address space.
Within a program, individual threads are
explicitly “spawned” and give the appearance
of simultaneously running sequences of
commands.
On a single processor machine, the
simultaneous running is an illusion – cpu is
time splicing.
Differ from separate processes in that each
process runs in its own address space – threads
are a lightweight shared memory model

Single-user programs/clients

continuously responsive user interfaces
 e.g., accept input when event handler is busy
 Do not block on time-consuming i/o or socket
operations
 e.g., make help menu accessible during timeconsuming database operation, etc.
 Speed up tasks when multiple processors available
 Model multiple clients on single platform

Servers

Allows multiple clients to connect without “busy
signal”


User clicks GUI button to download web page
(occurs in separate thread so GUI isn’t
“frozen”)
Massive numerical problems split among
processors



assumes each thread runs on separate processor; not
necessarily the case
Server spawns thread for each client connection
and main thread goes back to accept()
User clicks button which begins timeconsuming database lookup. Client can accept
more input while lookup is taking place.



Imagine a GUI program that performs a timeconsuming task in the event handler How can
the GUI remain responsive?
If we do task in a separate thread and sleep it
periodically, user interface thread will appear
“live”.
See FrameThread.java and
FrameNoThread.java

Extend Thread. Specifically ...




Create a class that extends Thread and place the
work that the Thread will carry out in the run()
method (ie override the run method).
Create an object from your Thread class.
Call the start() method on the Thread object.
The new Thread then enters the runnable state (it
may or may not run immediately depending on
resources/priority).

Implement Runnable. Specifically ...





Create a class that implements the Runnable
interface. Place all of the work that the Thread will
perform in the run() method.
Create an object from your Runnable class.
Create a Thread object and pass the Runnable object
to the constructor.
Call the start() method on Thread object.
See simple examples under basic directory.
Class ThreadExample{
public static void main(String[] args){
System.out.println(“Main thread started”);
MyFirstThread t = new MyFirstThread();
t.start();
System.out.println(“main thread continuing”);
}
}
Class MyFirstThread extends Thread{
void run(){
System.out.println(“in new thread …”);
}
}
class ThreadTest{
public static void main(String[] args){
System.out.println(“main thread started …”);
MyRunnableObj r = new MyRunnableObj();
Thread t = new Thread(r);
t.start();
System.out.println(“Main thread continuing”);
}
}
Class MyRunnableObj implements Runnable{
public void run(){
System.out.println(“new thread started …”);
}
Common these days, like with other libraries, to use
anonymous inner classes for simple threads
Thread t = new Thread(new Runnable(){
public void run(){
//do work here
}
});
This provides a few advantages:
-Reduces proliferation of small, simple classes
-Allows t access to instance variables of outer class
-Puts code definition close to where object is created.





Main thread continues
New threads execute the run method and die
when they are finished
If any thread calls System.exit(0), it will kill all
threads.
Think of each run() as its own main
Program does not exit until all non-daemon
threads die.

Four states a Thread can be in:

New
 When you create with new operator but haven’t run yet.

Runnable
 When you invoke start() method. Note that Thread is not
necessarily running, could be waiting.

Blocked
 When sleep() is called
 Blocking operation such as input/output
 wait() is called by the Thread object
 Thread tries to obtain a lock on a locked object

Dead
 Dies a natural death because the run method exits
normally
 Dies abruptly after an uncaught exception terminates
the run method

As of 1.5 Java supports a getState() method in the
Thread class that reports one of these states.



The execution of multiple threads on a single CPU
is called scheduling.
The Java runtime supports a very simple,
deterministic scheduling algorithm known as fixed
priority scheduling.
Thread.setPriority(int newPriority)
Value must be between MIN_PRIORITY and
MAX_PRIORITY
 NORM_PRIORITY is normal value




When multiple threads are ready to be executed,
the thread with the highest priority is chosen for
execution.
Only when that thread stops, or is suspended for
some reason, will a lower priority thread start
executing.
Scheduling of the CPU is fully preemptive. If a
thread with a higher priority than the currently
executing thread needs to execute, the higher
priority thread is immediately scheduled.


The Java runtime will not preempt the currently
running thread for another thread of the same
priority. In other words, the Java runtime does not
time-slice. However, the system implementation of
threads underlying the Java Thread class may
support time-slicing. Do not write code that relies
on time-slicing.
In addition, a given thread may, at any time, give
up its right to execute by calling the yield method.
Threads can only yield the CPU to other threads of
the same priority--attempts to yield to a lower
priority thread are ignored.

static void Thread.yield()

When all the runnable threads in the system
have the same priority, the scheduler chooses
the next thread to run in a simple, nonpreemptive, round-robin scheduling order.
Atomic processes, sharing resources,
synchronization/coordination,
avoiding deadlock


Any time a writeable variable is visible to more
than one thread, potential problems exist.
Simple example: two clients try to purchase
item at same time.




Order or execution unpredictable
If (itemsLeft > itemsRequested) not reliable!
Must create “thread-safe” programs
More on this later …


Everything in either Object or Thread class
Two classes of methods:

Those defined in Object
 wait(), notify(), notifyAll()

Those defined in Thread class
 join(), sleep(), interrupt(), isAlive(), yield(), etc.


All involve situations where threads
communicate with each other in some way.
Will discuss later …


It is best to learn how these methods work with a few
canonical examples
First is the following scenario





A client issues a command that requires a time consuming
operation – in this case we can imagine it to be reading a lot of
data from an input stream
The UI should not block during this operation. Other things can
be done that are useful. Thus, we place the operation in a
thread.
However, certain commands require this read operation to be
complete before they can sensibly be executed.
How do we say “block this command until the other thread is
finished”?
Study ExJoin.java in class notes … it uses Thread.join() to
handle this situation.


In the next example we imagine a thread that is launched
into a time consuming operation – in this case an expensive
calculation in a large loop.
The main thread remains responsive to the GUI and reports
on progress of the worker thread. As this progress is
monitored, the client may wish to terminate the worker
thread.

How can this be done?

We use the following Thread methods to carry this out:



void interrupt(): sends an interrupt request to thread
boolean isInterrupted(): tests weather the thread is interrupted
static Thread currentThread(): returns object represent this Thread.



Thread.interrupt() is an important method. It
supersedes stop() and suspend(), which are
now deprecated.
The idea with interrupt() is that one thread
notifies another thread that it might be time to
stop, but leaves it up to the thread itself to
decide.
How does a thread know that it has been
interrupted?


By calling isInterruped() or, if the thread is blocking
An InterruptedException is thrown




The next and probably most important topic is
atomicity – how to ensure that one thread completes an
entire sequence of instructions without being
preempted by another thread.
Note that instructions are not lines of Java code – one
line can be made up of several instructions, possibly
many more
The motivation is simple – if one thread takes on action
best on a test condition, but another thread begins to
execute and halts the original thread, the condition
may no longer be true by the time the initial thread
recommences execution.
How do we “lock” threads out of a piece of code until
one thread is finished executing?





One thread is called the Producer. Producer shoves
consecutive integers into a Cubbyhole object as fast
as it can.
Other thread is called Consumer. Consumer grabs
from the Cubbyhole object as fast as it can.
Consumer would like to grab once for each put by
the Producer, but what if one goes faster than the
other?
We want to block Consumer from consuming until
Producer has finished producing.
Likewise, we want to block Producer from
producing again until Consumer has conumed.
public class CubbyHole{
private int contents;
public synchronized int get() {
return contents;
}
public synchronized void put(int value) {
contents = value;
}
}
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() * 100));
} catch (InterruptedException e) { }
}}}
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);
}}}




Note that these classes of themselves do not
preclude a race condition.
This is done by shychronizing access to the
Cubbyhole object.
We want to guarantee that the Consumer
thread can’t get until the Producer has
produced.
Need to study wait() and notify() methods.


void wait(): relinquishes the object lock without exiting the
object method – ie if the thread can only proceed within
object method once another thread has changed the object’s
state.
Can only be called if the calling thread already possesses the
lock – otherwise, an IllegalMonitorStateException is thrown.
The lock is given to the next waiting thread, or chosen
randomly if more than one thread is waiting on the same
object’s lock.
void notify()/notifyAll(): a thread cannot pull itself out of
the wait() state, but requires another thread to call
notify/notifyAll to allow it to reclaim the lock and continue.


This topic confuses many beginning Java programmers
Two forms:




Former is more general, but causes confusion. Best to
use simple form whenever possible.
Second form is equivalent to:


synchronized(objReference){ …}//synchronize a block
synchronized void methodName(){…}//synch a method
synchronized(this) for entire method body
Recently java has added a java.util.concurrent.locks
package, which contains much of the same
functionality but perhaps packaged in a more friendly
way. We will go over these briefly as the last step in
this lecture.

Fairly straightforward rules:



When a given thread is executing a synchronized
method in an object, no other thread can execute an
other synchronized method on the SAME OBJECT!
We say that the first thread obtains a lock on the
object and doesn’t release it until it finishes the
synched method
Beware that only code which tries to obtain a lock on
the object will have to wait. For example, if a thread
wishes to call a non-synched method in the object, it
will not be blocked.

Remember the following rules:



When a thread encounters a synchronized block of
code, it attempts to obtain a lock on the object that is
being synchronized upon.
Consider the first thread in a program that
encounters the lock. Since it is the first thread, it will
successfully obtain the lock.
Now consider a second thread that encounters any
synchronized block of code that synchronzies on the
same object.

This second thread will attempt to obtain the lock on
objReference.

It will fail to obtain the lock unless the first thread
has released it. This means that the first thread must
have finished its synchronized block of code.

If the second thread cannot obtain the lock, it must
wait until the first thread releases it.

Must use the opposite route that put the
Thread into the blocked state




If put to sleep, specified time interval must elapse.
If waiting for i/o operation, operation must have
finished.
If the thread called wait(), then another thread must
call notify/notifyAll.
If waiting for a lock, then owning thread must have
relinquished the lock.
public class CubbyHole {
private int contents;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) { }
}
available = false;
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}





This time we have a queue of specified length containing String
messages.
Two Consumer and one Producer thread run simultaneously.
The Producer adds messages to the queue up to the maximum
queue size, then waits for the Consumer to remove one before
proceeding.
The Consumer removes messages from the queue unless none are
present, in which case it waits for the Producer to add one.
Note that this concept of a blocking queue mimics a very common
model where a resource may not be available and polling is
inelegant and inefficient. notify() replaces polling with a pushback
model.



The term “race condition” is used to describe a
situation where the correctness of multithreaded code depends on an ordering of
operations that is not guaranteed in any
execution.
If one thread “races” ahead of another, the code
could fail to give correct results.
A very nice example is the concept of a Bank
with clients simultaneously moving money
within and between accounts.

An Account object allows clients to withdraw,
deposit, and see the balance of a single account.

One thread, the Saver, deposits money.

Another thread, the Spender, withdraws money.


In this example, if there are insufficient funds to
allow a specified withdrawal, then, rather than
block, an Exception is thrown.
Study the example and see what can go wrong..



In this more realistic example many Account
objects are created and threads are create to
transfer money between them in different random
ways.
If the code is correct, the total amount of money
should not change – it is just moved around but
never withdrawn.
However, this requires proper atomicity of the
transfer method. This example shows what can go
wrong and how to fix it.
Download