In this lab, you will work with threads.
In this part, you will simulate the operation of a stock-retrieval application. You will write a class that extends Thread and then create a couple instances and start the threads.
Create a new Java project named Lab02.
Create a class named StockTicker in the part1 package. The new class should have a main method.
Within the new class, but outside of the main method, write the skeleton of a private static member inner class named StockTickerThread that extends java.lang.Thread:
private static class StockTickerThread extends Thread
{
}
Complete the inner class as follows:
Define a String field named symbol that will hold the stock symbol.
Use the Source - Generate Constructor Using Fields menu to write a one-argument constructor that accepts a stock-symbol String and assigns it to the field.
Use the Source - Override/Implement Methods menu to override the run method. You will complete it in the next step. You can delete the generated call to the superclass.
Complete the overridden run method:
Create an instance of the java.util.Random class.
Code an infinite loop.
Within the loop, use the java.util.Random object to obtain a random int that's in the range of 0 to 5000 (use the nextInt method).
Use the random integer as the number of milliseconds to sleep to simulate contacting a remote stock-ticker service.
Retrieve another random integer, this one between 0 and 100 -- this will simulate the stock value.
Print the symbol and value to the console.
Complete the main method as follows:
Create two instances of the StockTickerThread, using "IBM" and "MSFT" (or whatever you want) for the symbols.
Start a thread on each instance.
Run the program. You should see simulated stock quotes from the two symbols.
To stop the program, press the red square icon in the Console's titlebar.
Now let's make terminating the program more clean. Follow these steps:
Edit the StockTickerThread class and define a boolean field named cancel and initialize it to false.
Write a method named stopTicker that sets the cancel field to true.
Modify the "forever" loop so that it exits if cancel is true.
Edit main, and after starting the threads, print a message telling the user they can press Enter to terminate.
Then use System.in.read() to wait for the user to press Enter, at which point your program should call stopTicker on both thread objects.
Note that System.in.read() waits for the user to press Enter - we don't really care here what else they type. Therefore, it's sufficient to just call System.in.read() without examining anything returned from it.
Run and test. Can you stop the threads by pressing Enter into the Console?
In the last part, you created threads by subclassing the java.lang.Thread class. However, you cannot use that technique if the class that comprises the thread already has a superclass -- in Java, a class can only have one superclass. If that's the case, then the class can implement the java.lang.Runnable interface instead.
In this part, you will create a Java applet that use a so-called background thread. Since applets must extend either javax.swing.JApplet or java.awt.applet.Applet, your applet will need to implement Runnable so it can support the thread.
The background thread will perform an animation -- it will move a text string around the applet's rectangle, "bouncing" the string off of the rectangle's edges.
Create a class named AnimatorApplet in the part2 package that extends javax.swing.JApplet and implements java.lang.Runnable. This class should not have a main method, since applets are invoked automatically by the container in which they run (typically, a Web browser or an applet-viewer).
Override the following methods from the Applet class. You don't need to add any code yet.
Implement the run method from the Runnable interface. You will complete it in a later step.
First, let's work on controlling the background animation thread. Follow these steps:
Define a field in the class that will contain the thread's reference:
private Thread me;
In the start method, write code that creates a thread object and stores its reference in the me variable, then starts the new thread. Hint: You can use the Java this keyword to refer to the the object (the applet) that implements Runnable.
In the stop method, set the me field to null.
In the run method, code a while loop that runs as long as the current thread is me:
while (Thread.currentThread() == me)
{
}
This may seem convoluted, but it's the conventional way to control a background thread in an applet.
Now let's work on the animation code. Follow these steps:
Define the following fields:
private int x; // x position
private int y; // y position
private int dx = 5; // x speed
private int dy = 5; // y speed
private boolean firstTime;
Write a private method that you will call from the thread to actually draw the text. Note that it uses a XOR technique -- XOR drawing lets you erase an object by drawing again in the same place. In other words, if you call this method twice, the first time it will draw the text and second time it will erase it.
private void draw(Graphics g)
{
g.setColor(Color.black);
g.setXORMode(Color.white);
g.drawString("Hello", x, y);
}
Write a another method that you will call from the thread to calculate a new position for the text -- you will complete this method in the next step.
private void move()
{
// retrieve applet's rectangle width and height
Dimension d = getSize();
}
To complete the move method, follow these steps:
Calculate a new position (x,y) by adding the dx and dy fields to x and y respectively.
We want the text to "bounce" off of the applet's edges, so we need to reverse the speed (dx,dy) when we detect that the text has moved beyond an edge. To make this a little more interesting visually, you will randomize the speed on each bounce.
Start by looking for the case when x is less than zero:
if (x<0)
{
}
If x is less than zero, set dx to a random number between 1 and 5. You can use the java.util.Random class and the Math.abs function.
Next, look for the case where x is greater than d.width. If that's true, then set dx to a random number between -1 and -5.
Repeat the two tests for y, setting the dy variable to an appropriate positive or negative random value.
Now you can complete the run method, which comprises the thread. Follow these steps:
Inside of the loop, sleep the thread for 100 milliseconds.
Retrieve a Graphics object that you can use to draw to the applet's surface:
Graphics g = getGraphics();
Now it's time to erase the text from the last time the thread ran. But if this is the first time through the loop, there's nothing to erase. So check that case, then call your draw method:
if (firstTime == true)
firstTime = false;
else
draw(g);
In any case, call your move method, followed by a call to draw to draw the text at its new position.
Add code to the start method to set firstTime to true before starting the thread.
Add code to the stop method to set firstTime to false.
To run your program in RAD's applet viewer, right-click on AnimatorApplet.java in the Package Explorer, then choose Run As - Java Applet.
The applet viewer will instantiate your applet and call its start method -- you should see your animated text.
You can use the applet viewer's Applet - Stop and Applet - Start menu items to control your thread's execution.
In this part, you will demonstrate how using multiple threads can lead to a race condition and how synchronization can solve such problems.
Create a class named TestSynch in the part3 package with a main method.
Inside the TestSynch class, write a private static inner class named MyCounter. Complete the inner class as follows:
Define a private field of type int named count.
Write a method named adjustCount that accepts no arguments and returns void. You will complete the method in the next step.
The adjustCount method is where the race condition will occur. Complete it as follows:
Set the count field to 12. Since count is a field, it will be shared among all of the threads that call this method -- that will lead to the race condition.
Sleep the current thread for a random interval between 0 and 1000 milliseconds.
Increment the count field.
Sleep again for a random interval between 0 and 1000 milliseconds.
Test the value of count -- if it's not 13, print a message to the console with the current thread's name and the value of count You can retrieve the thread name using an expression like Thread.currentThread().getName().
Create another private static inner class within TestSynch named MyThread that extends java.lang.Thread. Complete it as follows:
Define a field named obj of type MyCounter.
Write a two-argument constructor that accepts a String and a MyCounter object. The constructor should call the superclass constructor with the String (thread name) and assign the MyCounter reference to the field.
Override the run method and write an infinite loop. Inside the infinite loop, call the adjustCount method on the MyCounter reference stored in the field.
Complete the main method in TestSynch as follows:
Create a MyCounter object.
Create three MyThread objects, passing Strings of "Thread1", "Thread2" and "Thread3" and the reference to the MyCounter object.
Start each of the threads.
To run the program, use the command-prompt technique you used in the first part of the lab. The command-line to run the program is:
java part3.TestSynch
If there was only one thread calling adjustCount, the value of count would always be 13 -- why is that not the case here?
Edit the adjustCount method definition and add the synchronized keyword, then test again. There should be no output. How does the synchronized keyword fix the problem?
One handy use of threads is to start a lengthy process in a separate thread and then have the "main" thread go about its business doing other things. Then when the "main" thread needs the results of the background operation, it can join with the thread, which causes the "main" thread to wait if the background thread hasn't yet finished.
In this part of lab, you will read a text file as a "lengthy process" using a separate thread.
Create a class named TestJoin in the part4 package with a main method.
Inside of the TestJoin class, write a private static inner class named ReadFileThread that extends Thread. Complete the inner class as follows:
Define two private String fields -- one to hold a filename and the other to hold the results of reading a text file.
Write a one-argument constructor that accepts the filename and stores it in the field.
Write a "get" method that returns the String field containing the text read from the file.
Override the run method and complete it as described in the next step.
Complete the thread's run method as follows:
Create a StringBuffer that you will use to accumulate the characters from the file.
Open the text file:
FileReader fr = new FileReader(fname);
Note that you will need to use a try-catch block to handle exceptions.
Define a int to hold each character read from the file, then loop through, reading a character at a time from the file:
int ch = fr.read();
while (ch != -1)
{
ch = fr.read();
}
Inside the loop, above the read, sleep the thread for 10 milliseconds to make this a lengthy process.
Append each character read into the StringBuffer. You will need to cast from int to char.
Outside of the loop, close the FileReader and then convert the StringBuffer object into a String and store its reference in the field you defined earlier.
Complete the main method as follows:
Create an instance of the ReadFile thread, passing the program's own source file name as the text file: part4/TestJoin.java.
Set the thread's priority to the minimum value.
Start the thread.
In a real program, the main thread would now do something useful, in parallel with the background thread. In this lab, just print out 50 'a' characters, sleeping 100 milliseconds between each character. Then print a "Waiting" message.
Join with the background thread.
Retrieve the result String from the thread object and print it to the console.
Run your program from the command line as before. Can you see the parallel activity?
Play around with the sleep times and the count of 'a' characters and see if you can get the thread to complete before main and vice versa.
In this part, you will practice some of the techniques you learned in this lab.
In this part, you will add a background thread to a GUI application. The background thread will simulate a lengthy printing process.
Import the {Lab Installation Directory}/starters/Lab04/reinforcement folder into the Lab04 project.
Examine and run this class to see what it does (not much).
Your job is to add a simulated printing process when the user clicks the Print button and to interrupt and stop the print if the user presses the Cancel button. Here are some hints:
We recommend that you use a static member inner class for the thread as in previous parts.
You can use anonymous inner classes to respond to the button clicks as in previous parts.
You can simulate printing 10 pages by looping and sleeping for 1000 milliseconds for each "page". You should print a message to the console indicating as each page "prints" and a message when the printing is finish.
For extra credit, only start a new print job when the previous is finished. Display a message box if the user asks to print while a previous print is in process.