Introduction to Programming Using Java Version 6.0 phần 9 potx

76 280 0
Introduction to Programming Using Java Version 6.0 phần 9 potx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

CHAPTER 12. THREADS AND MULTIPROCESSING 595 definition of an instance method is p retty much equivalent to putting the body of the method in a synchronized statement of the form synchronized(this) { }. It is also possible to have synchronized static methods; a synchronized static method is synchronized on the special class object that represents the class containing the static method. The real rule of synchronization in Java is this: Two threads cannot be synchronized on the same obje ct at the same time; that is, they cannot simultaneously be executing co de segments that are synchronized on that object. If one thread is synchronized on an object, and a second thread tries to synchronize on the same object, the second thread is forced to wait until the first thread has finished with the object. This is implemented using something called a synchronization lock. Every object has a synchronization lo ck, and that lock can be “held” by only one thread at a time. To enter a synchronized statement or synchronized method, a thr ead must obtain the associated object’s lock. If th e lock is available, then the thread obtains the lock and immed iately begins executing the synchronized code. It releases the lock after it finishes executing the synchronized code. If Thread A tries to obtain a lock that is already held by Thread B, then Thread A has to wait until Thread B releases the lock. In fact, Thread A will go to sleep, and will not be awoken until the lock becomes available. ∗ ∗ ∗ As a simple example of shared resources, we return to the prime-counting problem. I n this case, instead of having every thread perform exactly the same task, we’ll so some real parallel processing. The program w ill count the prime numbers in a given range of integers, and it will do so by dividing the work up among several threads. Each thread will be assigned a part of the full range of integers, and it will count the primes in its assigned part. At the end of its computation, the thread has to add its count to the overall total of primes in the entire range. The variable that represents the total is shared by all the threads, since each thread h as to add a number to the total. If each thread just says total = total + count; then there is a (small) chance that two threads will try to do this at the same time and that the final total will be wrong. To prevent this race condition, access to total has to be synchronized. My program uses a synchronized method to add the counts to the total. This method is called once by each thread: synchronized private static void addToTotal(int x) { total = total + x; System.out.println(total + " primes found so far."); } The source code for the program can be found in ThreadTest2.java. This program counts the primes in the range 3000001 to 6000000. (The numbers are rather arbitrary.) The main() routine in this program creates between 1 and 5 threads and assigns part of the job to each thread. It waits for all the threads to finish, using the join() method as described above. It then reports the total number of primes foun d, along with the elapsed time. Note that join() is required here, since it doesn’t make sense to report the number of primes until all of the threads have finished. If you run the program on a multiprocessor computer, it should take less time for the program to run when you use more than one thread. You can compile and run the pr ogram or try the equivalent applet in the on-line version of this section. ∗ ∗ ∗ CHAPTER 12. THREADS AND MULTIPROCESSING 596 Synchronization can help to prevent race cond itions, but it introduces the possibility of another type of error, deadlock. A deadlock occurs when a thread waits forever for a resource that it will never get. In the kitchen, a deadlock might occur if two very simple-minded cooks both want to measure a cup of milk at the same time. The first cook grabs the measuring cup, while the second cook grabs the milk. The first cook needs the milk, but can’t find it because the second cook has it. The second cook needs the measuring cu p, but can’t find it because the first cook has it. Neither cook can continue and nothing more gets done. This is deadlock. Exactly the same thing can happen in a program, for example if th ere are two threads (like the two cooks) both of which need to obtain locks on the same two objects (like the milk and the measuring cup) before they can proceed. Deadlocks can easily occur, unless great care is taken to avoid them. 12.1.4 Volatile Variables Synchronization is only one way of controlling communication among threads. We will cover several other techniques later in the chapter. For n ow, we finish this section with one more communication technique: volatile variables. In general, threads communicate by sharing variables and accessing those variables in syn- chronized methods or s y nchronized s tatements. However, synchronization is fairly expensive computationally, and excessive u s e of it s hould be avoided. So in some cases, it can make sense for threads to refer to shared variables without synchronizing their access to those variables. However, a subtle problem arises when the value of a shared variable is set in one thread and used in another. Because of the way that threads are implemented in Java, the second thread might not see the changed value of the variable immediately. That is, it is possible that a thread will continue to see the old value of the shared variable for some time after the value of the variable has been changed by another thread. This is because threads are allowed to cache shared data. That is, each thread can keep its own local copy of the shared data. When one thread changes the value of a shared variable, the local copies in the caches of other threads are not immediately changed, so the other threads can continue to see the old value, at least briefly. When a synchronized method or statement is entered, threads are forced to update their caches to the most current values of the variables in the cache. So, using shared variables in synchronized code is always safe. It is possible to use a shared variable safely outside of synchronized code, but in that case, the variable must be declared to be volatile. The volatile keyword is a modifier that can be added to a variable declaration, as in private volatile int count; If a variable is declared to be volatile, no thread will keep a local copy of th at variable in its cache. Instead, the thread will always use the official, main copy of the variable. This means that any change that is made to the variable will immediately be visible to all threads. This makes it safe f or threads to refer to volatile shared variables even outside of synchronized co de. Access to volatile variables is less efficient than access to non-volatile variables, but more efficient than using synchronization. (Remember, though, that synchronization is still the only way to prevent race conditions.) When the volatile modifier is applied to an object variable, only the variable itself is declared to be volatile, not the contents of the object that the variable points to. For this CHAPTER 12. THREADS AND MULTIPROCESSING 597 reason, volatile is used mostly for variables of simple types such as primitive types and enumerated types. A typical example of using volatile variables is to send a signal from one thread to another that tells the second thread to terminate. The two threads would share a variable volatile boolean terminate = false; The run method of the second thread would check the value of terminate frequently, and it would end when the value of terminate b ecomes true: public void run() { while ( terminate == false ) { . . // Do some work. . } } This thread will run until some other thread sets the value of terminate to true. Something like this is really the only clean way for one thread to cause another thread to die. (By the way, you might be wondering why thr eads should use local data caches in the fir s t place, since it seems to complicate things unnecessarily. Caching is allowed because of the structure of multiprocessing computers. In many multiprocessing computers, each processor has some local memory that is directly connected to the processor. A thread’s cache can be stored in the local memory of the processor on which the thread is running. Access to this local memory is much faster than access to other memory, so it is more efficient for a thread to use a local copy of a shared variable rather than some “master copy” that is stored in non-lo cal memory.) 12.2 Programming with Threads Threads introduce new complexity into programming, but they are an important tool and (online) will only become more essential in the future. So, every programmer should know some of the fundamental design patterns that are used with threads. In this section, we will look at some basic techniques, with more to come as the chapter progresses. 12.2.1 Threads Versus Timers One of the most basic uses of threads is to perform some period task at set intervals. In fact, this is so basic that there is a specialized class f or performing this task—and you’ve already worked with it. The Timer class, in package javax.swing, can generate a sequence of ActionEvents separated by a specified time interval. Timers were introduced in Section 6.5, where they were used to implement animations. Behind the scenes, a Timer uses a th read. The thread sleeps most of the time, but it wakes up periodically to generate the events associated with the timer. Before timers were introduced, threads had to be used directly to implement a similar functionality. In a typical use of a timer for animation, each event from the timer causes a new frame of the animation to be computed and displayed. In the respons e to the event, it is only necessary to update some state variables and to repaint the display to reflect the changes. A Timer to do that every thirty milliseconds might be created like this: CHAPTER 12. THREADS AND MULTIPROCESSING 598 Timer timer = new Timer( 30, new ActionListener() { public void actionPerformed(ActionEvent evt) { updateForNextFrame(); display.repaint(); } } ); timer.start(); Suppose that we wanted to do the same thing with a th read. The run() method of the thread would h ave to execute a loop in which the thread sleeps for 30 milliseconds, then wakes up to do the updating and repainting. This could be implemented in a nested class as follows using the method Thread.sleep() that was discussed in Subsection 12.1.2: private class Animator extends Thread { public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { } updateForNextFrame(); display.repaint(); } } } To run the animation, you would create an object belonging to this class and call its start() method. As it stands, there would be no way to stop the animation on ce it is started. One way to make it possible to stop the animation would be to end the loop when a volatile boolean variable, terminate, becomes true, as discussed in Subsection 12.1.4. Since thread objects can only be executed once, in order to restart the animation after it has been stopped, it would be necessary to create a n ew thread. In the next section, we’ll see some more versatile techniques for controlling threads. There is a su btle difference between using threads and using timers for animation. The thread that is used by a Swing T imer does nothing but generate events. The event-handling co de that is executed in response to those events is actually executed in the Swing event- handling thread, which also handles repainting of components and responses to user actions. This is important because the Swing GUI is not thread-safe. That is, it does not use synchro- nization to avoid race conditions among threads trying to access GUI components and their state variables. As long as everything is done in the Swing event thread, there is no prob- lem. A problem can arise when another thread manipulates components or the variables that they use. In the Animator example given above, this could happen when the thread calls the updateForNextFrame() method. The variables that are modified in updateForNextFrame() would also be used by the paintComponent() method that draws the frame. There is a race condition here: If these two methods are being executed simultaneously, there is a possibility that paintComponent() will use inconsistent variable values—some appropriate f or the new frame, some for the previous frame. One solution to this problem would be to declare both paintComponent() and updateForNextFrame() to be synchronized methods. The real solution in this case is to use a timer rather than a thread. In practice, race conditions are not likely to be an issue for CHAPTER 12. THREADS AND MULTIPROCESSING 599 simple animations, even if they are implemented using threads. But it can become a real issue when threads are used for more complex tasks. I should note that the repaint() method of a component can be safely called from any thread, without worrying about synchronization. Recall that repaint() does not actually do any painting itself. It just tells the system to schedule a paint event. T he actual painting will be done later, in the Swing event-handling thread. I will also note that Java has another timer class, java.util.Timer, that is appropriate f or use in non-GUI programs. The sample program RandomArtWithThreads.java uses a thread to drive a very simple animation. You can compare it to RandomArtPanel.java, from Section 6.5, which implemented the same animation with a timer. 12.2.2 Recursion in a T hread Although timers should be used in preference to threads when possible, there are times when it is reasonable to use a thread even for a straightforward animation. One reason to do so is when the thread is r unning a recursive algorithm, and you want to repaint the display many times over th e course of the recursion. (Recursion is covered in Section 9.1.) It’s difficult to drive a recursive algorithm with a series of events from a timer; it’s much more natural to use a single recursive method call to do the recursion, and it’s easy to do that in a thread. As an example, the program QuicksortThreadDemo.java uses an animation to illustrate the recursive QuickSort algorithm for sorting an array. In this case, the array contains colors, and the goal is to sort the colors into a standard spectrum from red to violet. You can see the program as an applet in the on-line version of this section. In the program, the user r an domizes the array and starts the sorting process by clicking the “Start” button below the display. T he “Start” button changes to a “Finish” button that can be used to abort the sort before it finishes on its own. In this program, the display’s repaint() method is called every time the algorithm makes a change to the array. Whenever this is done, the thread sleeps for 100 milliseconds to allow time for the display to be repainted and for the user to see the change. There is also a longer delay, one full second, just after the array is randomized, before the sorting starts. Since these delays occur at several points in the code, QuicksortThreadDemo defines a delay() method that makes the thread that calls it sleep for a specified period. The delay() method calls display.repaint() just before sleeping. While the animation thread sleeps, the event-handling thread will have a chance to run and will have plenty of time to repaint th e display. An interesting question is how to implement the “Finish” button, which should abort the sort and terminate the thread. Pressing this button causes that value of a volatile boolean variable, running, to be set to false, as a signal to the thread that it should terminate. The problem is that this button can be clicked at any time, even when the algorithm is many levels down in the recursion. Before the thread can terminate, all of those recursive method calls must return. A nice way to cause that is to throw an exception. QuickSortThreadDemo defines a new exception class, ThreadTerminationException, for this purpose. The delay() method checks the value of the signal variable, running. If running is false, the delay() m ethod throws the exception that will cause the recursive algorithm, and eventually the animation thr ead itself, to terminate. Here, then, is the delay() method: private void delay(int millis) { if (! running) throw new ThreadTerminationException(); display.repaint(); CHAPTER 12. THREADS AND MULTIPROCESSING 600 try { Thread.sleep(millis); } catch (InterruptedException e) { } if (! running) // Check again, in case it changed during the sleep period. throw new ThreadTerminationException(); } The ThreadTerminationException is caught in the thread’s run() method : /** * This class defines the treads that run the recursive * QuickSort algorithm. The thread begins by randomizing the * array, hue. It then calls quickSort() to sort the entire array. * If quickSort() is aborted by a ThreadTerminationExcpetion, * which would be caused by the user clicking the Finish button, * then the thread will restore the array to sorted order before * terminating, so that whether or not the quickSort is aborted, * the array ends up sorted. */ private class Runner extends Thread { public void run() { try { for (int i = hue.length-1; i > 0; i ) { // Randomize array. int r = (int)((i+1)*Math.random()); int temp = hue[r]; hue[r] = hue[i]; hue[i] = temp; } delay(1000); // Wait one second before starting the sort. quickSort(0,hue.length-1); // Sort the whole array, recursively. } catch (ThreadTerminationException e) { // User clicked "Finish". for (int i = 0; i < hue.length; i++) hue[i] = i; } finally {// Make sure running is false and button label is correct. running = false; startButton.setText("Start"); display.repaint(); } } } The program uses a variable, runner, of typ e Runner to represent the thread that does the sorting. When the user clicks the “Start” button, the following code is executed to create and start the thread: startButton.setText("Finish"); runner = new Runner(); running = true; // Set the signal before starting the thread! runner.start(); CHAPTER 12. THREADS AND MULTIPROCESSING 601 Note that the value of the signal variable running is set to true before starting the thread. If running were false when the thread was started, the thread might see that value as soon as it starts and interpret it as a signal to stop before doing anything. Remember that when runner.start() is called, runner starts running in parallel with the thread that called it. Stopping th e thread is a little more interesting, because the thr ead might be sleeping when the “Finish” button is pressed. The thr ead has to wake up before it can act on the signal that it is to terminate. To make the thread a little more responsive, we can call runner.interrupt(), which will wake the thread if it is sleeping. (See Subsection 12.1.2.) This doesn’t have much practical effect in this program, bu t it does make the program respond noticeably more quickly if the user p resses “Finish” immediately after pressing “Start,” while the thread is sleeping for a full second. 12.2.3 Threads for Background Computation In order for a GUI program to b e responsive—that is, to respond to events very soon after they are generated—it’s important that event-handling methods in the program finish their work very quickly. Remember that events go into a queue as they are generated, and the computer cannot respond to an event until after the event-handler methods for previous events have done their work. This means that while one event handler is being executed, other events will have to wait. If an event handler takes a wh ile to run, the user interface will effectively freeze up during that time. This can be very annoying if the delay is more than a fraction of a second. Fortunately, modern computers can do an awful lot of computation in a fraction of a second. However, some computations are too big to be done in event handlers. The solution, in that case, is to do the computation in another thread that runs in parallel with the event-handling thread. This makes it p ossib le for the computer to respond to user events even while the computation is ongoing. We say that the computation is done “in the background.” Note that this application of threads is very different from the previous example. When a thread is used to drive a simple animation, it actually does very little work. The thread only has to wake up several times each second, do a few computations to update state variables for the next frame of the animation, and call repaint() to cause the next frame to be displayed. There is plenty of time while the thread is sleeping for the computer to redraw th e display and handle any other events generated by the user. When a thread is used for background computation, however, we want to keep the com- puter as busy as possible working on th e computation. The thread will compete for proces- sor time with the event-handling thread; if you are not careful, event-handling—repainting in particular—can still be delayed. Fortunately, you can use thread priorities to avoid the problem. By setting the computation thread to run at a lower priority than the event-handling thread, you make sure that events will be processes as quickly as possible, while the computation thread will get all the extra processing time. S ince event handling generally uses very little processing time, this means that most of the processing time goes to the background computation, b ut the interface is still very responsive. (Thread priorities were discussed in Subsection 12.1.2.) The s ample program BackgroundComputationDemo.java is an example of background pro- cessing. This program creates an image that takes some time to compute. The program uses some techniques for working with images th at will not be covered until Subsection 13.1.1, for now all that you need to know is that it takes some computation to compute the color of each pixel in the image. The image itself is a piece of a mathematical object known as the Mandel- brot set. We will use the same image in several examples in this chapter, and will return to the Mandelbrot set in Section 13.5. CHAPTER 12. THREADS AND MULTIPROCESSING 602 In outline, BackgroundComputationDemo is similar to the QuicksortThreadDemo discussed above. The computation is done is a thread defined by a nested class, Runner. A volatile boolean variable, runner, is used to control the thread. If the value of runner is set to false, the thread should terminate. The sample program has a button that the user clicks to start and to abort the computation. The difference is that the thread in this case is meant to run continuously, without sleeping. To allow the user to see that progress is being made in the computation (always a good idea), every time the thread computes a row of pixels, it copies those pixels to the image that is shown on the screen. The user sees the image being built up line-by-line. When the computation thread is created in response to the “Start” button, we need to set it to run at a priority lower than the event-handling thread. The code that creates the thread is itself runn ing in the event-handling th read, so we can use a priority that is one less than the priority of the thread that is executing the code. Note that the priority is set inside a try catch statement. If an error occurs while trying to set the thread priority, the program will still work, though perhaps not as smoothly as it would if th e priority was correctly set. Here is how th e thread is created and started: runner = new Runner(); try { runner.setPriority( Thread.currentThread().getPriority() - 1 ); } catch (Exception e) { System.out.println("Error: Can’t set thread priority: " + e); } running = true; // Set the signal before starting the thread! runner.start(); The other major point of interest in this program is that we have two threads that are both using the object that represents the image. The computation thread accesses the image in order to set the color of its pixels. The event-handling thread accesses the same image when it copies the image to the screen. Since the image is a resource that is shared by several threads, access to the image object should be synchronized. When the paintComponent() method copies the image to the screen (using a method that we have not yet covered), it does s o in a synchronized statement: synchronized(image) { g.drawImage(image,0,0,null); } When the computation thread sets the colors of a row of pixels (using another unfamiliar method), it also uses synchronized: synchronized(image) { image.setRGB(0,row, width, 1, rgb, 0, width); } Note that both of these statements are synchronized on the same object, image. This is es- sential. In order to prevent the two code segments from being executed simultaneously, the synchronization must be on the same object. I use the image object here because it is conve- nient, but just about any object would do; it is not required that you synchronize on the object to which you are trying to control access. Although BackgroundComputationDemo works OK, there is one problem: The goal is to get the computation done as quickly as possible, using all available processing time. T he program CHAPTER 12. THREADS AND MULTIPROCESSING 603 accomplishes that goal on a computer that has only one pro cessor. But on a computer that has several processors, we are still using only one of those pro cessors for the computation. It would be nice to get all the processors working on the problem. To do that, we need real parallel processing, with several computation threads. We turn to that problem next. 12.2.4 Threads for Multiprocessing Our next example, MultiprocessingDemo1.java, is a variation on BackgroundComputationDemo. Instead of doing the computation in a single thr ead, MultiprocessingDemo1 can divide the problem among several threads. The user can select the number of threads to be used. Each thread is assigned one section of the image to compute. The th reads perform their tasks in parallel. For example, if there are two threads, the fi rst thread computes the top half of the image while the second thread computes the bottom half. Here is picture of the program in the middle of a computation using three threads. The gray areas represent parts of th e image that have not yet been computed: You should try out the program. An applet version is on-line. On a multi-processor computer, the computation will complete more quickly when using several threads than when using just one. Note that when using one thread, this program has the same behavior as the previous example program. The approach used in this example for dividing up the problem among threads is not optimal. We will see in the next section how it can be improved. However, MultiprocessingDemo1 makes a good first example of multiprocessing. When the user clicks the “Start” button, the program has to create and start the sp ecified number of threads, and it h as to assign a segment of the image to each thread. Here is how this is done: workers = new Runner[threadCount]; // Holds the computation threads. int rowsPerThread; // How many rows of pixels should each thread compute? rowsPerThread = height / threadCount; // (height = vertical size of image) running = true; // Set the signal before starting the threads! threadsCompleted = 0; // Records how many of the threads have terminated. for (int i = 0; i < threadCount; i++) { int startRow; // first row computed by thread number i int endRow; // last row computed by thread number i CHAPTER 12. THREADS AND MULTIPROCESSING 604 // Create and start a thread to compute the rows of the image from // startRow to endRow. Note that we have to make sure that // the endRow for the last thread is the bottom row of the image. startRow = rowsPerThread*i; if (i == threadCount-1) endRow = height-1; else endRow = rowsPerThread*(i+1) - 1; workers[i] = new Runner(startRow, endRow); try { workers[i].setPriority( Thread.currentThread().getPriority() - 1 ); } catch (Exception e) { } workers[i].start(); } Beyond creating more than one thread, very few changes are needed to get the benefits of multiprocessing. Just as in the previous example, each time a thread has computed the colors for a row of pixels, it copies that row into the image, and synchronization is used in exactly the same way to control access to the image. One thing is new, however. When all the th reads have finished ru nning, the name of the button in the program changes f rom “Abort” to “Start Again”, and the pop-up menu, which has been disabled while the threads were running, is re-enabled. The problem is, how to tell when all the thr eads have terminated? (You might think about why we can’t use join() to wait for the threads to end, as was done in the example in ; at least, we can’t do that in the event-handling thread!) In this example, I use an instance variable, threadsCompleted, to keep track of how many threads have terminated so far. As each thread finishes, it calls a method that ad ds one to the value of th is variable. (The method is called in the finally clause of a try statement to make abs olutely sure that it is called.) When the number of threads that have finished is equal to the number of threads that were created, the metho d updates the state of the program appropriately. Here is the metho d: synchronized private void threadFinished() { threadsCompleted++; if (threadsCompleted == workers.length) { // All threads have finished. startButton.setText("Start Again"); startButton.setEnabled(true); running = false; // Make sure running is false after the threads end. workers = null; // Discard the array that holds the threads. threadCountSelect.setEnabled(true); // Re-enable pop-up menu. } } Note that this method is synchronized. This is to avoid the race condition when threadsCompleted is incremented. Without the synchronization, it is possible that two threads might call the method at the same time. If the timing is just right, both threads could read the same value for threadsCompleted and get the same answer when th ey increment it. The net result will be that threadsCompleted goes up by one instead of by two. O ne thread is not properly counted, and threadsCompleted will never become equal to the number of threads created. The program would hang in a kind of deadlock. The problem would occur only very [...]... while it is waiting in order to perform some periodic task, such as causing a message “Waiting for computation to finish” to blink ∗ ∗ ∗ Let’s look at an example that uses wait() and notify() to allow one thread to control another The sample program TowersOfHanoiWithControls .java solves the Towers Of Hanoi puzzle (Subsection 9. 1.2), with control buttons that allow the user to control the execution of... different threads to handle the communication with different clients, the fact that I/O with one client is blocked won’t stop the server from communicating with other clients It’s important to understand that using threads to deal with blocking I/O differs in a fundamental way from using threads to speed up computation When using threads for speedup in Subsection 12.3.2, it made sense to use one thread... wakes up! To make that possible, the program uses wait() in the solution thread to put that thread to sleep, and it uses notify() in the event-handling thread to wake up the solution thread whenever it changes the value of status Here is the actionPerformed() method that responds to clicks on the buttons When the user clicks a button, this method changes the value of status and calls notify() to wake... notice that the middle piece takes longer to compute than the top or bottom piece In general, when dividing a problem into subproblems, it is very hard to predict just how much time it will take to solve each subproblem Let’s say that one particular subproblem happens to take a lot longer than all the others The thread that computes that subproblem will continue to run for a relatively long time after... read the source code to see how it works ∗ ∗ ∗ One more point about MultiprocessingDemo3 I have not provided any way to terminate the worker threads in this program They will continue to run until the Java Virtual Machine exits To allow thread termination before that, we could use a volatile signaling variable, running, and set its value to false when we want the worker threads to terminate The run()... subproblems are very small, this overhead can add significantly to the total amount of work that has to be done In my example program, the task is to compute a color for each pixel in an image For dividing that task up into subtasks, one possibility would be to have each subtask compute just one pixel But the subtasks produced in that way are probably too small So, instead, each subtask in my program will compute... Clicking “Next Step” executes one step, which moves a single disk from one pile to another Clicking “Run” lets the algorithm run automatically on its own; “Run” changes to “Pause”, and clicking “Pause” stops the automatic execution There is also a “Start Over” button that aborts the current solution and puts the puzzle back into its initial configuration Here is a picture of the program in the middle of... have decided how to decompose a task into subtasks, there is the question of how to assign those subtasks to threads Typically, in an object-oriented approach, each subtask will be represented by an object Since a task represents some computation, it’s natural for the object that represents it to have an instance method that does the computation To execute the task, it is only necessary to call its computation... runPauseButton) { // Toggle between running and paused if (status == GO) { // Animation is running Pause it status = PAUSE; nextStepButton.setEnabled(true); // Enable while paused runPauseButton.setText("Run"); } else { // Animation is paused Start it running status = GO; nextStepButton.setEnabled(false); // Disable while running runPauseButton.setText("Pause"); } } else if (source == nextStepButton) {... void run() { while (true) { runPauseButton.setText("Run"); // Set user interface to initial state nextStepButton.setEnabled(true); startOverButton.setEnabled(false); setUpProblem(); // Set up the initial state of the puzzle status = PAUSE; // Initially, the solution thread is paused checkStatus(); // Returns only when user has clicked "Run" or "Next Step" startOverButton.setEnabled(true); try { solve(10,0,1,2); . primes in the range 300 000 1 to 60 000 00. (The numbers are rather arbitrary.) The main() routine in this program creates between 1 and 5 threads and assigns part of the job to each thread. It waits. says total = total + count; then there is a (small) chance that two threads will try to do this at the same time and that the final total will be wrong. To prevent this race condition, access to total. has to be synchronized. My program uses a synchronized method to add the counts to the total. This method is called once by each thread: synchronized private static void addToTotal(int x) { total

Ngày đăng: 13/08/2014, 18:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan