ThreadIntro - People.cs.uchicago.edu

Download Report

Transcript ThreadIntro - People.cs.uchicago.edu

Lesson 9
Intro to Java Threads
What are threads?
 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 – shared
memory model
Why use threads?
 Single-user programs/clients
– continuously responsive user interfaces
• e.g., accept input when event handler is busy
• e.g., make help menu accessible during timeconsuming database operation, etc.
• Speed up tasks when multiple processors available
 Servers
– Allows multiple client connections
simultaneously
General examples
 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 time-consuming
database lookup. Client can accept more input
while lookup is taking place.
Concrete example
 Consider two versions of a program which
animates a ball bouncing around in a window.
 In one version, the animation takes place in the
event handler thread. Thus, the gui is frozen for
the whole animation
 In a second version, the animation takes place in a
new thread spawned in the event handler. This
gives the event thread a chance to operate.
 Compare Bounce.java and BounceThread.java
Second concrete example
 Imagine a GUI program that performs a time-
consuming 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
Machinery – How to setup
threads in Java
How to create separate threads
in Java -- technique I
 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).
How to create threads
Technique 2
 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.
Simple Example Thread
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 …”);
}
}
Example using second technique
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 …”);
}
What happens to new threads?
 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.
Thread States
 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
Thread states, cont.
– Dead
• Dies a natural death because the run method exits
normally
• Dies abruptly after an uncaught exception terminates
the run method
– Use isAlive() method to determine if Thread is
currently alive (either runnable or blocked).
Thread Priority
 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 Priority
 Each Java thread is given a numeric priority
between MIN_PRIORITY and MAX_PRIORITY.
 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.
Thread Priority
 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.
Thread Priority
 When all the runnable threads in the system
have the same priority, the scheduler
chooses the next thread to run in a simple,
non-preemptive, round-robin scheduling
order.
A common scenario: polling vs
callbacks
 How do we implement the following?
– Thread1 spawns Thread2
– Thread1 does work
– Thread2 does work that results in new value of
variable (or new data in file, etc).
– Thread1 finishes work and needs updated value
(or file) from Thread2’s work.
– How can we synchronize activities?
What’s so difficult?
Atomic processes, sharing resources,
synchronization, deadlock
Bottom Line
 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 …
Managing Threads
 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 …
Producer/Consumer example
 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?
Producer Class
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) { }
}}}
CubbyHole
public class CubbyHole{
private int contents;
public synchronized int get() {
return contents;
}
public synchronized void put(int value) {
contents = value;
}
}
Consumer class
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);
}}}
Comments
 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.
How to synchronize code
 This topic confuses many beginning Java
programmers
 Two forms:
– synchronized(objReference){ …}//synchronize a block
– synchronized void methodName(){…}//synch a method
 Former is more general, but causes confusion.
Best to use simple form whenever possible.
 Second form is equivalent to:
– synchronized(this) for entire method body
Synchronizing a method
 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.
Synchronizing a block
 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.
Synchronizing a block, cont.
– 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.
wait() and notify()/notifyAll()
 A common scenario is as follows:
A thread enters a synchronized block of code
(and thus obtains the object lock).
The code cannot continue the sychronized block
until some other thread has done some work on
the same object and left it in a new state
Thus, the first thread wants to temporarily
relinquish the lock to another thread.
wait() and notify()/notifyAll()
 Called when a thread needs to wait for some
other thread(s) to complete a task before
continuing.
 The current thread simply calls wait(), and
the thread freezes until another object calls
notify() upon the waiting object (or
notifyAll()).
 notifyAll() is much safer since notify()
chooses randomly!
wait() and notify()/notifyAll()
 Beware, wait() can only be called from a
synchronized method or block of code.
 When wait is called on an object, its lock is
released until it is notified.
Moving out of blocked state
 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.
Synchronized Cubbyhole
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;
}
Cubbyhole, cont.
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}
Interrupting Threads
 Thread terminates when run method ends.
 So, run method should check once in a
while whether there is more work to do in
regular way.
 However, what if thread is sleeping or
otherwise blocked?
 This is where interrupt() method comes
into play.
Interrupting, cont.
 When interrupt method is called on thread
that is blocking, the blocking call is
terminated by an InterruptException.
public void run(){
try{
while (more work to do){ do work }
catch(InterrupetedException e){
//thread was interrupted during sleep or wait
//do whatever you wish here
}
Interrupting, cont.
 Problem with previous code is that
interrupts only succeed if thread is blocked
in call to sleep() or wait().
 Can get around this by calling interrupted()
periodically to see if thread has recently
been interrupted:
while (!interrupted() && more work to do){
// do work
}
Deadlock – Dining Philosophers
example
 http://java.sun.com/docs/books/tutorial/esse
ntial/threads/deadlock.html
Atomic operations -- Bank
Account example
 See BankTest.java and SynchBankTest.java
Tips on writing thread-safe code
 Obvious way is to use synchronization (see next
slide)
– Performance problems
– Can lead to deadlock
– General rule: don’t overuse if possible
 Local variables
– Each thread gets its own copy
 “Immutable sequences”
– Objects which can’t be changed (String, Integer, etc.)
Re-entrant threads
 Java’s model supports a concept called re-
entrant threads.
 If a thread t obtains a lock on object o by
calling a synchronized method in o (say
m1), and then from within m1 calls a
second sychronized method in o m2,
deadlock is guaranteed not to occur.
Other useful methods
 join() tells the calling thread to halt
execution until the second thread (whose
yield method is called), has completed.
Thread t = new Thread(runnableObj);
t.join();
Additional Topics
 Priorities
 Thread Groups
 Creating Thread pools
 Threads and Swing
 Higher-level methods – Timer class
Distributed Objects
 Higher level alternative to sockets
 RMI or CORBA