Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Java Threads CS 350 – Fall 2001 – 11/29/01, updated 3/18/02 … Preliminary … Java Threads (Reference: Silberschatz: page 122 …): See instructors commentary notes on Java (on the web site). Overview Provides thread support at the language level All java programs have at least one thread corresponding to the main method. Other threads can be created using system provided classes in a program. The primary focus will be on stand-alone java programs containing a main method (as opposed to applets). NOTE: Java threads exist in the “Java Virtual Machine” which is usually implemented as a layer above the Operating System Thread creation There is a generic thread class provided by JDK from which all threads in an application may be derived by extension. There is a method in the thread class called run() which does nothing. In the extended class, the run() method is defined and will over-ride the generic run() in the thread class with the desired behavior of the thread being implemented, ie., run() will be implemented in the extended class. Calling the start() method in the derived thread (from a class in your program) creates the new thread in the JVM. Allocation alone does not fire off the thread. In addition to initializing the new thread, start will call run() which will make the thread “do its thing” in the JVM. Note: do not call run directly – let start() do it. See and try the example in the example below (ref Fig. 5.7, Silberschatz): Java Threads Spring 2002 11/29/01 page 1 class Worker1 extends Thread { public void run() { System.out.println("I Am a Worker Thread"); } } public class First { public static void main(String args[]) { Worker1 runner = new Worker1(); runner.start(); System.out.println("I Am The Main Thread"); } } An alternate method of creating a thread is by implementing the interface called Runnable provided by JDK. This interface class contains the nonimplemented run() method. The class containing main creates an object (call it runner) from the implemented abstract class. Finally main allocates an object for the thread class passing a reference to runner to its constructor. Main then calls start() in this thread object. See fig 5.8 for an example: class Worker2 implements Runnable { public void run() { System.out.println(“I am a worker thread. } } “); public class Second { public static void main(String args[]) { Runnable runner = new Worker2(); Thread thrd = new Thread(runner); thrd.start(); System.out.println("I Am The Main Thread"); } } Java Threads Spring 2002 11/29/01 page 2 Thread Management The thread class provides various methods for managing threads. The following API’s are provided: suspend() – suspends the execution of the currently running thread sleep() – puts the current thread to sleep for a specified amount of time. resume() – resumes execution of a suspended thread see the clock applet example Fig 5.9: /** * ClockApplet.java * This applet displays the time of day. * Note that this program uses deprecated methods, * an alternative version will be shown in chapter 8. */ import java.applet.*; import java.awt.*; public class ClockApplet extends Applet implements Runnable { public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) {} repaint(); } } // this method is called when the applet is started or // we return to the applet public void start() { if ( clockThread == null ) { clockThread = new Thread(this); clockThread.start(); } else clockThread.resume(); } // this method is called when we leave the page the applet is on public void stop() { if (clockThread != null) clockThread.suspend(); } // this method is called when the applet is removed from the cache public void destroy() { Java Threads Spring 2002 11/29/01 page 3 if (clockThread != null) { clockThread.stop(); clockThread = null; } } public void paint(Graphics g) { g.drawString( new java.util.Date().toString(), 10, 30); } private Thread clockThread; } Thread states: Similar to processes, threads have the states: New – the state on being created using the “new” statement in the code. Runnable – when run() is called via the start() call, the thread is moved to the runnable state. This means that the thread is eligible to be run in the JVM. This state is a bit like the ready state for processes, except for one difference: when a thread goes into active execution, it is still in the runnable state instead a s distinct “running” state which a process would be in. Blocked – a thread becomes blocked if it performs a blocking call such as I/O or it does sleep() or suspend call. Dead – A thread goes to the dead state when its run() method terminates or when its stop() method is called. Threads and the JVM/Operating system (see pp. 128-129) Remember all java threads “live” in the JVM. Some JVM threads are for system management, while others are user created in applications (sect 5.6.4). Java Threads Spring 2002 11/29/01 page 4 JVM is usually implemented on “top” of the host operatins system. The JVM specification does not indicate how Java threads are mapped to underlying the operating system (threads) – this is left to the particular implementation. Typically a Java thread is considered a user-level thread, and the JVM is responsible for the thread management. The various OS/User thread models previously discussed applies to Java threads treated as user threads. A multithreaded solution to the producer/consumer problem See code given in both chapter 4 and 5 for the bounded buffer and “IPC” solution, and in chapter7 for the bounded buffer semaphore solution . Complete code is given in the author’s web site: http://www.bell-labs.com/topic/books/aos-book/ Also see diagram below in this document for the description of classes used to solve the shared memory or bounded buffer version of the problem (chapter 4). Java Threads Spring 2002 11/29/01 page 5 Java Threads Spring 2002 11/29/01 page 6 Code for the multi-threaded “bounded buffer” solution as given in Chapter 4. /** * Server.java * no-synch case – ch.4 * This creates the buffer and the producer and consumer threads. * * @author Greg Gagne, Peter Galvin, Avi Silberschatz * @version 1.0 - July 15, 1999 * Copyright 2000 by Greg Gagne, Peter Galvin, Avi Silberschatz * Applied Operating Systems Concepts - John Wiley and Sons, Inc. */ public class Server { public static void main(String args[]) { BoundedBuffer server = new BoundedBuffer(); // now create the producer and consumer threads Producer producerThread = new Producer(server); Consumer consumerThread = new Consumer(server); producerThread.start(); consumerThread.start(); } } Java Threads Spring 2002 11/29/01 page 7 /** * Producer.java * no-synch case – ch.4 * This is the producer thread for the bounded buffer problem. * * @author Greg Gagne, Peter Galvin, Avi Silberschatz * @version 1.0 - July 15, 1999 * Copyright 2000 by Greg Gagne, Peter Galvin, Avi Silberschatz * Applied Operating Systems Concepts - John Wiley and Sons, Inc. */ import java.util.*; public class Producer extends Thread { public Producer(BoundedBuffer b) { buffer = b; } public void run() { Date message; while (true) { int sleeptime = (int) (BoundedBuffer.NAP_TIME * Math.random() ); System.out.println("Producer sleeping for " + sleeptime + " seconds"); try { sleep(sleeptime*1000); } catch(InterruptedException e) {} // produce an item & enter it into the buffer message = new Date(); System.out.println("Producer produced " + message); buffer.enter(message); } } private BoundedBuffer buffer; } Java Threads Spring 2002 11/29/01 page 8 /** * Consumer.java * no-synch case – ch.4 * This is the consumer thread for the bounded buffer problem. * * @author Greg Gagne, Peter Galvin, Avi Silberschatz * @version 1.0 - July 15, 1999 * Copyright 2000 by Greg Gagne, Peter Galvin, Avi Silberschatz * Applied Operating Systems Concepts - John Wiley and Sons, Inc. */ import java.util.*; public class Consumer extends Thread { public Consumer(BoundedBuffer b) { buffer = b; } public void run() { Date message; while (true) { int sleeptime = (int) (BoundedBuffer.NAP_TIME * Math.random() ); System.out.println("Consumer sleeping for " + sleeptime + " seconds"); try { sleep(sleeptime*1000); } catch(InterruptedException e) {} // consume an item from the buffer System.out.println("Consumer wants to consume."); message = (Date)buffer.remove(); } } private BoundedBuffer buffer; } Java Threads Spring 2002 11/29/01 page 9 /** * BoundedBuffer.java * no-synch case – ch.4 * This program implements the bounded buffer using shared memory. * Note that this solutions is NOT thread-safe. It will be used * to illustrate thread safety using Java synchronization in Chapter 7. * * @author Greg Gagne, Peter Galvin, Avi Silberschatz * @version 1.0 - July 15, 1999 * Copyright 2000 by Greg Gagne, Peter Galvin, Avi Silberschatz * Applied Operating Systems Concepts - John Wiley and Sons, Inc. */ import java.util.*; public class BoundedBuffer { public BoundedBuffer() { // buffer is initially empty count = 0; in = 0; out = 0; buffer = new Object[BUFFER_SIZE]; } // producer calls this method public void enter(Object item) { while (count == BUFFER_SIZE) ; // do nothing // add an item to the buffer ++count; buffer[in] = item; in = (in + 1) % BUFFER_SIZE; if (count == BUFFER_SIZE) System.out.println("Producer Entered " + item + " Buffer FULL"); else System.out.println("Producer Entered " + item + " Buffer Size = " + count); } // consumer calls this method public Object remove() { Object item; while (count == 0) ; // do nothing // remove an item from the buffer --count; item = buffer[out]; out = (out + 1) % BUFFER_SIZE; if (count == 0) System.out.println("Consumer Consumed " + item + " Buffer EMPTY"); else System.out.println("Consumer Consumed " + item + " Buffer Size = " + count); return item; } public static final int private static final int private private private private NAP_TIME = 5; BUFFER_SIZE = 3; volatile int count; int in; // points to the next free position in the buffer int out; // points to the next full position in the buffer Object[] buffer; } Java Threads Spring 2002 11/29/01 page 10 Java Synchronization Based on “Applied Operating System Concepts”, 1st ed., by Silberschatz. There are three mechanisms offered by the Java language for assisting in process synchronization. yield() method If you use some form of busy wait to achieve synchronization, then to avoid wasting CPU time, the yield() method could be called as the thread polls on some condition. Yield() simply causes the calling thread to relinquish the CPU and voluntarily goes to the ready queue. only threads of equal or higher priority can ruin in its place. This has the potential of deadlock. Use of the synchronized key word for methods: Example: public synchronized Object remove() { …} Every object in Java has associated with it a single lock. If a method of an object is declared with the synchronized key word, calling the method requires owning the lock for the object. If the lock is already owned, the thread calling the synchronized method blocks and is put in the entry set queue for the object’s lock. If the lock is available when a synchronized method is called, the calling thread becomes the owner of the lock, and it can enter the method. The lock is released when the thread exits the method. If an entry set for the lock is not empty, when the lock is released, the JVM selects an arbitrary thread from this set as the new owner of the lock. See below: ... take the lock with you when entering the CS: This technique is OK for mutual exclusion, except when the owner of a lock uses a yield() polling loop to wait for an event to happen (for example, in the producer/consumer problem, the producer sees a full queue, and goes into a yield polling loop while holding the lock – see fig 7.28), the consume will never be able to Java Threads Spring 2002 11/29/01 page 11 consume anything to free up the producer because it does not have the lock. Thus we have deadlock again. We need some way of having one thread signaling another waiting thread when a condition occurs. This is achieved with the wait() and notify() synchronization primitives. Use of wait() and notify() along with the synchronized key word In a method declared with the synchronized key word In addition to having a lock and entry set, every object also has associated with if a wait set (of threads which is initially empty). When a thread enters a synchronized method, it owns the lock for the object. However, this thread may determine that it is unable to continue because a certain condition is not met (example, buffer full for producer). In this case the thread calls the wait() method, with the following happening: 1. 2. 3. The thread releases the lock for the object. The state of the thread is set to Blocked. The thread is placed in the wait set for the object. When some other thread calls notify(), indicating the condition the first thread was waiting or has taken place, we have the following events. The call to notify(): 1. 2. 3. Picks an arbitrary thread T from he list of threads in the wait set. Moves T from the wait set to the entry set. Sets the state of T from Blocked to Runnable (ready). T is now eligible to compete for the lock with the other threads. Once T has regained control of the lock, it returns from calling wait(), where it may check the condition that cause the block again. See below: Java Threads Spring 2002 11/29/01 page 12 See the source code for the bounded buffer problem (producer/consumer), for an illustration of the wait()/Notify() synchronization techniques (fig. 7.30 or {7.32, 7.33, 7.34}). Comparison of java Wait/notify with semaphore wait/signal: wait() and notify() resemble the wait(sem) and signal(sem) for semaphores, but there is a fundamental difference. The semaphore has an value associated with it. By calling wait(sem), you automatically blocks or “falls through” depending on the semaphore value and signal(sem) always increments the value in addition to releasing a thread/process from the wait queue. the Java wait(), on the other hand, unconditionally blocks when called. There is no value which automatically gets tested to determine if you block or not as in a semaphore. The calling thread has the responsibility to explicitly determine it it should block by calling wait(). For example the producer makes this determination by explicitly testing if the count is equal to the buffer size. In addition to release a thread from a Java wait queue, the consumer must explicitly do something that would release a resource that the producer is waiting for before calling notify(). This is similar to the signaling mechanism in semaphores. Typically the Java wait() is called after the thread is in the critical section and it discovers (by explicitly testing) that it must wait for some event. The counting semaphore wait(sem), on the other hand is always called and may or may not block. Java does not have a built in semaphore, but a semaphore can be coded in Java using the Java synchronization techniques to make the wait/signal atomic: Java Threads Spring 2002 11/29/01 page 13 /** * Semaphore.java * * A basic counting semaphore using Java synchronization. * * @author Greg Gagne, Peter Galvin, Avi Silberschatz * @version 1.0 - July 15, 1999 * Copyright 2000 by Greg Gagne, Peter Galvin, Avi Silberschatz * Applied Operating Systems Concepts - John Wiley and Sons, Inc. */ public final class Semaphore { public Semaphore() { value = 0; } public Semaphore(int v) { value = v; } public synchronized void P() { while (value <= 0) { try { wait(); } catch (InterruptedException e) { } } value --; } public synchronized void V() { ++value; notify(); } private int value; } Solution for the Bounded buffer problem using java semaphores: See fig. { 7.13, 7.14, 7.15} Java Threads Spring 2002 11/29/01 page 14