[ Team LiB ] Previous Section Next Section

4.6 Exercises

Exercise 4-1. Write a Java program that takes a list of filenames on the command line and prints out the number of lines in each file. The program should create one thread for each file and use these threads to count the lines in all the files at the same time. Use java.io.LineNumberReader to help you count lines. You'll probably want to define a LineCounter class that extends Thread or implements Runnable. Now write a variant of your program that uses your LineCounter class to read the files sequentially, rather than at the same time. Compare the performance of the multithreaded and single-threaded programs, using System.currentTimeMills( ) to determine elapsed time. Compare the performance of the two programs for two, five, and ten files.

Exercise 4-2. The ThreadSafeIntList class of Example 4-2 is a simplified version of Example 2-7 with synchronized methods. Another approach to synchronizing data structures is to write a wrapper class with synchronized methods that delegate to an instance of the original class. See, for example, the synchronizedList( ) and related methods of java.util.Collections. Modify the original IntList class (Example 2-7) to use this approach. Add a static factory method named synchronizedIntList( ) that takes an IntList as its argument and returns an IntList object that has thread-safe behavior. You'll need to implement the wrapper class as a subclass of IntList, overriding its public methods to make them synchronized and make them delegate to the specified IntList instance.

Exercise 4-3. Example 4-4 demonstrates how deadlock can occur when two threads each attempt to obtain a lock held by the other. Modify the example to create deadlock among three threads, where each thread is trying to acquire a lock held by one of the other threads.

Exercise 4-4. Example 4-4 uses the synchronized statement to demonstrate deadlock. Write a similar program that causes two threads to deadlock, but use synchronized methods instead of the synchronized statement. This sort of deadlock is a little more subtle and harder to detect.

Exercise 4-5. Example 4-6 shows an implementation of the Java 1.3 java.util.Timer API. Java 1.2 introduced another Timer class, the javax.swing.Timer class, which has a similar purpose but a different API. It invokes the actionPerformed( ) method of any number of registered ActionListener objects one or more times after a specified delay and at a specified interval. Read the documentation for this Timer class, then create your own implementation of it. If you've read Chapter 11, you know that the methods of event listeners, such as the actionPerformed( ) method, are supposed to be invoked only by the event dispatch thread. Therefore, your implementation of the Timer class should not invoke actionPerformed( ) directly, but should instead use java.awt.EventQueue.invokeLater( ) or javax.swing.SwingUtilities.invokeLater( ) to tell the event dispatch thread to invoke the method.

Exercise 4-6. Once you have read Chapter 5, you may want to come back to this chapter to try this exercise. The Server class of Chapter 5 is a multithreaded, multiservice network server. It demonstrates important networking techniques, but it also makes heavy use of threads. One particular feature of this program is that it creates a ThreadGroup to contain all the threads it creates. Modify Server so that in addition to creating this one master thread group, it also creates a nested thread group for each of the individual services it provides. Place the thread for each individual client connection within the thread group for its service. Also, modify the program so that each service can have a thread priority specified and use this priority value when creating connection threads. You will probably want to store the thread group and priority for each service as fields of the nested Listener class.

    [ Team LiB ] Previous Section Next Section