📄 ch8.htm
字号:
thrownew StackEmptyException();<BR> // Otherwise, return the topitem and decrement the top...<BR> Integer I = s[top];<BR> s[top] = null;<BR> -top;<BR> return I;<BR> }<BR><BR> // List the contents of the stack...<BR> public void list() {<BR> for (int i = 0; i <= top;++i)<BR> System.out.println(i+ ": " + s[i]);<BR> }<BR>}</TT></BLOCKQUOTE><HR><P>It is important to note that threads generally function by <I>time-slicing</I>.What occurs in an environment that supports time-slicing is thatthreads share CPU time while executing their respective code.In effect, although the threads are said to be running simultaneously,there is really only one thread executing at a given moment intime. It just <I>seems</I> like the threads are running simultaneously.The speed of the computer and the small increments of time makeit possible for the threads to look like they're performing simultaneously.<P>Suppose there are two threads, A and B. If thread A is executinga method, thread B could get a slice of time before A is finishedwith its method. If the method is thread-safe, it won't matterto thread A that one or more threads interrupts its processing.Regardless of the time-slicing sequence, a thread-safe threadA will get the desired results.<BR><P><CENTER><TABLE BORDERCOLOR=#000000 BORDER=1 WIDTH=80%><TR VALIGN=TOP><TD><B>Note</B></TD></TR><TR VALIGN=TOP><TD><BLOCKQUOTE>Java works best in environments designed to support <I>preemptive</I> time scheduling. A preemptive scheduler gives running tasks (processes or threads) small portions of time to execute by using time-slicing, discussed in this section. UNIX and Windows 95 are two operating systems that support some form of preemptive scheduling. However, some platforms, such as Windows 3.1, support a more primitive form of scheduling, called <I>nonpreemptive</I>. In this form of scheduling, one task doesn't give another task a chance to run until it's finished or has manually yielded its time. This makes writing cooperative programs fairly difficult-especially those with long CPU-intensive operations. You basically have to code your application around the system being nonpreemptive; in certain portions of the code, you need to call some kind of yield method to let other threads execute. In preemptive scheduling environments, this kind of manual coding is not necessary.</BLOCKQUOTE></TD></TR></TABLE></CENTER><P><P>With time-slicing in mind, it isn't difficult to expose the problemwith the TestStack class. Think about what would happen if the<TT>push()</TT> and <TT>pop()</TT>code shared time slices in the following manner:<BLOCKQUOTE><TT>PUSH: ++top;<BR>POP: Integer I = s[top];<BR>POP: s[top] = null;<BR>POP; -top;<BR>PUSH: s[top] = item;</TT></BLOCKQUOTE><P>In this case, the <TT>pop()</TT> callwould be in error because it would return a stack top that hasn'tbeen assigned a value yet. In short, if the <TT>push()</TT>and <TT>pop()</TT> operations shareslices of time, they will probably not work properly. The operationswith the <TT>top</TT> variable needto be <I>atomic</I>, which effectively means that another threadcannot interrupt the execution of the operation until it's finished.<P>This issue of operations causing undesired behavior because ofconcurrency is often referred to as the <I>producer/consumer</I><I>problem</I>. This problem is characterized by one thread producingdata (in this case, pushing an item onto the stack), while a concurrentthread is consuming the data (popping the stack). The problemis one of <I>synchronization</I>: Consuming and producing operationscannot be interleaved indiscriminately; rather, the operationsneed to be synchronized to guarantee thread-safe behavior.<BR><P><CENTER><TABLE BORDERCOLOR=#000000 BORDER=1 WIDTH=80%><TR VALIGN=TOP><TD><B>Note</B></TD></TR><TR VALIGN=TOP><TD><BLOCKQUOTE>Although a stack class was used in this example, note that there is a stack class available in the java.util package. The stack class written in this chapter is used strictly to illustrate problems with multithreading. Also note that the Exception classes associated with the stack code of this section give you a good example of how to create a subhierarchy of Exception classes. In this case, a new hierarchy of stack exceptions was created, with StackException as the root.</BLOCKQUOTE></TD></TR></TABLE></CENTER><P>Listing 8.5 provides Applet and Thread classes that use the TestStackclass appearing throughout this discussion on synchronization.The Applet class, StackApplet, pushes an element onto the stackwhenever you click the mouse. It follows this push with a listingof the current stack contents. The Thread class, called StackThread,loops indefinitely, looking for items on the stack. If one isfound, its integer value is displayed to standard output. If not(indicated by a StackEmptyException object being thrown), thethread sleeps a little and tries again. The synchronization problemcan be seen by clicking the mouse rapidly. The <TT>push()</TT>,<TT>pop()</TT>, and <TT>list()</TT>methods can then be interleaved. It is likely you'll see the listingappearing incorrectly if you click fast enough. In fact, the problemwill be blatant: The output from the <TT>pop()</TT>method-which changes the <TT>top</TT>variable also used in <TT>list()</TT>-willoccur in the middle of the <TT>list()</TT>method, thus undermining its results.<HR><BLOCKQUOTE><B>Listing 8.5. Applet and Thread classes that use the TestStackclass.<BR></B></BLOCKQUOTE><BLOCKQUOTE><TT>import java.awt.*;<BR>import java.lang.*;<BR>import java.applet.Applet;<BR><BR>public class StackApplet extends Applet {<BR> TestStack s;<BR> int counter = 0; // For stack testdata...<BR> Thread t; // The stack thread...<BR> // Create the stack at initialization...<BR> // Make a thread to read the stack...<BR> public void init() {<BR> s = new TestStack(20);<BR> t = new StackThread(s);<BR> t.start();<BR> }<BR><BR> // Add an item to the stack whenever you click<BR> // on the mouse...<BR> public boolean mouseDown(Event ev, int x, inty) {<BR> try {<BR> s.push(newInteger(++counter));<BR> s.list();<BR> }<BR> catch (StackFullExceptione) {<BR> System.out.println("Stackfull!");<BR> }<BR> return true;<BR> };<BR><BR> // Kill the stack thread when leaving...<BR> public void destroy() {<BR> t.stop();<BR> try {<BR> t.join();<BR> }<BR> catch (InterruptedExceptione) { }<BR> }<BR>}<BR><BR>// Thread that constantly reads the stack and prints out<BR>// the current top...<BR>class StackThread extends Thread {<BR> TestStack s;<BR> public StackThread(TestStack s) {<BR> this.s = s;<BR> }<BR> // Loop forever, looking at the top of the stack...<BR> public void run() {<BR> Integer Top;<BR> while (true) {<BR> // Printout top of stack, if stack isn't empty...<BR> try {<BR> Top= s.pop();<BR> System.out.println("Thread:Read " + Top);<BR> }<BR> // Sleepsome if stack is empty...<BR> catch (StackEmptyExceptionse) {<BR> try{<BR> sleep(250);<BR> }<BR> catch(InterruptedException e) { }<BR> }<BR> }<BR> }<BR>}</TT></BLOCKQUOTE><HR><H3><A NAME="IntroducingtheSynchronizedModifier">Introducing theSynchronized Modifier</A></H3><P>The developers of Java are aware of the synchronization problemdescribed in the preceding section. They also know that synchronizationis often dealt with by issuing many lines of code. Fortunatelyfor Java developers, the Java architects took advantage of programmingconstructs introduced over 20 years ago that make creating thread-safeclasses relatively easy. Java synchronization is based on theconcept of <I>monitors</I>. The idea is that each class and objecthas its own monitor that functions as a lock on the item. Forexample, if one thread locks an object's monitor, then anotherthread cannot access that object until the monitor is released.A monitor can make a code fragment behave like a <I>critical section</I>,with a piece of code that should only have one thread in it ata given time.<P>The Java language's <I>synchronized</I> modifier is used to implementa monitor. The modifier can be applied on a method or a blockof code. If a method is declared as synchronized, then a threadthat invokes the method owns the monitor of the object or classuntil it leaves the method. If another thread tries to invokethe method while the other thread owns the monitor, then thatcalling thread will have to wait until the first thread is finished.When this occurs, the monitor is released, and the next threadcan execute the method.<P>Placing the synchronized modifier in just three areas of the TestStackclass is all that's needed to make that class thread-safe. Todo this, the three method declarations of the class need to beredeclared, as follows:<BLOCKQUOTE><TT>public synchronized void push(Integeritem) throws StackFullException<BR>public synchronized Integer pop() throws StackEmptyException<BR>public synchronized void list()</TT></BLOCKQUOTE><P>With that simple change, the TestStack class is now thread-safe!<P>Another way of using the synchronized modifier is to apply itto blocks of code. Specifically, the critical sections of codeshould be synchronized. The TestStack class can be modified todo this, as shown in Listing 8.6. The same methods from the previousdeclaration are modified to have blocks of the structure<BLOCKQUOTE><TT>synchronized(this) {<BR>}</TT></BLOCKQUOTE><P>around their critical sections of code. The <TT>this</TT>of the synchronized statement refers to the instance of the TestStackclass. The effect in this case is similar to that of synchronizingmethods; no thread can enter synchronized code while another threadholds the monitor of the object. As in synchronizing methods,this version of the TestStack class is thread-safe. Note, however,that the code is a little more cumbersome and harder to read.In general, synchronizing methods is preferred over synchronizingcode blocks for both readability and ease of use. Furthermore,method-based synchronization is considered more object-orientedbecause it more <BR>effectively "hides" the fact that the code is executingas a thread, thus allowing you to focus on the object's behaviorand not on how it works in a concurrent atmosphere. On the otherhand, synchronizing blocks is more efficient because the monitorsare held for shorter periods of time. If your code has only asmall section of code that is critical, then it might be bestto synchronize that block of code. However, classes such as TestStackare probably best implemented with synchronized methods.<HR><BLOCKQUOTE><B>Listing 8.6. The TestStack class with synchronized code blocks.<BR></B></BLOCKQUOTE><BLOCKQUOTE><TT>// This class implements a simple stackas<BR>// an array of integers...<BR>class TestStack {<BR> Integer s[]; // The stack array ofinteger objects...<BR> int top; // The current top of the stack. Nextitem to place.<BR> // Construct a stack of the specified size...<BR> public TestStack(int size) {<BR> s = new Integer[size];<BR> top = -1; //Empty stack...<BR> }<BR> // Push an item onto the stack...<BR> public void push(Integer item) throws StackFullException{<BR> synchronized(this) {<BR> // Throw exception if stackis full...<BR> if (top == s.length)<BR> thrownew StackFullException();<BR> // Otherwise increment thetop and add the item...<BR> ++top;<BR> s[top] = item;<BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -