540 likes | 635 Views
OOP Spring 2004 lecture 9 Threads. Kasper B. Graversen IT-University of Copenhagen. Today’s program. What is thread programming all about Terminology How to construct a thread States of a thread Operations on a thread block, sleep, die, join Typical usages
E N D
OOP Spring 2004lecture 9Threads Kasper B. Graversen IT-University of Copenhagen
Today’s program • What is thread programming all about • Terminology • How to construct a thread • States of a thread • Operations on a thread • block, sleep, die, join • Typical usages • Synchronize / Racing conditions • Priorities and how they won’t help you.. • Dead locks • queue-workers • Timer threads • Problems with separating threads from the OS..
Background • Secretary.java • The program halts • The program busy waits • Can be remedied, but then the program becomes inflexible
What are Threads Good For? • Responsive applications • A graph is drawn while at the same times menus can be selected • Tasks can be cancelled • Servers (web, ftp,...) • Some problems are intrinsically parallel, and are thus easier to express • simulations • To monitor status of some resource. • Database, servers uptime etc • The notion of multiple processes was invented many years before Java. It was a necessity in the old days when computers where expensive… sharing the CPU cycles among several users. In UNIX system you have the fork() and join() methods for that.
Concurrency vs. Parallelism time CPU CPU1 CPU2
Multitasking and Multithreading • Multitasking A computer's ability to do multiple executions concurrently • Thread A sequence of execution within a program • Multithreading Multiple threads in a program • Each thread has a stack • All threads share the same dynamic memory (the heap) • When threads execute the same method on the same object, each will have its own copy of the local variables the methods uses (its registers) similar to that they do not share the argument values. Classes (code) Heap Classes (code) Heap Registers Stack Registers Stack Registers Stack
Scheduling • Thread schedulingdetermines how threads are allocated CPU time • preemptivethe thread scheduler preempts (pauses) a running thread to allow different threads to execute • non-preemptive / quasi parallelismthe scheduler never interrupts a running thread. The running thread must yield control to the CPU (as our secretary modification)May cause starvation (threads wait to be executed in the CPU a lot of time, maybe even forever). • Both types may cause livelock (threads are given CPU time, but they are not able to make any progress) • Java’s scheduler is preemptive and based on priority of threads • Threads with higher priority are executed in preference to threads with lower priority.
Creating threads • Two ways • Let the class extend Thread and instantiate an object of that class • Let the class implement Runnable and create objects and start it using a special method • In both cases implement the run() method class Print implements Runnable { public void run() { for(int i = 0; i < 23000; i++) System.out.print("-"); } } class Printer extends Thread { char character; Printer(char character) { this.character = character; } public void run () { for(int i = 0; i < 23000; i++) System.out.print(character); } }
Starting the threads • Depending on the how the class looks, we start threads differently class ThreadPrinterExample { public static void main(String[] args) { Print p0 = new Print(); Printer p1 = new Printer('*'); Printer p2 = new Printer('|'); p1.start(); p2.start(); new Thread(p0).start(); } }
Result of running the example |-|-|-|-|-|-|*-*-*-*-*-*-*-*-*-|*|*|*|*|*|*|*|*|*-|-|- -*-*-|*|*|*|*|*|*|*|*|*-|-|-|-|-|-|-|-|-|*-*-*-*-*-*-* |-|-|-|-|-|-|-|*-*-*-*-*-*-*-*-*|-|-|-|-|-|-|-|-|-*|*| -*-*-*|-|-|-|-|-|-|-|-|-*|*|*|*|*|*|*|*|*|-*-*-*-*-*-* *|*|*|*|*|*|*|*|-*-*-*-*-*-*-*-*-*|-|-|-|-|-|-|-|-|-*| -*-*-*-*|-|-|-|-|-|-|-|-|-*|*|*|*|*|*|*|*|*|-*-*-*-*-* *|*|*|*|*|*|*|*|*|-*-*-*-*-*-*-*-*-*|-|-|-|-|-|-|-|-|- -*-*-*-*-*|-|-|-|-|-|-|-|-|-*|*|*|*|*|*|*|*|*|-*-*-*-* |-*|*|*|*|*|*|*|*|*|-*-*-*-*-*-*-*-*-*|-|-|-|-|-|-|-|- -*-*-*-*-*-*|-|-|-|-|-|-|-|-|-*|*|*|*|*|*|*|*|*|-*-*-* |-|-*|*|*|*|*|*|*|*|*|-*-*-*-*-*-*-*-*-*|-|-|-|-|-|-|-| ||||-------|||||||||||||||||||||||||||||||||||||||||||| ||||||||||||||||||||||||||||||||||||||||||||||||||||||| • It looks neat in the beginning.. Clearly every one gets the same amount of time.. • Suddenly things happens.. • The blocks are no longer of equal size • Sometimes a lot of switching • Conclusion: do not try to guess any order of execution.
Use thread or interface? • The class java.lang.Thread actually implements Runnable • The 1.4.2 java API states in java.lang.Runnable • “In most cases, the Runnable interface should be used if you are only planning to override the run() method and no other Thread methods.This is important because classes should not be subclassed unless the programmer intends on modifying or enhancing the fundamental behavior of the class.“ • On the other hand in java.lang.Thread no such distinction is made, and both approaches are shown.
States of a thread • The life cycle of a thread can be describe as Blocked New Ready Running Done
Thread Methods void start() • Creates a new thread and makes it runnable • Can be called only once • When extending Thread remember to call super if you overwrite it void run() • Defines the life of the thread – how it is run, if you will. • The thread dies when exiting the run method.
States of a thread • The life cycle of a thread can be describe as Blocked New Ready Running Done
Thread Methods • yield() • Causes the currently executing thread object to temporarily pause and allow other threads to execute • Allow only threads of the same priority to run • sleep(int m) • The thread sleeps for m milliseconds • Does not release it’s lock on the object • Both methods has the effect that the thread stops for some time, but eventually will continue again automatically.
States of a thread • The life cycle of a thread can be describe as Blocked New Ready Running Done
Thread Methods • Threads can interact with each other • Since threads is just an object, one can call methods and set fields • Or do the same on an object which is shared by several threads • join([int millis]) • Waits for this thread to die • Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.
States of a thread • The life cycle of a thread can be describe as Blocked New Ready Running Done
Threads and predictability • Innocent programs can easily become unpredictable class A extends Thread { public static int x = 0; public final static int N = 1000000; public void run() { for(int i = 0; i < N; i++) A.x = A.x + 1; } public static void main(String[] args) throws Exception { new A().start(); new A().start(); Thread.sleep(3000); System.out.println( A.x + " " + (A.x == 2*N) ); } }
Threads and predictability • We do not get the correct result. • It can be difficult to spot dangerous from peaceful code. • The program is highly dependent on time, had we not waited the 3 seconds, we would always get a failure • We get a different result each time – two runs are not identical.. maybe only sometimes the execution fails
Threads and predictability • A better version in that it does what we try to express • what happens if a2 finishes before a1? class A extends Thread { public static int x = 0; public final static int N = 1000000; public void run() { for(int i = 0; i < N; i++) A.x = A.x + 1; } public static void main(String[] args) throws Exception { A a1 = new A(); a1.start(); A a2 = new A(); a2.start(); a1.join(); a2.join(); System.out.println( A.x + " " + (A.x == 2*N) ); } }
Threads and predictability • So what happened? • What do we really mean when we say A.x = A.x + 1; ? • The problem is tied to reading and writing of values. • Multiple entities can read simultaneously, whereas they cannot write simultaneously. • Actually such a definition does not fully uphold the questions of consistency • A write must ensure it has read valid data (which was wrong above) • A read can only take place in between writes not while writing (e.g. a person consist of a name and a birth of date.. one cannot read information on that person if his data is being modified as you will read an inconsistent person. • We need a notion of “some one having priority over the other threads” in order to be able to leave the system in a consistent state.
Threads and predictability • Race conditionWhen the outcome of a program is affected by the how the program’s threads are given CPU time • Which is something we as programmers rarely have complete control over • Two or more threads are simultaneously modifying a single object (or set of objects in more complex examples) • The threads “race” to store their value
Priority of threads • Every thread has a priority • The priority is inherited from the thread creating the thread • Priorities wary from 1-10 where 10 is the highest • The priority of a thread is changed using setPriority(int) • And read using getPriority() • Priority constants are defined: • MIN_PRIORITY (= 1) • NORM_PRIORITY (= 5) • MAX_PRIORITY (= 10) • Can we solve our previous problem?
Priority of threads • Priorities cannot help us here.. • The problem is intimately tied to how we schedule (or rather that we schedule). • If we were in an environment with no scheduling, the problem would not arise.. and actually that is the key to our solution.. to support a temporary stop of scheduling • However, it should affect fewest threads possible.. or we loose the benefits of threads if constantly one thread excluded the rest. Section 17.12 p. 445 Java language specification Every thread has a priority. When there is competition for processing resources, threads with higher priority are generally executed in preference to threads with lower priority. Such preference is not, however, a guarantee that the highest priority thread will always be running, and thread priorities cannot be used to reliably implement mutual exclusion.
Critical sections • A critical section is a chunk of code which ...MANGLER • To control this we use semaphores • Special variables which can be modified and checked atomically • A critical section can only be entered if the semaphore is 1 • When entering a critical section set the semaphore to 0, and raise it to 1 when exiting the section void bar() { while(SEMAPHOR == 0) { wait x milliseconds } down(); ... critical stuff ... up(); }
Monitors • In Java semaphores are realized as monitors – it works on an object level rather than on the source code level. • Each object has a monitor associated with it. It is used to determine which thread has control of a particular object instance. • In execution ofa synchronized method (or block), access to the object monitor must be gained before the execution • Entering a monitor is also called locking the monitor, or acquiring ownership of the monitor • Access to the object monitor is queued automatically • When a thread tries to lock the monitor that another thread already have ownership over, the thread must wait until the other thread frees the monitor.
Synchronizing class B extends Thread { Object token; public static int x = 0; public final static int N = 100000000; public B(Object token) { this.token = token; } public void run() { synchronized(token) { for(int i = 0; i < N; i++) B.x = B.x + 1; } } public static void main(String[] args) throws Exception { Object token = new Object(); B b1 = new B(token); b1.start(); B b2 = new B(token); b2.start(); b1.join(); b2.join(); System.out.println( B.x + " " + (B.x == 2*N) ); } }
More on synchronized • A typical usage is synchronizing the current object which is being called. • If you access a bank account you don’t want any other to access that particular account. • Such a technique is called syntactic sugar void foo () { synchronized (this) { ... } } • can be specified as synchronized void foo() { ... } • Marking a method as synchronized, associates a monitor with the class itself
Synchronizing on yourself • If one thread is doing some deposit, another thread cannot access that bank account; neither deposit() nor withdraw() • pitfalls: fields must be private, all methods must be synchronized public class BankAccount { private float balance; publicsynchronizedvoid deposit(float amount) { balance += amount; } publicsynchronizedvoid withdraw(float amount) { balance -= amount; } }
Synchronizing • So can we use the syntax shortcut here? class B extends Thread { Object token; public static int x = 0; public final static int N = 100000000; public B(Object token) { this.token = token; } public synchronized void run() { for(int i = 0; i < N; i++) B.x = B.x + 1; } public static void main(String[] args) throws Exception { Object token = new Object(); new B(token).start(); new B(token).start(); Thread.sleep(3000); System.out.println( B.x + " " + (B.x == 2*N) ); } }
Deadlocks • A deadlock is a situation where the program waits forever for some resource which will never be released. This situation has to apply to several threads • also called a deadly embrace • One of the most used examples is that one thread wants to acquire a printer and then a fax, while another thread wants to acquire a fax and a printer. t1 { printer.acquire(); fax.acquire(); } t2 { fax.acquire(); printer.acquire(); } class Printer { synchronized void acquire() {.. } class Fax { synchronized void acquire() {.. }
Deadlocks • Yet another simple example public void doSomething() { Synchronized (a) { Synchronized (b) { // code } } } public void doOther() { Synchronized (b) { Synchronized (a) { // code } } }
Deadlocks public class Account { private float balance; publicsynchronizedvoid deposit(float amount) { balance += amount; } publicsynchronizedvoid withdraw(float amount) { balance -= amount; } publicsynchronizedvoid transfer(float amount, Account target){ withdraw(amount); target.deposit(amount); } }
Deadlocks public class MoneyTransfer extends Thread { private Account from, to; private float amount; public MoneyTransfer(Account from, Account to, float amount) { this.from = from; this.to = to; this.amount = amount; } public void run() { source.transfer(amount, target); } }
Deadlocks Account bert = new Account(); Account anna = new Account(); ... Runnable trans1 = new MoneyTransfer(bert, anna, 300); trans1.start(); ... Runnable trans2 = new MoneyTransfer(anna, bert, 500); trans2.start();
Deadlocks • Will this code cause deadlock problems? class A { synchronized void a() { b(); } synchronized void b() {} public static void main() { new A().a(); } }
Like waiting for the bus... • Two threads can wait for each other’s termination...what happens? class JoinExample extends Thread { public JoinExample whoToWaitFor; public void run() { try{ whoToWaitFor.join(); } catch(InterruptedException e){} } public static void main(String[] args) { JoinExample j1 = new JoinExample(); JoinExample j2 = new JoinExample(); j1.whoToWaitFor = j2; j2.whoToWaitFor = j1; j1.start(); j2.start(); }}
Solutions to dead locks • Make acquiring of multiple monitors an atomic action • a synchronized method in a separate object which all the threads use. • Use tools to analyze code to find potential problems
Thread Methods void wait([millis]) • Thread releases its monitor on that object. The thread will wait until some other thread calls notify/notifyAll. millis is an optional timeout paramet er. Any lock the thread has is not released. void notify() • Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen. The choice is arbitrary. • Can only call it if the thread is the owner of the objects monitor (this is only a problem if not all methods are synchronized) void notifyAll() • Wakes up all threads waiting. Only one thread is allowed to acquire the monitor, but all waiting threads are awoken and are ready to try acquire the monitor. Good for things such as a shutdown method.
States of a thread • The life cycle of a thread can be describe as Blocked New Ready Running Done
Thread methods • A thread cannot ask if it has control over another object or not. So i.e. wait(2000) will wait 2 seconds, and then let the thread compete for gaining control with any other thread. • The advantage is that if you know that some condition will not be true before a certain amount of time, other threads may enjoy the lock of the object instead of giving you control.
Producer-consumer • A typical pattern using threads is the producer-consumer pattern • Can be described as a synchronized transaction between two parties • One or more guys work to produce some product and you have one or more guys who consumes (uses) these products. • Here show only the most simple case
producer-consumer • A classic example is a web server • A server should provide information to clients fastest possible • We cannot allow that the server doesn’t answer a client • Solution: use threads... • But what if we have 1000 clients asking for information simultaniously? • Threads are not cheap, especially if there are many threads • 1:1 client-thread is thus not an option • But 10 worker threads could be ok. • Every time a request for a document reaches the server, the server asks a thread to go fetch the document and return that to the client. • But if the thread the server asks is busy fetching a document, the server will block and wait... • When the server is blocked, the server does not respond to client requests! • Solution: let the server but requests in a queue, and let the threads take care of the tasks. • The server is not aware of how the requests are handled – loose coupling
producer-consumer • The overall structure of our program • Due to the loose coupling the queue could time how long jobs wait in the queue and scale the number of threads clients queue workers server
Producer-consumer • what happens if the server wants to check out what it had last put into the queue? • what happens if some worker thread is awoken for some reason but there are no jobs in the queue? E.g. other kinds of jobs are put in the queue class Server { JobQueue queue; receiveRequest(String url) { queue.add(url); } } class JobQueue { ArrayList queue; synchronized void add(String url) { queue.add(url); this.notify(); } synchronized String getJob() { if(queue.size() == 0) wait(); return queue.get(0); } } class Worker extends Thread { JobQueue queue; public void run() { while(true) { queue.getJob(); ... } }
Producer-consumer • A better version of the consumer part. If awoken it will lay dormant if the needed resource is unavailable. • Consumers and producers generally looks like synchronized String getJob() { while(queue.size() == 0) wait(); return queue.get(0); } synchronized(token) { while (!resourceAvailable()) token.wait(); consumeResource(); } produceResource(); synchronized(token) { token.notifyAll(); }
Actions on threads • Notice that all operations shown today affects the current running thread, never other threads • except for start() • suspend(), resume(), stop() allows one to perform actions on threads, but they are inherently dangerous. • suspend() stops a thread without releasing monitor acquiring • I have tried this where System.out got locked by a thread which then was suspended -> resulted in when printing debug messages to the screen the program stopped • stop() stops a thread and releases monitors, however, it may easily leave objects in only partial sane state and will may have waiting threads wait forever since the stopped thread did not call a notify() • see the Javadoc for a good explanation of this.
Stopping threads • So how then do we stop a thread? We ask it to stop, and leave it up to the thread to stop (return from its run method) in an orderly fashion. • The thread should check this variable regularly private volatile Thread running; public void stop() { running = null; } public void run() { Thread thisThread = Thread.currentThread(); while (running == thisThread) { ..thread body.. } }
Stopping threads • What if the thread is blocked/waiting? • stopping the thread takes a long time because the thread has to wake up to realize it must stop • One can send it signals – the interrupted exception is used for such a thing • of course this requires the thread has been programmed to take apropriate actions upon receiving such a notification public void stop() { Thread tmp = running; running = null; tmp.interrupt(); }