by Brian Gloyer
A thread executes a series of instructions. Every line of code that is executed is done so by a thread. Some threads can run for the entire life of the applet, while others are alive for only a few milliseconds. A thread also creates a new thread or kills an existing one. Threads run in methods or constructors. The methods and constructors themselves are lifeless. The threads go into the methods and follow their instructions. Methods and constructors reside in the computer's memory. Figure 16.1 shows threads, constructors, and methods in a typical applet.
Figure 16.1. Two threads running through three classes.
The applet methods start(), paint(), and so on are all called by underlying threads. These threads are created by the Web browser. When there is a mouse click, or a repaint() has been called. The underlying thread calls the appropriate thread in the applet. While the threads are running in the applet methods, they cannot do anything else. If the applet holds theses threads too long, it locks up the browser. If an applet needs to do something that will take some time in one of its methods, it should start a new thread. Some specific uses for threads are listed below:
The class java.lang.Thread is used to create and control threads. To create a thread, a new instance of this class must be created. However, the thread does not start running right away. Thread.start() must be called to actually make the thread run. When Thread.start() is called, the thread begins executing in the run() method of the target class. A new Thread class always starts running the public void run() method of a class. There are two ways to create a thread:
There isn't much difference between the two approaches. Both extending the Thread class and implementing the Runnable interface have the same functionality. The interface approach must me used if the thread is to actually start in the applet class. But if the thread is going to be running in its own class, it may be more convenient to extend the Thread class. Examples of both approaches are in this chapter.
The Thread class has seven constructors. All of them create a new thread. The thread does not start running until Thread.start() is called. When Thread.start() is called, the new thread starts running in the run() method of an object. The constructors are the following:
Thread() Thread(Runnable) Thread(ThreadGroup) Thread(String) Thread(ThreadGroup,String) Thread(Runnable,String) Thread(ThreadGroup,Runnable,String)
The constructors can use three possible parameters:
There are many methods in the Thread class. Some of them (like destroy()) don't seem to have been implemented yet, and may never be. Some of the methods that control the thread execution are the following:
Listing 16.1 shows how to start, stop, suspend, and resume threads. It uses the Runnable interface. Threads like this are useful for things like controlling animation sequences or repeatedly playing audio samples. This example uses a thread that counts and prints a string every second. The thread starts when the applet is initialized. It continues to run until the user leaves the page. If the user returns to the page (and all is well), the thread continues from where it left off. This allows applets to retain their states while the user is away.
Listing 16.1. Thread examples.
import java.lang.Thread; import java.applet.Applet; public class InfiniteThreadExample extends Applet implements Runnable { Thread myThread; public void init() { System.out.println("in init() -- starting thread."); myThread= new Thread(this); myThread.start(); } public void start() { System.out.println("in start() -- resuming thread."); myThread.resume(); } public void stop() { System.out.println("in stop() -- suspending thread."); myThread.suspend(); } public void destroy() { System.out.println("in destroy() -- stoping thread."); myThread.resume(); myThread.stop(); } public void run() { int i=0; for(;;) { i++; System.out.println("At " + i + " and counting!"); try {myThread.sleep(1000);} catch (InterruptedException e ) {} } } }
The output of InfiniteThreadExample is shown here. The applet ran for nine seconds until it was stopped.
in init() -- starting thread. At 1 and counting! in start() -- resuming thread. At 2 and counting! At 3 and counting! At 4 and counting! At 5 and counting! At 6 and counting! At 7 and counting! At 8 and counting! At 9 and counting! in stop() -- suspending thread. in destroy() -- stoping thread.
The applet has only five methods:
Unfortunately, its not always possible to know exactly when or if the methods are called. It can vary from browser to browser, or even by how the user has the browser configured. Netscape 3.0 calls init() and then calls start() the first time the applet is loaded. If the user leaves the page with the applet, stop() is called. Returning to the applet calls start(), but it is possible that init() may be the first called. It depends on whether or not the applet is still residing in memory. If it is, then only start() is called; otherwise, both init() and start() are called again.
The method destroy() is called before the applet is removed from memory. All threads should be destroyed at this time so that its resources can be used by other applets. The browsers can only handle so many threads. If the user visits too many applets that don't clean up after themselves, the browser may crash. Generally, threads should be suspended when the user leaves the page and killed when the applet is destroyed. It is possible to leave threads running while the user is visiting other pages, but the user may not appreciate it.
Listing 16.2 shows how to use threads to handle events. When an event that existing threads shouldn't take the time to handle happens in the applet, a new thread is spawned to handle that event. After the event is handled, the new thread quietly dies. Listing 16.2 uses threads to handle mouse clicks. Each thread draws a blue target, as you can see in Figure 16.2. Methods such as mouseDown() or mouseUp() are called by threads external to the applet. While the thread is running in the applet, no other mouse movements are detected. Keeping the external thread may not just affect the applet, but possibly the whole browser. Generally, these methods should be returned as soon as possible. If it is necessary to do a long computation or wait for something else, a new thread should be started. By starting a new thread, the external thread can return almost immediately.
Listing 16.2. Handling an event with threads.
import java.applet.Applet; import java.awt.*; public class FiniteThreadExample extends Applet { Image offImage; /* off screen image */ Graphics offGraphics; /* Graphics for offImage */ public void init() { offImage=createImage(400,300); offGraphics=offImage.getGraphics(); } public void paint(Graphics g) { g.drawImage(offImage,0,0,null); } public void update(Graphics g) { paint(g); } public boolean mouseDown(Event e, int x, int y) { new DrawTarget(this,x,y,offGraphics); return true; } } class DrawTarget extends Thread { int xPos,yPos; /* position of the target */ Applet myMaster; /* who to repaint */ Graphics offGraphics; /* Graphics to draw on */ public DrawTarget(Applet a, int x, int y, Graphics g) { xPos=x; yPos=y; myMaster=a; offGraphics=g; start(); } public void run() { int i; /* i is direction the circles are moving */ int r; /* r is the radius of the current circle */ offGraphics.setColor(Color.white); offGraphics.setXORMode(Color.blue); for(r=0,i=10;i>-20;i-=20) /* i=(10,-10) */ for(r+=i;(r<90)&&(r>0);r+=i) /* r=(10,20...80,80,70...10) */ { offGraphics.fillOval(xPos-r,yPos-r,2*r,2*r); myMaster.repaint(); try {sleep(200);} catch (InterruptedException e) {} } } }
The class FiniteThreadExample is used to paint the applet, to catch the mouse clicks, but not to start the threads. The applet uses a class that extends the Thread class to start new threads. The class FiniteThreadExample has four methods shown below that sets up things, handles the painting, and catches the mouse clicks:
DrawTarget is where the threads are created to draw the targets. DrawTarget inherits properties from java.lang.Thread and has a single constructor and method, which are listed below:
Figure 16.2. The applet in Listing 16.2 draws targets.
Listing 16.3 shows how data can be corrupted in a multithreaded environment. If more that one thread manipulates shared variables or objects at the same time, corruption may result. Instance variables are shared between threads. If one is modified by a thread, the change affects the other threads. Method variables are unique for each threads. Each Thread has its own copy. Listing 16.3 uses 2 classes, ProblemThreadExample and CorruptedDataExample. The class ProblemThreadExample extends the Applet class. It has two methods, which are listed after Listing 16.3.
Listing 16.3. How to get corrupted data.
import java.lang.*; import java.applet.Applet; public class ProblemThreadExample extends Applet { CorruptedDataExample CDE; public void start() { int i; CDE=new CorruptedDataExample(); for(i=0;i<20;i++) /* start 20 threads */ new Thread(CDE,new String("booThread"+i)).start(); } public void stop() { CDE.stopThreads(); } } class CorruptedDataExample extends Object implements Runnable { int num=0; /* num will be corrupted */ public void run() { int i=0; for(;;) { for(i=0;i<1000;i++) { num=num+10; num=num-10; } try {Thread.sleep(10000);} catch (InterruptedException e ) {} System.out.println(Thread.currentThread().getName()+ " sees the number: " + num); } } void stopThreads() { Thread tArray[]; int numThreads; numThreads=Thread.activeCount(); tArray=new Thread[numThreads]; Thread.enumerate(tArray); for(int i=0;i<numThreads;i++) if(tArray[i].getName().startsWith("booThread")) tArray[i].stop(); } }
The following is the ProblemThreadExample output:
booThread0 sees the number: 0 booThread1 sees the number: 0 booThread2 sees the number: 0 booThread3 sees the number: 0 booThread4 sees the number: 10 booThread6 sees the number: 10 booThread7 sees the number: 10 booThread8 sees the number: 10 booThread9 sees the number: 10 booThread10 sees the number: 0 booThread11 sees the number: 0 booThread12 sees the number: 0 booThread13 sees the number: 0 booThread5 sees the number: 0 booThread14 sees the number: 0 booThread15 sees the number: 0 booThread16 sees the number: 0 booThread17 sees the number: 0 booThread18 sees the number: 0 booThread19 sees the number: 0 booThread0 sees the number: 0 booThread1 sees the number: 0 booThread3 sees the number: 0 booThread4 sees the number: 0 booThread6 sees the number: 0 booThread8 sees the number: 0 booThread9 sees the number: 0 booThread2 sees the number: 0 booThread7 sees the number: 0 booThread10 sees the number: 10 booThread11 sees the number: 0 booThread12 sees the number: -10 booThread13 sees the number: -10 booThread5 sees the number: -10 booThread14 sees the number: -10 booThread16 sees the number: -10 booThread17 sees the number: -10 booThread18 sees the number: -10 booThread19 sees the number: -10
The first step in the infinite loop would have no ill effect in a single threaded environment. It simply adds 10, then subtracts 10 to a variable with the net result being no change. However, in a multithreaded environment, the operations can interfere with each other. You can see one scenario in the following steps in which two threads try to add 10 at the same time.
Step Thread A Thread B num
1. Atmp[la]num 0
2. Atmp[la]Atmp+10 0
3. Btmp[la]num 0
4. Btmp[la]Btmp+10 0
5. num[la]Btmp 10
. . . .
. . . .
. . . .
10. num[la]Atmp 10
Two num=num+10; operations have been executed, but the value of num has only increased by 10. The problem here is that Thread A was interrupted in the middle of its operation before it could save its results. Threads A and B have added 10 to the same number.
This type of problem is somewhat rare, but should not be ignored. In the previous example, 10 is added and subtracted 1000 times in 20 threads, and the problem still did not occur that often. The bad thing about these types of bugs is that they can be extremely difficult to find. Imagine that num is the index of an array. The problem may not show up until long after num has been corrupted. Generally, these bugs are not reproducible, so they are hard to catch. In some ways the problem is amplified in Java because Java is expected to run on so many platforms. On some systems, num=num+10 may be an atomic operation(cannot be interrupted). In this case, everything works fine. A developer may create an applet on such a system thinking that everything is OK, but it may not work when someone from a different system views the applet. Data corruption can also be more common with other data types. Integer operation are simple compared to many others. Arrays or other data structures can take much longer to process so it is much more likely to be corrupted.
In Listing 16.3, the threads are given names. They are named booThread0 through booThread19 when they are created by the constructor Thread(Runnable,String). The names can be used to identify the threads. The thread names are printed along with num in the run() method of CorruptedDataExample. The Thread method current.Thread() is called to get a reference to the currently running thread.
The names are also used to stop the threads. A reference is needed for each thread so that stop() can be called to kill it. Thread.enumerate(Thread[]) gets a reference to every thread in this group. Some of theses threads are the booThreads, but they may be others. The other threads should not be killed. Before each thread is killed its is checked to see if its name starts with booThread.
A way to prevent data from being corrupted by multiple threads is to prevent the interruption of critical regions. Critical regions are places like num=num+10 above, where only one thread should be running at once. Java's synchronized can be used to ensure that only one thread is in a critical region at once. When the thread enters a synchronized code block, it tries to get a lock on that region. While the thread is in the critical region, no other thread can enter the critical region. If a thread tries to enter and the code is already locked, the thread has to wait for the other thread to leave the critical region.
Listing 16.3 can be fixed by synchronizing the access to num. The run() method in CorruptedDataExample can be modified to the following:
public void run() { int i=0; int tmp; /*new*/ for(;;) { for(i=0;i<1000;i++) { synchronized (this) /*new*/ { num=num+10; num=num-10; } } try {Thread.sleep(10000);} catch (InterruptedException e ) {} synchronized (this) /*new*/ {tmp=num;} /*new*/ System.out.println(Thread.currentThread().getName()+ " sees the number: " + tmp); /*new*/ } }
The following lines make up a protected critical region:
synchronized (this) { num=num+10; num=num-10; }
synchronized (this) ensures that only one thread can be in the following code block. The argument this tells the thread to use the lock for this object.
The variable num is also referenced when the string is printed. The new code is as follows:
synchronized (this) /*new*/ {tmp=num;} /*new*/ System.out.println(currentThread().getName()+ " sees the number: " + tmp); /*new*/
A critical region is used to copy num to temporary storage. The string is then printed using the temporary storage. It would have been possible to synchronize the print line directly, but it would cut performance because the print line does many other things that have nothing to do with referencing num. All the threads waiting to enter the critical region will needlessly wait longer while the print line is executed. Generally, the synchronized blocks should be as small as possible while still protecting the critical region.
You may also think that the other variables, i and tmp, in the run method also need to be synchronized, but it's not necessary. Both i and tmp are method variables so each running thread has its own private copy. There are 20 i's and tmp's but there is only one num.
Listing 16.4 can be seen in Figure 16.3. This example shows how synchronized can be used control critical regions. There are two synchronized methods drawRoundTarget() and drawSquareTarget(). If a thread is in a synchronized method, no other thread can be in any synchronized method that uses the same lock. This example draws only one square or circle at a time. The seven methods of the SynchronizedThreadExample applet are shown after Listing 16.4.
Listing 16.4. Using synchronized.
import java.applet.Applet; import java.awt.*; public class SynchronizedThreadExample extends Applet implements Runnable { Image offImage; /* off screen image */ Graphics offGraphics; /* Graphics for offImage */ Thread Thr1, Thr2; /* threads */ public void start() { offImage=createImage(400,300); offGraphics=offImage.getGraphics(); offGraphics.setColor(Color.white); offGraphics.setXORMode(Color.blue); Thr1=new Thread(this); Thr1.start(); Thr2=new Thread(this); Thr2.start(); } public void stop() { Thr1.stop(); Thr2.stop(); } public void paint(Graphics g) { g.drawImage(offImage,0,0,null); } public void update(Graphics g) { paint(g); } public void run() { for(;;) { drawRoundTarget(); drawSquareTarget(); } } synchronized void drawRoundTarget() { for(int r=0,i=10;i>-20;i-=20) /* i=(10,-10) */ for(r+=i;(r<90)&&(r>0);r+=i) /* r=(10,20...80,80,70...10) */ { offGraphics.fillOval(200-r,150-r,2*r,2*r); repaint(); try {Thread.currentThread().sleep(200);} catch (InterruptedException e) {} } } synchronized void drawSquareTarget() { int i,r; for(r=0,i=10;i>-20;i-=20) /* i=(10,-10) */ for(r+=i;(r<90)&&(r>0);r+=i) /* r=(10,20...80,80,70...10) */ { offGraphics.fillRect(200-r,150-r,2*r,2*r); repaint(); try {Thread.currentThread().sleep(250);} catch (InterruptedException e) {} } } }
Figure 16.3. Listing 16.4 draws a square.
The seven methods of the SynchronizedThreadExample applet are as follows:
What if two locks are needed?
The current applet only allows one target to be drawn at a time, be it round or square. Suppose that you want the applet to draw a round and a square target at the same time; you would need two locks for two independent critical regions. The problem is that each object has only one lock. Creating separate classes for drawRoundTarget() and drawSquareTarget() could solve the problem, but it may not be convenient. A better way is to create new objects, and to use their locks to control the methods. This is done by modifying Listing 16.4 as follows:
. . . . . . Object RoundSync,SquareSync; /*new*/ public void start() { . . . . . . RoundSync=new Object(); /*new*/ SquareSync=new Object(); /*new*/ Thr1=new Thread(this); Thr1.start(); Thr2=new Thread(this); Thr2.start(); } void drawRoundTarget() { synchronized (RoundSync) /*new*/ { for(int r=0,i=10;i>-20;i-=20) . . . . . . } } void drawSquareTarget() { synchronized (SquareSync) /*new*/ { for(r=0,i=10;i>-20;i-=20) . . . . . . } } }
Two new Objects are created: RoundSync and SquareSync. The Objects don't actually do anything themselves, but their locks are used when drawing the targets. The instances of Object are obtained in the start method. synchronized (RoundSync) is put in front of the for loops in the drawRoundTarget() method and drawSquareTarget() is modified similarly. When a thread tries to execute the body of the target drawing methods, it first has to get a lock. drawRoundTarget() gets a lock from the object RoundSync and drawSquareTarget() gets a lock from SquareSync(). After these modifications have been made, the drawRoundTarget() and drawSquareTargets() methods do not block each other. The applet draws round and square targets at the same time. But it is not able to draw two round targets or two square targets at the same time. Figure 16.4 shows the results of the modifications.
Figure 16.4. A square and round target being drawn at the same time.
The dining philosophers problem is a classic concurrent programming problem. In the problem, a philosopher only does two things, think and eat. A philosopher thinks for a while, and then gets hungry. Then it eats until it gets full, and then starts thinking again. Each philosopher is so involved in its own thinking or eating that it's oblivious to anything else. The problem has five philosophers that are sitting at a table. Between neighboring philosophers there is a single chop stick. A philosopher must have both the stick on its right and the stick on its left in order for it to eat. Obviously, no two neighboring philosophers can be eating at the same time. The philosophers cannot disturb each other. If a philosopher wants to start eating and its neighbor has one of the sticks, the philosopher must wait until the stick becomes available. To solve the problem, an algorithm must be designed to let all of the philosophers eat.
A simple algorithm for the philosophers could be:
Think
Get right chopstick
Get left chopstick
Eat
Drop left chopstick
Drop right chopstick
Repeat
There is one very serious problem with this algorithm. Suppose that each philosopher picks up the right chopstick at the same time. When they try to get the left stick, it won't be there. The neighbor to the left has it. All of the philosophers starve to death with a chopstick in one hand and food on the table. This is known as a deadlock.
Deadlocks are always a danger in multithreaded environments. A deadlock has occurred because:
All deadlocks in any problem have the same reasons for deadlocking. Except, instead of waiting for chopsticks, they are waiting for some other resource or resources. If only one of the conditions can be broken, a deadlock will not occur.
The first condition is usually hard to break. In the previous algorithm this could done by allowing philosophers to share a chopstick, but that isn't really possible. Sometimes threads need exclusive use of a resource. Things like the instance of a class or a socket may require that only one thread may use it at once.
The second condition can sometimes be used to avoid deadlocks. If a philosopher was allowed to take a chopstick from its neighbor, there would not be a deadlock. However, if the philosophers keep taking sticks from each other, they may never get a chance to take a bite. Some problems can be solved by allowing resource stealing. It depends on the resource and the problem.
If the deadlock cannot be avoided with the other conditions, it should be avoided by breaking the third condition. The philosophers can avoid a deadlock if there is a special chopstick that they aren't allowed to hold while they are waiting for the second chopstick. They are allowed to eat with the special stick, but they can't just hold it. An algorithm should not be to strict, otherwise the resources may be under used. For example, an algorithm could be made that only allows one philosopher to eat at once. Obviously, there would be no deadlocks, but a philosopher may have to wait longer before it can eat.
There are many solutions to the dining philosophers problem. One solution follows.
One of the chopsticks is marked as gold, while the rest are wood. No philosopher is allowed to hold the gold stick without eating. This prevents the philosophers from deadlocking. The philosophers picks up one chopstick then the other. If the gold stick is picked up first, it is put down and the other stick is picked up. It is possible that in the time between putting down the gold chopstick and picking up the other chopstick, all the other philosophers will have eaten and moved the gold chopstick all the way around the table, so when the philosopher picks up the other chopstick, it too is the gold chopstick. If this happens, the philosopher starts over. The solution is as follows.
Think
Pick up right chopstick
If right chopstick is gold
Drop right chopstick
Pick up left chopstick
If left chopstick is gold
Start over
Pick up right chopstick
Else
Pick up left chopstick
Eat
Switch chopsticks in hands
Drop right chopstick
Drop left chopstick
Repeat
The chopsticks are switched when the philosopher puts down the chopsticks. This allows the philosophers equal chances to eat. Otherwise, the philosopher to the left of the gold chopstick would be disadvantaged.
The philosophers may interfere with each other when they try to pick up a chopstick. A critical region is used to ensure that one philosopher can pick up or put down a chopstick. If one philosopher is picking up or putting down a stick, its neighbor is not allowed to touch it. What if a philosopher enters the critical section to get a chopstick, but the chopstick is not there? The philosopher must wait until the stick returns. This is done by a special wait in the critical section. The philosopher releases its lock and waits in the critical section. This allows the other philosopher to enter the critical section to return the chopstick. After the chopstick is returned, the waiting philosopher is woken up. After awakening, the philosopher reacquires the lock and continues executing. At any one time there can be only one philosopher running in the critical section, but it is OK if another philosopher is also sleeping in the critical section.
Java has three wait() and two notify() methods that aid in synchronizing threads. The wait() methods cause the thread to pause in the critical region. While paused, the thread releases its lock. It must get the lock again before it starts executing again. The notify() methods wakes up threads that have called wait(). Calling notify() when no wait() has been called has no effect. The methods shown below are in the Object class and can only be called in a synchronized block or method.
The dining philosophers applet uses four classes: the ones shown in Listings 16.5, 16.6, 16.7, and 16.8. The first class, DiningPhilosophers, extends the Applet class. The structure of this class is similar to the first example. Philosopher threads are created when the applet is initialized. They are suspended if the user leaves the page, and resumed if the user returns. However, unlike the first example, no threads are created in this class. The dining philosophers example can be seen in Figure 16.5.
Figure 16.5. The dining philosophers example.
Listing 16.5. The class DiningPhilosophers.
public class DiningPhilosophers extends Applet { final int numPhils=5; Image offImage; /* off screen image */ Graphics offGraphics; /* Graphics for offImage */ Philosopher Phil[] = new Philosopher[numPhils]; Chopstick Stick[] = new Chopstick[numPhils]; ScenePainter painter; public void init() { int i; offImage=createImage(400,300); offGraphics=offImage.getGraphics(); painter=new ScenePainter(offGraphics,this,numPhils); for(i=0;i<numPhils;i++) Stick[i]=new Chopstick (i==0 ? Chopstick.gold : Chopstick.wood, painter,i); for(i=0;i<numPhils;i++) Phil[i]= new Philosopher(Stick[i],Stick[(i+1)%numPhils], painter,i); } public void start() { int i; for(i=0;i<numPhils;i++) Phil[i].resume(); } public void stop() { int i; for(i=0;i<numPhils;i++) Phil[i].suspend(); } public void destroy() { int i; for(i=0;i<numPhils;i++) { Phil[i].resume(); Phil[i].stop(); } } public void paint(Graphics g) { g.drawImage(offImage,0,0,null); } public void update(Graphics Dijkstra) { paint(Dijkstra); } }
The class DiningPhilosophers has six methods and is similar to the InfiniteThreadExample.
Each philosopher at the table is its own thread. The thread is created in the class Philosopher by extending Thread. The methods in the class control the philosopher's life of thinking and eating. Initially, the philosopher is thinking. After some time, the philosopher picks up the two chopsticks next to it and starts eating. It calls methods in the Chopstick class (see Listing 16.7) to get the chopsticks. The philosopher also paints its state by calling methods in the ScenePainter class (see Listing 16.8).
Listing 16.6. The class Philosopher.
class Philosopher extends Thread { final int rT=10, ThinkTime=5000, EatTime=3000; Chopstick rightStick,leftStick; ScenePainter painter; int rightHand,leftHand; int myNum; public Philosopher(Chopstick right, Chopstick left, ScenePainter p, int n) { painter=p; myNum=n; rightStick=right; leftStick=left; start(); } public void run() { for(;;) { think(); PickUpSticks(); eat(); PutDownSticks(); } } void think() { painter.drawThink(myNum); try {sleep((int)(ThinkTime*Math.random()));} catch (InterruptedException e) {} painter.drawThink(myNum); } void PickUpSticks() { for(boolean gotem=false;!gotem;) { painter.drawFirstGrab(myNum); rightHand=rightStick.grabStick(); painter.drawSecondGrab(myNum); if(rightHand==Chopstick.gold) { painter.drawGoldGrab(myNum); rightStick.dropStick(rightHand); leftHand=leftStick.grabStick(); if(leftHand==Chopstick.gold) { leftStick.dropStick(leftHand); continue; /* gold stick went around table */ } rightHand=rightStick.grabStick(); painter.drawGoldGrab(myNum); } else leftHand=leftStick.grabStick(); painter.drawSecondGrab(myNum); painter.drawFirstGrab(myNum); gotem=true; } } void eat() { painter.drawEat(myNum); try {sleep((int)(EatTime*Math.random()));} catch (InterruptedException e) {} painter.drawEat(myNum); } void PutDownSticks() {/* swap sticks and put them down */ rightStick.dropStick(leftHand); leftStick.dropStick(rightHand); } }
The class Philosopher is used to start the threads for the dining philosophers applet. The class has the following five methods:
All of the synchronization is done in the Chopstick class. There is one instance of this class for each chopstick on the table. The class is used by the philosophers when they want to pick up or return a chopstick. The three states of the chopstick are represented by the variable stickHolder: noStick means the chopstick is gone, wood means this is the wooden stick, and gold means this is the golden stick. stickHolder is an instance variable. More than one philosopher may be trying to get/drop it at once, so there is a danger of data corruption. The methods of this class are synchronized to ensure that stickHolder does not get corrupted (see Listing 16.7).
Listing 16.7. The class Chopstick.
class Chopstick extends Object { final static int noStick=0; final static int wood=1; final static int gold=2; ScenePainter painter; int stickHolder; int myNum; public Chopstick(int stick, ScenePainter p, int n) { painter=p; myNum=n; dropStick(stick); } synchronized int grabStick() { int Thuy; if(stickHolder==noStick) try {wait();} catch (InterruptedException e) {} painter.drawStick(myNum,stickHolder); Thuy=stickHolder; stickHolder=noStick; return Thuy; } synchronized void dropStick(int stick) { stickHolder=stick; painter.drawStick(myNum,stickHolder); notify(); } }
The class Chopstick is used to synchronize the threads in the dining philosophers applet. The class has the following two methods:
The class ScenePainter is used by the philosophers to paint their state. If a philosopher starts eating, puts down a stick, or does anything else, it calls methods in this class. The states of philosophers (for example, eating or waiting for a stick) are represented by different shapes. When a philosopher starts thinking, it calls drawThink(myNum) to draw the philosopher in its thinking state. Then, when it is done thinking, it calls drawThink(myNum) again to erase the philosopher. All of the methods in this class are called in pairs. The first call draws a state and the second erases it.
Listing 16.8. The class ScenePainter.
class ScenePainter extends Object { int sX1[], sY1[], sX2[], sY2[], pX[], pY[]; final int xOffset=150, yOffset=150, Tscale=70; final int rg=2, rEating=15, rThinking=8, rFirst=7; final int rSecond=3, rGold=5; Graphics G; Component C; public ScenePainter(Graphics g, Component c, int numPhils) { int i; pX = new int[numPhils]; pY = new int[numPhils]; sX1 = new int[numPhils]; sY1 = new int[numPhils]; sX2 = new int[numPhils]; sY2 = new int[numPhils]; double arc=Math.PI/numPhils; G=g; C=c; for(i=0;i<numPhils;i++) { pX[i]= (int)(xOffset+ Tscale*Math.cos(i*2*arc)); pY[i]= (int)(yOffset+ Tscale*Math.sin(i*2*arc)); sX1[i]=(int)(xOffset+ Tscale*Math.cos(i*2*arc+arc)); sY1[i]=(int)(yOffset+ Tscale*Math.sin(i*2*arc+arc)); sX2[i]=(int)(xOffset+.7*Tscale*Math.cos(i*2*arc+arc)); sY2[i]=(int)(yOffset+.7*Tscale*Math.sin(i*2*arc+arc)); } G.setColor(Color.white); G.setXORMode(Color.blue); } void drawStick(int num, int stick) { G.drawLine(sX1[num],sY1[num],sX2[num],sY2[num]); if(stick==Chopstick.gold) G.fillOval(sX1[num]-rg,sY1[num]-rg,rg+rg,rg+rg); C.repaint(); } void drawEat(int num) { fillCircle(num,rEating); } void drawThink(int num) { fillCircle(num,rThinking); } void drawFirstGrab(int num) { fillSquare(num,rFirst); }
The class ScenePainter is used to draw the state of all the philosophers in the dining philosophers applet.
Threads are the life in an applet. Some threads are created by the system, but others are started in the applet. Threads can be started to remove some of the burden from a system thread, or to start its own line of execution.
This chapter contained several Applets that started threads. The examples showed specific uses of threads that can be expanded to general use. Most threading problems can be solved by using slight variations of the first two examples. These examples have threads that do not communicate with other threads. If threads need to communicate with each other, care must be taken to avoid data corruption. The third example shows data corruption and the steps that must be taken to avoid it. The forth example shows how two threads can be synchronized so that they can do things in the right order. The final example, the dining philosophers, shows many of the subtle problems in a multithreaded environment. If threads in an applet compete for common resources, a deadlock can occur unless care is taken to avoid it. The examples in this chapter are just that, examples. The reader is encouraged to experiment with them, tweak some of the parameters, add more threads, but most of all, have fun.