4.8. ThreadsJava makes it easy to define and work with multiple threads of execution within a program. java.lang.Thread is the fundamental thread class in the Java API. There are two ways to define a thread. One is to subclass Thread, override the run() method, and then instantiate your Thread subclass. The other is to define a class that implements the Runnable method (i.e., define a run() method) and then pass an instance of this Runnable object to the Thread() constructor. In either case, the result is a Thread object, where the run() method is the body of the thread. When you call the start() method of the Thread object, the interpreter creates a new thread to execute the run() method. This new thread continues to run until the run() method exits, at which point it ceases to exist. Meanwhile, the original thread continues running itself, starting with the statement following the start() method. The following code demonstrates: final List list; // Some long unsorted list of objects; initialized elsewhere /** A Thread class for sorting a List in the background */ class BackgroundSorter extends Thread { List l; public BackgroundSorter(List l) { this.l = l; } // Constructor public void run() { Collections.sort(l); } // Thread body } // Create a BackgroundSorter thread Thread sorter = new BackgroundSorter(list); // Start it running; the new thread runs the run() method above, while // the original thread continues with whatever statement comes next. sorter.start(); // Here's another way to define a similar thread Thread t = new Thread(new Runnable() { // Create a new thread public void run() { Collections.sort(list); } // to sort the list of objects. }); t.start(); // Start it running
Threads can run at different priority levels. A thread at a given priority level does not run unless there are no higher-priority threads waiting to run. Here is some code you can use when working with thread priorities: // Set a thread t to lower-than-normal priority t.setPriority(Thread.NORM_PRIORITY-1); // Set a thread to lower priority than the current thread t.setPriority(Thread.currentThread().getPriority() - 1); // Threads that don't pause for I/O should explicitly yield the CPU // to give other threads with the same priority a chance to run. Thread t = new Thread(new Runnable() { public void run() { for(int i = 0; i < data.length; i++) { // Loop through a bunch of data process(data[i]); // Process it if ((i % 10) == 0) // But after every 10 iterations, Thread.yield(); // pause to let other threads run. } } });
Often, threads are used to perform some kind of repetitive task at a fixed interval. This is particularly true when doing graphical programming that involves animation or similar effects: public class Clock extends Thread { java.text.DateFormat f = // How to format the time for this locale java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM); boolean keepRunning = true; public Clock() { // The constructor setDaemon(true); // Daemon thread: interpreter can exit while it runs start(); // This thread starts itself } public void run() { // The body of the thread while(keepRunning) { // This thread runs until asked to stop String time = f.format(new java.util.Date()); // Current time System.out.println(time); // Print the time try { Thread.sleep(1000); } // Wait 1000 milliseconds catch (InterruptedException e) {} // Ignore this exception } } // Ask the thread to stop running public void pleaseStop() { keepRunning = false; } }
Notice the pleaseStop() method in the previous example. You can forcefully terminate a thread by calling its stop() method, but this method has been deprecated because a thread that is forcefully stopped can leave objects it is manipulating in an inconsistent state. If you need a thread that can be stopped, you should define a method such as pleaseStop() that stops the thread in a controlled way. In Java 1.3, the java.util.Timer and java.util.TimerTask classes make it even easier to run repetitive tasks. Here is some code that behaves much like the previous Clock class: import java.util.*; // How to format the time for this locale final java.text.DateFormat timeFmt = java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM); // Define the time-display task TimerTask displayTime = new TimerTask() { public void run() { System.out.println(timeFmt.format(new Date())); } }; // Create a timer object to run the task (and possibly others) Timer timer = new Timer(); // Now schedule that task to be run every 1000 milliseconds, starting now Timer.schedule(displayTime, 0, 1000); // To stop the time-display task displayTime.cancel();
Sometimes one thread needs to stop and wait for another thread to complete. You can accomplish this with the join() method: List list; // A long list of objects to be sorted; initialized elsewhere // Define a thread to sort the list: lower its priority, so it only runs // when the current thread is waiting for I/O, and then start it running. Thread sorter = new BackgroundSorter(list); // Defined earlier sorter.setPriority(Thread.currentThread.getPriority()-1); // Lower priority sorter.start(); // Start sorting // Meanwhile, in this original thread, read data from a file byte[] data = readData(); // Method defined elsewhere // Before we can proceed, we need the list to be fully sorted, so // we've got to wait for the sorter thread to exit, if it hasn't already. sorter.join();
When using multiple threads, you must be very careful if you allow more than one thread to access the same data structure. Consider what would happen if one thread was trying to loop through the elements of a List while another thread was sorting those elements. Preventing this problem is called threadsynchronization and is one of the central problems of multithreaded computing. The basic technique for preventing two threads from accessing the same object at the same time is to require a thread to obtain a lock on the object before the thread can modify it. While any one thread holds the lock, another thread that requests the lock has to wait until the first thread is done and releases the lock. Every Java object has the fundamental ability to provide such a locking capability. The easiest way to keep objects thread-safe is to declare any sensitive methods synchronized. A thread must obtain a lock on an object before it can execute any of its synchronized methods, which means that no other thread can execute any other synchronized method at the same time. (If a static method is declared synchronized, the thread must obtain a lock on the class, and this works in the same manner.) To do finer-grained locking, you can specify synchronized blocks of code that hold a lock on a specified object for a short time: // This method swaps two array elements in a synchronized block public static void swap(Object[] array, int index1, int index2) { synchronized(array) { Object tmp = array[index1]; array[index1] = array[index2]; array[index2] = tmp; } } // The Collection, Set, List, and Map implementations in java.util do // not have synchronized methods (except for the legacy implementations // Vector and Hashtable). When working with multiple threads, you can // obtain synchronized wrapper objects. List synclist = Collections.synchronizedList(list); Map syncmap = Collections.synchronizedMap(map);
When you are synchronizing threads, you must be careful to avoid deadlock, which occurs when two threads end up waiting for each other to release a lock they need. Since neither can proceed, neither one can release the lock it holds, and they both stop running: // When two threads try to lock two objects, deadlock can occur unless // they always request the locks in the same order. final Object resource1 = new Object(); // Here are two objects to lock final Object resource2 = new Object(); Thread t1 = new Thread(new Runnable() { // Locks resource1 then resource2 public void run() { synchronized(resource1) { synchronized(resource2) { compute(); } } } }); Thread t2 = new Thread(new Runnable() { // Locks resource2 then resource1 public void run() { synchronized(resource2) { synchronized(resource1) { compute(); } } } }); t1.start(); // Locks resource1 t2.start(); // Locks resource2 and now neither thread can progress!
Sometimes a thread needs to stop running and wait until some kind of event occurs, at which point it is told to continue running. This is done with the wait() and notify() methods. These aren't methods of the Thread class, however; they are methods of Object. Just as every Java object has a lock associated with it, every object can maintain a list of waiting threads. When a thread calls the wait() method of an object, it is added to the list of waiting threads for that object and stops running. When another thread calls the notify() method of the same object, the object wakes up one of the waiting threads and allows it to continue running: /** * A queue. One thread calls push() to put an object on the queue. * Another calls pop() to get an object off the queue. If there is no * data, pop() waits until there is some, using wait()/notify(). * wait() and notify() must be used within a synchronized method or * block. */ import java.util.*; public class Queue { LinkedList q = new LinkedList(); // Where objects are stored public synchronized void push(Object o) { q.add(o); // Append the object to the end of the list this.notify(); // Tell waiting threads that data is ready } public synchronized Object pop() { while(q.size() == 0) { try { this.wait(); } catch (InterruptedException e) { /* Ignore this exception */ } } return q.remove(0); } } Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|