Individual execution paths (with or without sharing). Here‘s an applet that illustrates the notion. A thread is a single sequential flow of control within a process. A single process can have multiple concurrently executing threads. For example, a process may have a thread reading input from the user, while at the same time another thread is updating a database containing the user‘s account balance, while at the same time a third thread is updating the display with the latest stock quotes. Such a process is called a multithreaded process; the program from which this process executes is called a multithreaded program. The To create a new thread of execution, you first declare a new class that is a subclass of You then create an instance of this subclass, followed by a call to theclass myThread extends Thread { public void run() { // do something } } start() method (which really is, because of inheritance, Thread.start() . That method will execute the run() method defined by this subclass.
You can achieve the same effect by having the class directly implement themyThread m = new myThread(); m.start(); // something else Runnable interface.
To create a thread to execute thisclass A implements Runnable { public void run () { // do something } } run() method, do the following:
Thread Priorities Each thread has a priority that is used by the Java runtime in scheduling threads for execution. A thread that has a higher priority than another thread is typically scheduled ahead of the other thread. However, the way thread priorities are precisely affect scheduling is platform-dependent. A thread inherits its priority from the thread that created it. A thread‘s priority can be changed subsequent to the thread‘s creation at any time using theA a = new A(); Thread m = new Thread(a); m.start(); // do something else setPriority() method in the Thread class, if allowed by the security manager.
Thread State and Synchronization between Threads When a thread is started, its state is active. Its state remains active until it has terminated execution or is stopped. An active thread can be executing or suspended. When a thread is first started, it starts executing its Interrupts A thread can send an interrupt to another thread. This sets a flag in the target thread to indicate that it has been interrupted. The target thread can then check for this flag at its discretion and react appropriately. Examples There are four kinds of threads programming:
1. Unrelated Threads The simplest threads program involves threads of control that do different things and don‘t interact with each other, and this will be our first example.
This was our first example.frilled.cs.%cat Drinks.java public class Drinks { public static void main(String[] a) { Coffee t1 = new Coffee(); t1.start(); new Tea().start(); //an anonymous thread } } class Coffee extends Thread { public void run() { try { while (true) { System.out.println("I like coffee..."); sleep(500); } } catch (InterruptedException e) { return; // end this thread } } } class Tea extends Thread { public void run() { try { while (true) { System.out.println("I like tea..."); sleep(700); } } catch (InterruptedException e) { return; // end this thread } } } frilled.cs.%javac Drinks.java frilled.cs.%java Drinks I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like tea... I like coffee... I like coffee... I like tea... I like coffee... frilled.cs.% 2. Related but Unsynchronized Types This level of complexity has threaded code to partition a problem, solving it by having multiple threads work on different pieces of the same data structure. The threads don‘t interact with each other. Here, threads of control do work that is sent to them, but don‘t work on shared data, so they don‘t need to access it in a synchronized way. An example of this would be spawning a new thread for each socket connection that comes in. A less common but still interesting example of related but unsynchronized threads involves partitioning a data set, and instantiating multiple copies of the same thread to work on different pieces of the same problem. Be careful not to duplicate work, or even worse, to let two different threads operate on the same data at once. Here‘s an example program that tests whether a given number is a prime number. That involves a lot of divisions so it‘s a good candidate for parcelling the work out among a number of threads. Tell each thread the range of numbers it is to test-divide into the possible prime. Then let them all loose in parallel, as illustrated below. First the code:
I will have a few questions in class but won‘t list them here now.frilled.cs.%cat TestPrime.java public class TestPrime { public static void main(String s[]) { long possPrime = Long.parseLong(s[0]); int centuries = (int) (possPrime/100) + 1; for (int i=0; i < centuries; i++) { new TestRange(i*100, possPrime).start(); } } } class TestRange extends Thread { static long possPrime; long from, to; // constructor // record the number we are to test, and // the range of factors we are to try TestRange (int argFrom, long argPossPrime) { possPrime = argPossPrime; if (argFrom == 0) from = 2; else from = argFrom; to = argFrom + 99; } public void run () { for (long i = from; i <= to && i < possPrime; i++) { if (possPrime % i == 0) { // i divides possPrime exactly System.out.println("factor " + i + " found by thread " + getName()); return; } yield(); } } } frilled.cs.%javac TestPrime.java frilled.cs.%java TestPrime 9001 frilled.cs.%java TestPrime 9002 factor 2 found by thread Thread-0 factor 643 found by thread Thread-6 factor 1286 found by thread Thread-12 factor 4501 found by thread Thread-45 frilled.cs.% 3. Mutually-Exclusive Threads Here‘s where threads start to interact with each other, and that makes life a little more complicated. In particular we use threads which need to work on the same pieces of the same data structure. These threads need to take steps to stay out of each others‘ way so that they don‘t each simultaneously modify the same piece of data leaving an uncertain result. Staying out of each other‘s way is known as mutual exclusion. Here‘s an example. The code below simulates a steam boiler. It defines some values (the current reading of a pressure gauge, and the safe limit for that gauge), and then instantiates 10 copies of a thread called
And here‘s the output when you try to run it:public class SteamBoiler { static int pressureGauge = 0; static final int safetyLimit = 20; public static void main(String[] args) { Pressure[] p1 = new Pressure[10]; for (int i = 0; i < 10; i++) { p1[i] = new Pressure(); p1[i].start(); } try { for (int i=0; i < 10; i++) p1[i].join(); } catch (Exception e) { System.out.println(e); } System.out.println("gauge reads " + pressureGauge + ", safelimit is 20."); } } class Pressure extends Thread { void RaisePressure () { if (SteamBoiler.pressureGauge < SteamBoiler.safetyLimit - 15) { // wait briefly to simulate some calculations try { sleep(100); } catch (Exception e) { } SteamBoiler.pressureGauge += 15; } else ; // pressure too high -- don‘t add to it. } public void run() { RaisePressure(); } } That‘s not good.frilled.cs.%java SteamBoiler gauge reads 150, safelimit is 20. frilled.cs.% This is a classic example of what is called a data race or a race condition. A race condition occurs when two or more threads update the same value simultaneously. To avoid data races, follow this simple rule: whenever two threads access the same data, they must use mutual exclusion. You can optimize slightly, by allowing multiple readers at one instant. In Java, thread mutual exclusion is built on data
The Java programmer never deals with the low-level and error-prone details of creating, acquiring and releasing locks, but only specifies the region of code and the object that must be exclusively held in that region. You want to make your regions of synchronized code as small as possible, because mutual exclusion really chokes performance. Here are examples of each of these alternatives of synchronizing over a class, a method, or a block, with comments on how exclusion works. Mutual exclusion over an entire class This is achieved by aplying the keyword Here are some exercises for your practice.static synchronized void RaisePressure() { ... }
Mutual exclusion over a block of statements This is achieved by attaching the keyword
You need to provide thevoid RaisePressure () { synchronized(someObject) { if (SteamBoiler.pressureGauge < SteamBoiler.safetyLimit - 15) { // same code as before } else ; // pressure too high -- don‘t add to it. } } Obj object, so we declare it in steamBoiler :
static Object semaphore = new Object();
Mutual exclusion over a method This is achieved by applying the keyword
is equivalent tosynchronized void fun() { ... } Note: this won‘t work in our example for obvious reasons (each one of the 10 pressure checker threads will be able to seize a lock on themselves and the race condition will reoccur).void fun() { synchronized(this) { ... } }
4. Communicating and Mutually-Exclusive Threads Here‘s where things become downright complicated until you get familiar with the protocol. The hardest kind of threads programming is where the threads need to pass data back and forth to each other. Imagine that we are in the same situation as in the previous section: we have threads that process the same data, so we need to run synchronized. However, in our new case, imagine that it‘s not enough just to say "don‘t run while I am running". We need the threads to be able to say: "OK, I have some data ready for you" and to suspend themselves if there isn‘t data ready. There‘s a convenient parallel programming idiom, known as
The most common occurrence of this is a producer/consumer situation - one thread is producing the data irregularly, and another thread is consuming (processing) it when it can. Usually the producer is storing the produced data into some kind of bounded buffer which means that the produce(r) may fill it up and will need to Here‘s the pseudo-code for the Wait and Notify://producer thread: produces one datum enter synchronized code (i.e., grab mutex lock) while (buffer_full) wait() // read below the semantics of wait (re: lock) produce_data notify() leave synchronized code (i.e., release lock)
wait and notify are methods in the basic class Object so they are shared by all objects in the system.
There are several variants: The difference betweenpublic final native void notify(); public final native void notifyAll(); public final void wait () throws InterruptedException; public final void wait (long time, int nanos) throws InterruptedException; public final native void wait (long timeout) throws InterruptedException;
frilled.cs.%ls -ld *.java -rw------- 1 dgerman 517 Apr 10 16:04 Consumer.java -rw------- 1 dgerman 1002 Apr 10 16:27 Producer.java -rw------- 1 dgerman 173 Apr 10 15:59 WaitNotify.java frilled.cs.%cat W*.java public class WaitNotify { public static void main(String args[]) { Producer p = new Producer(); p.start(); Consumer c = new Consumer(p); c.start(); } } frilled.cs.%cat P*.java class Producer extends Thread { private String[] buffer = new String[8]; private int pi = 0; // produce index private int gi = 0; // get index public void run () { // just keep producing for (;;) produce(); } private final long start = System.currentTimeMillis(); private final String banana() { return "" + (int) (System.currentTimeMillis() - start); } synchronized void produce() { // while there isn‘t room in the buffer while (pi-gi + 1 > buffer.length) { try { wait(); } catch (Exception e) { } } buffer[pi&0x7] = banana(); System.out.println("produced[" + (pi&0x7) + "] " + buffer[pi&0x7]); pi++; notifyAll(); } synchronized String consume() { // while there‘s nothing left to take from the buffer while (pi == gi) { try { wait(); } catch (Exception e) { } } notifyAll(); return buffer[gi++&0x7]; // mask off the bits (lowest 3 bits - circular buffer) } } frilled.cs.%cat C*.java class Consumer extends Thread { Producer whoIamTalkingTo; // java idiom for constructor Consumer (Producer who) { whoIamTalkingTo = who; } public void run() { java.util.Random r = new java.util.Random(); for (;;) { String result = whoIamTalkingTo.consume(); System.out.println("consumed: " + result); // next line is just to make it run a bit slower int randomtime = r.nextInt() % 250; try { sleep(randomtime); } catch (Exception e) { } } } } frilled.cs.%javac W*.java frilled.cs.%java WaitNotify produced[0] 3 produced[1] 5 produced[2] 6 produced[3] 7 produced[4] 7 produced[5] 7 produced[6] 8 produced[7] 8 consumed: 3 produced[0] 10 consumed: 5 produced[1] 96 consumed: 6 consumed: 7 consumed: 7 consumed: 7 consumed: 8 consumed: 8 consumed: 10 consumed: 96 produced[2] 349 produced[3] 349 produced[4] 350 produced[5] 350 produced[6] 350 produced[7] 351 produced[0] 351 produced[1] 351 consumed: 349 consumed: 349 consumed: 350 consumed: 350 consumed: 350 produced[2] 507 produced[3] 513 produced[4] 513 produced[5] 514 produced[6] 514 consumed: 351 produced[7] 626 consumed: 351 produced[0] 796 consumed: 351 consumed: 507 consumed: 513 produced[1] 926 produced[2] 927 produced[3] 927 consumed: 513 produced[4] 956 consumed: 514 consumed: 514 produced[5] 1126 produced[6] 1126 consumed: 626 produced[7] 1276 consumed: 796 produced[0] 1336 consumed: 926 produced[1] 1346 consumed: 927 consumed: 927 consumed: 956 produced[2] 1566 produced[3] 1567 produced[4] 1567 consumed: 1126 consumed: 1126 consumed: 1276 produced[5] 1656 produced[6] 1657 produced[7] 1657 consumed: 1336 consumed: 1346 consumed: 1566 consumed: 1567 produced[0] 1907 produced[1] 1907 produced[2] 1907 produced[3] 1908 consumed: 1567 produced[4] 2076 ^Cfrilled.cs.%exit ![]()
Notes
Last updated: Apr 8, 2002 by Adrian German for NC009
|
|