Synchronizing threads in Java
2011-08-31 12:58
225 查看
The nice thing about threads in Java is that they are always there. This has hindered the porting of Java to some platforms not offering native threads (like Windows 3.1) because the person doing the port has to both port Java and create a threads
package for it to use. Once you start to use threads effectively, you will not want to go back to more pedestrian single-threaded programming.
A Java programmer's first exposure to threads is usually an applet that uses them to provide animation. In these applets, the thread simply sleeps for a period of time before updating the next frame or moving text in an animated ticker. Threads, however,
are much more useful than this. Another way to use threads is with the
Every Java object instance and class potentially has a monitor associated with it. I say potentially because if you don't use any of the synchronization functions, the monitor is never actually allocated, but it's waiting there just in case.
A monitor is simply a lock that serializes access to an object or a class. To gain access, a thread first acquires the necessary monitor, then proceeds. This happens automatically every time you enter a synchronized method. You create a synchronized method
by specifying the keyword
During the execution of a synchronized method, the thread holds the monitor for that method's object, or if the method is static, it holds the monitor for that method's class. If another thread is executing the synchronized method, your thread is blocked
until that thread releases the monitor (by either exiting the method or by calling
To explicitly gain access to an object's monitor, a thread calls a synchronized method within that object. To temporarily release the monitor, the thread calls the
A very simple example of
The first class is named
The algorithm is essentially this:
To implement this, however, we add a few more lines:
In line 3 we declare our state variable,
must have the synchronized keyword or the call to
In line 7 we get our own name from the thread object. As you will see later, we set this after the thread is created. This helps in debugging since our thread is named something useful and is a convenient way to identify the players.
Lines 9 through 12 solve the problem of whose turn it is before anyone has gone. The policy implemented is that the first thread to invoke this method will get the honor of going first.
Lines 14 through 17 execute when it is the current thread's turn to go. When executed, the thread updates the state variable with the next thread's turn. This is done before the notify, as the notify may cause another thread to start running immediately
before it knows it is its turn to run. Then
not wake up the correct thread and the system will stop until that thread's wait times out.
Lines 19 through 26 execute when it isn't the current thread's turn to go. Line 21 simply calls
was awakened with a call to
This timeout test is performed in line 22. If a timeout occurs, an informative message is printed to the console. In practice this will happen only when the time spent in lines 14 through 17 is greater than 2.5 seconds.
Line 26 is where we catch
Really, that is all there is to this part of the code. I did, however, add some additional code (shown below) between lines 8 and 9 to allow a third thread to cause the threads using this class to exit.
As you can see, this is done by setting the special opponent DONE in the call to
Once we have the class of type
As you can see, this code is even simpler. All we really need is a class that implements the
The two instance variables in this class are the reference holding the
There is a single constructor taking a
The run method runs an infinite loop, calling
To complete our example, we have an application class that will create a couple of threads using the
Because we want to execute this class from the command line, it must include a public static method named
Line 4 is where the code instantiates a copy of our
After line 11 is executed, there are three user threads running, one named alice, one named bob, and the main thread. On the system console you will start seeing messages of the form:
and so on. The threads alternate which one runs by the state in the
it calls
Finally, in lines 12 through 15 you will see that the main thread goes to sleep for five seconds or so, and when it wakes up, it calls
it will never exit (Sun knows about this bug). The short sleep in lines 18 through 20 cover this case and allow our program to exit normally on all systems.
So we have managed to get two threads to share the processor equally by synchronizing use of a common object instance. If you have followed the discussion and believe you understand the code, test your understanding by adding another thread to the mix and
call it Chuck. After you've done that, answer these questions for yourself:
he was a member of the Java group. He joined the Java group just after the formation of FirstPerson Inc. and was a member of the portable OS group (the group responsible for the OS portion of Java). Later, when FirstPerson was dissolved, he stayed with the
group through the development of the alpha and beta versions of the software. He was responsible for creating the Java version of the Sun home page in May 1995. He also developed a cryptographic library for Java and versions of the Java class loader that could
screen classes based on Digital Signatures. Before joining FirstPerson, Chuck worked in the Operating Systems area of SunSoft developing networking applications, where he did the initial design of NIS+.
package for it to use. Once you start to use threads effectively, you will not want to go back to more pedestrian single-threaded programming.
A Java programmer's first exposure to threads is usually an applet that uses them to provide animation. In these applets, the thread simply sleeps for a period of time before updating the next frame or moving text in an animated ticker. Threads, however,
are much more useful than this. Another way to use threads is with the
wait()and
notify()functions that are part of the
Objectclass.
Every Java object instance and class potentially has a monitor associated with it. I say potentially because if you don't use any of the synchronization functions, the monitor is never actually allocated, but it's waiting there just in case.
A monitor is simply a lock that serializes access to an object or a class. To gain access, a thread first acquires the necessary monitor, then proceeds. This happens automatically every time you enter a synchronized method. You create a synchronized method
by specifying the keyword
synchronizedin the method's declaration.
During the execution of a synchronized method, the thread holds the monitor for that method's object, or if the method is static, it holds the monitor for that method's class. If another thread is executing the synchronized method, your thread is blocked
until that thread releases the monitor (by either exiting the method or by calling
wait()).
To explicitly gain access to an object's monitor, a thread calls a synchronized method within that object. To temporarily release the monitor, the thread calls the
wait()function. Because the thread needs to have acquired the object's monitor, calling
wait()is supported only inside a synchronized method. Using
wait()in this way allows the thread to rendezvous with another thread at a particular synchronization point.
A very simple example of
wait()and
notify()is described in the following three classes.
The first class is named
PingPongand consists of a single synchronized method and a state variable. The method is
hit()and the only parameter it takes is the name of the player who will go next.
The algorithm is essentially this:
If it is my turn, note whose turn it is next, then PING, and then notify anyone waiting. otherwise, wait to be notified.
To implement this, however, we add a few more lines:
1 public class PingPong { 2 // state variable identifying whose turn it is. 3 private String whoseTurn = null; 4 5 public synchronized boolean hit(String opponent) { 6 7 String x = Thread.currentThread().getName(); 8 9 if (whoseTurn == null) { 10 whoseTurn = x; 11 return true; 12 } 13 14 if (x.compareTo(whoseTurn) == 0) { 15 System.out.println("PING! ("+x+")"); 16 whoseTurn = opponent; 17 notifyAll(); 18 } else { 19 try { 20 long t1 = System.currentTimeMillis(); 21 wait(2500); 22 if ((System.currentTimeMillis() - t1) > 2500) { 23 System.out.println("****** TIMEOUT! "+x+ 24 " is waiting for "+whoseTurn+" to play."); 25 } 26 } catch (InterruptedException e) { } 27 } 28 return true; // keep playing. 29 } 30 }
In line 3 we declare our state variable,
whoseTurn. This is declared private since the users of the class don't need to know it. Line 5 declares our method and it
must have the synchronized keyword or the call to
wait()will fail.
In line 7 we get our own name from the thread object. As you will see later, we set this after the thread is created. This helps in debugging since our thread is named something useful and is a convenient way to identify the players.
Lines 9 through 12 solve the problem of whose turn it is before anyone has gone. The policy implemented is that the first thread to invoke this method will get the honor of going first.
Lines 14 through 17 execute when it is the current thread's turn to go. When executed, the thread updates the state variable with the next thread's turn. This is done before the notify, as the notify may cause another thread to start running immediately
before it knows it is its turn to run. Then
notifyAll()is called to notify all threads that are waiting on this object that they can run. If you are using only two threads, simply call
notify()since that call will wake up exactly one thread from the set waiting to run. With two threads, only one thread can be waiting, so the correct thread will wake up. If you extend this to three or more threads, however, the notify call may
not wake up the correct thread and the system will stop until that thread's wait times out.
Lines 19 through 26 execute when it isn't the current thread's turn to go. Line 21 simply calls
wait()and goes to sleep. However, you will notice that in line 20 the code notes the current time. It does this because when execution continues after the wait call returns, the reason for continuing could be either the wait timed out or our thread
was awakened with a call to
notify(). The only way to tell the difference is to measure how long the thread was asleep.
This timeout test is performed in line 22. If a timeout occurs, an informative message is printed to the console. In practice this will happen only when the time spent in lines 14 through 17 is greater than 2.5 seconds.
Line 26 is where we catch
InterruptedException, which would be thrown if the thread in the
wait()call stops prematurely.
Really, that is all there is to this part of the code. I did, however, add some additional code (shown below) between lines 8 and 9 to allow a third thread to cause the threads using this class to exit.
8.01 if (whoseTurn.compareTo("DONE") == 0) 8.02 return false; 8.03 8.04 if (opponent.compareTo("DONE") == 0) { 8.05 whoseTurn = opponent; 8.06 notifyAll(); 8.07 return false; 8.08 }
As you can see, this is done by setting the special opponent DONE in the call to
hit(). When the opponent is done, line 8.02 makes sure the code returns the boolean false.
Once we have the class of type
PingPong, any thread with a reference to an instance of class
PingPongcan synchronize itself with other threads holding that same reference. To illustrate this, consider the following
Playerclass designed for use in the instantiation of a couple of threads:
1 public class Player implements Runnable { 2 PingPong myTable; // Table where they play 3 String myOpponent; 4 5 public Player(String opponent, PingPong table) { 6 myTable = table; 7 myOpponent = opponent; 8 } 9 10 public void run() { 11 while (myTable.hit(myOpponent)) 12 ; 13 } 14 }
As you can see, this code is even simpler. All we really need is a class that implements the
Runnableinterface. The
Threadclass provides a constructor that takes a reference to an object implementing
Runnable.
The two instance variables in this class are the reference holding the
PingPongobject and the name of this player's opponent. This latter field is used in the
hit()method to tell the object which player should go next.
There is a single constructor taking a
PingPongobject and the name of an opponent. To satisfy the
Runnableinterface, there is the method
runin lines 10 through 13.
The run method runs an infinite loop, calling
hit()until it returns false. This method returns true until some thread calls it with the opponent name DONE.
To complete our example, we have an application class that will create a couple of threads using the
Playerclass and pit them against each other. This is shown below in the
Gameclass.
1 public class Game { 2 3 public static void main(String args[]) { 4 PingPong table = new PingPong(); 5 Thread alice = new Thread(new Player("bob", table)); 6 Thread bob = new Thread(new Player("alice", table)); 7 8 alice.setName("alice"); 9 bob.setName("bob"); 10 alice.start(); // alice starts playing 11 bob.start(); // bob starts playing 12 try { 13 // Wait 5 seconds 14 Thread.currentThread().sleep(5000); 15 } catch (InterruptedException e) { } 16 17 table.hit("DONE"); // cause the players to quit their threads. 18 try { 19 Thread.currentThread().sleep(100); 20 } catch (InterruptedException e) { } 21 } 22 }
Because we want to execute this class from the command line, it must include a public static method named
mainthat takes a single argument that is an array of strings. This is the method signature the
javacommand keys off of when instantiating a class from the command line.
Line 4 is where the code instantiates a copy of our
PingPongclass and stores the reference in the local variable
table. Line 5 and line 6 are compound object creations, first creating new
Playerobjects and then using those objects in the creation of new
Threadobjects. At create time, the name of the opponent is specified so Alice's opponent is Bob and Bob's opponent is Alice. These new threads are named using the
setNamemethod in lines 8 and 9, and then they are started in lines 10 and 11.
After line 11 is executed, there are three user threads running, one named alice, one named bob, and the main thread. On the system console you will start seeing messages of the form:
PING! (alice) PING! (bob) PING! (alice) ...
and so on. The threads alternate which one runs by the state in the
PingPongobject. This object forces them to run one after another, however it also ensures that they run as rapidly after one another as possible since as soon as one is finished,
it calls
notifyAll()and the other thread begins to run.
Finally, in lines 12 through 15 you will see that the main thread goes to sleep for five seconds or so, and when it wakes up, it calls
hit()with the magic bullet name DONE. This will cause the alice and bob threads to exit. Due to a bug in the Windows version of the Java runtime, the main thread has to wait a bit to let alice and bob exit first, before it can exit. Otherwise
it will never exit (Sun knows about this bug). The short sleep in lines 18 through 20 cover this case and allow our program to exit normally on all systems.
So we have managed to get two threads to share the processor equally by synchronizing use of a common object instance. If you have followed the discussion and believe you understand the code, test your understanding by adding another thread to the mix and
call it Chuck. After you've done that, answer these questions for yourself:
About the author
Chuck McManis is currently the Director of Technology at GolfWeb Inc., a Web magazine devoted to the game of golf. His role there is to develop technologies that make the presentation of the magazine interactive, compelling, and enjoyable. Before joining GolfWebhe was a member of the Java group. He joined the Java group just after the formation of FirstPerson Inc. and was a member of the portable OS group (the group responsible for the OS portion of Java). Later, when FirstPerson was dissolved, he stayed with the
group through the development of the alpha and beta versions of the software. He was responsible for creating the Java version of the Sun home page in May 1995. He also developed a cryptographic library for Java and versions of the Java class loader that could
screen classes based on Digital Signatures. Before joining FirstPerson, Chuck worked in the Operating Systems area of SunSoft developing networking applications, where he did the initial design of NIS+.
相关文章推荐
- Synchronizing threads in Android/Java
- Creating threads in Java
- Threads in Java
- Synchronizing Java Threads on a Shared Resource with Multiple Views @ JDJ
- Core Java Tutorial -- Threads in Java
- Synchronizing Threads and GUI in Delphi application
- 4 ways to do concurrency in Java: Threads, Executors, ForkJoin and Actors
- [Java] Scanner(System.in) 从控制台输入
- this key -- think in java notes
- java的new BufferedReader(new InputStreamReader(System.in))
- intelij idea: Exception in thread "main" java.lang.ClassNotFoundException
- Exception in thread "main" java.sql.SQLException: Incorrect string value: '\xF0\xA2\x9C\xB6\xE2\x80.
- think in java interview-高级开发人员面试宝典(二)
- Solving “Dynamic Web Module 3.0 requires Java 1.6 or newer” in Maven Projects
- base64加密放入URL引发的问题解决,java.net.MalformedURLException:Illegal character in URL
- Think in java 答案_Chapter 5_Exercise 6
- ImageJ== Image Processing and Analysis in Java
- 静态数据的初始化(Think in java P95页代码解析)
- Exception in thread "TimerTotal_3" java.lang.OutOfMemoryError: Java heap space
- Thinking in Java from Chapter 11