Java Thread Programming 1.8.1 – Inter-thread Communication
來源:互聯網
上載者:User
The Need for Inter-thread SignalingThrough synchronization, one thread can safely change values that another thread will read. How does the second thread know that the values have changed? What if the second thread is waiting for the values to change by rereading the values every few seconds? One not-so-good way that a thread can wait for a value to change is by using a
busy/wait:while ( getValue() != desiredValue ) {Thread.sleep(500);} Such code is called a busy/wait because the thread is busy using up processor resources to continually check to see if the value has changed. To use fewer resources, the sleep time could be increased, but then the thread might not find out about the change for quite some time. On the other hand, if the sleep time is reduced, the thread will find out sooner, but will waste even more of the processor resources. In Java, there is a much better way to handle this kind of situation: the
wait/notify mechanism. 有時候我們需要線程間的通訊,比如第二個線程如何知道第一個線程的某些值發生了改變?不太好的方法如上,稱之為busy/wait,通過不斷迴圈並結合Thread.sleep()測試值是否發生變化,會佔用處理器資源,並且迴圈的頻率不容易掌握,快了浪費資源,慢了降低反應速度。像這種情況,java中給出了一種更好的解決方案:wait/notify機制
The Wait/Notify MechanismThe wait/notify mechanism allows one thread to wait for a notification from another thread that it may proceed.
Minimal Wait/NotifyAt a bare minimum, you need an object to lock on and two threads to implement the wait/notify mechanism.Imagine that there is a member variable, valueLock, that will be used for synchronization:private Object
valueLock = new Object();The first thread comes along and executes this code fragment:
synchronized ( valueLock ) {try { valueLock.
wait();} catch (
InterruptedException x ) { System.out.println(“interrupted while waiting”);}} The wait() method requires the calling thread to have previously acquired the object-level lock on the target object. In this case, the object that will be waited upon is valueLock, and two lines before the valueLock.wait() statement is the synchronized(valueLock) statement. The thread that invokes the wait() method releases the object-level lock and goes to sleep until notified or interrupted. If the waiting thread is interrupted, it competes with the other threads to reacquire the object-level lock and throws an InterruptedException from within wait(). If the waiting thread is notified, it competes with the other threads to reacquire the object-level lock and then returns from wait(). Wait()方法需要在獲得object-level lock之後才能調用,否則會拋出IllegalMonitor-StateException異常,當線程調用wait()方法後,會釋放object-level lock,然後sleep(),直到被notified或interrupted,如果被interrupted,此線程會重新競爭獲得object-level lock,然後拋出InterrruptedException,如果被notified,此線程會重新競爭獲得object-level lock,從wait()返回,繼續執行wait()以後的代碼。 Many times, a thread is interrupted to signal it that it should clean up and die (see Chapter 5). The statements used to wait can be slightly rearranged to allow the InterruptedException to propagate up further: try {
synchronized ( valueLock ) { valueLock.
wait();}} catch ( InterruptedException x ) {System.out.println(“interrupted while waiting”);
// clean up, and allow thread to return from run()} 有時候,中斷一個線程是為了將其銷毀清除,可以將InterruptedException異常延遲處理 Instead of catching InterruptedException, methods can simply declare that they throw it to pass the exception further up the call chain: public void someMethod() throws
InterruptedException {// ...
synchronized ( valueLock ) { valueLock.
wait();}// ...} 如果不在方法內捕捉異常,可以繼續上拋,留待以後處理 The thread doing the notification comes along and executes this code fragment:
synchronized ( valueLock ) {valueLock.
notify(); // notifyAll() might be safer...} This thread blocks until it can get exclusive access to the object-level lock for valueLock. After the lock is acquired, this thread notifies
one of the waiting threads. If no threads are waiting, the notification effectively does nothing. If more than one thread is waiting on valueLock, the thread scheduler arbitrarily chooses
one to receive the notification. The other waiting threads are not notified and continue to wait. To notify
all waiting threads (instead of just one of them), use notifyAll() (discussed later in this chapter). 首先要獲得object-level lock,然後調用notify()通知此object-level lock上所有等待線程中的一個,如果沒有waiting線程,則通知無效。如果有多個等待線程,則線程調用機制選擇其中一個通知,其它沒有獲得通知的繼續等待。如果要通知所有此鎖上的等待線程,適用notifyAll()
Typical Wait/NotifyIn most cases, a member variable is checked by the thread doing the waiting and modified by the thread doing the notification. The checking and modification occur inside the synchronized blocks to be sure that no race conditions develop. 大部分情況下,都是一個線程等待一個成員變數滿足某個條件,另一個線程修改此成員變數後進行通知。 This time, two member variables are used: private boolean
value = false;private Object
valueLock = new Object(); The value variable is checked by the thread doing the waiting and is set by the thread doing the notification. Synchronization on valueLock controls concurrent access to value. The first thread comes along and executes this code fragment:try {
synchronized ( valueLock ) { while (
value != true ) {
valueLock.wait(); } // value is now true}} catch (
InterruptedException x ) {System.out.println(“interrupted while waiting”);} After acquiring the object-level lock for valueLock, the first thread checks value to see if it is true. If it is not, the thread executes wait(), releasing the object-level lock. When this thread is notified, it wakes up, reacquires the lock, and returns from wait(). To be sure that it was not falsely notified (see the “Early Notification” discussion later in this chapter), it re-evaluates the while expression. If value is still not true, the thread waits again. If it is true, the thread continues to execute the rest of the code inside the synchronized block. While the first thread is waiting, a second thread comes along and executes this code fragment:
synchronized ( valueLock ) {value = true;
valueLock.notify(); // notifyAll() might be safer...} When the first thread executes the wait() method on valueLock, it releases the object-level lock it was holding. This release allows the second thread to get exclusive access to the object-level lock on valueLock and enter the synchronized block. Inside, the second thread sets value to true and invokes notify() on valueLock to signal
one waiting thread that value has been changed.
Wait/Notify with synchronized MethodsSometimes, the class is designed to synchronize on this instead of another object. In this case, the synchronized method modifier can be used instead of a synchronized statement. The following code fragments are an adaptation of the previous example. 如果不是同步其它對象,而是同步this,可以類定義中結合wait/notify機制使用synchronized method。 As before, a member variable value is initially set to false:private boolean
value = false; The first thread (threadA) comes along and invokes this waitUntilTrue() method: public
synchronized void waitUntilTrue() throws
InterruptedException {while (
value == false ) {
wait();}} While threadA is blocked on the wait(), a second thread (threadB) comes along and executes this method, passing in true for newValue: public
synchronized void setValue(boolean newValue) {if ( newValue != value ) {、
value = newValue;
notify(); // notifyAll() might be safer...}} Note that both methods are synchronized and are members of the same class. In addition, both threads are invoking methods on the same
instance of this class. The waitUntilTrue() method (with the wait() inside) declares that it might throw an InterruptedException. In this case, when threadB passes in true, value is changed and notify() is used to signal the waiting threadA that it may proceed. threadA wakes up, reacquires the object-level lock on this, returns from wait(), and re-evaluates the while expression. This time, value is true, and threadA will return from waitUntilTrue(). 如上,類定義中涉及到wait和notify的兩個方法都被定義成synchronized,如果兩個線程都是調用此類的同一個執行個體,則兩個線程間可以互相通訊。
Object API Used for Wait/NotifyThe wait/notify mechanism is embedded deep in the heart of Java. Object, the superclass of all classes, has five methods that are the core of the wait/notify mechanism: notify(), notifyAll(), wait(), wait(long), and wait(long, int). All classes in Java inherit from Object, so all classes have these public methods available to them. Additionally, none of these methods can be overridden in a subclass as they are all declared final. Java中所有類的父類Object內建了wait/notify機制,它內建wait/notify機制的五種核心方法:notify(),notifyAll(),wait(),wait(long),wait(long,int),所以java中的所有類都具有這五種方法,這五種方法是public,並且final,不能被子類覆蓋。
notify()public
finalnative void notify() throws
IllegalMonitorStateException // RuntimeException The notify() method is used by a thread to signal any other threads that might be waiting on the object. If more than one thread is waiting on the object, the thread scheduler will arbitrarily choose exactly one to be notified, and the others will continue to wait. If no threads are currently waiting on the object, notify() has no effect. Before invoking notify(), a thread must get exclusive access to the object-level lock for the object. Unlike wait(), the invocation of notify() does
not temporarily release the lock. If
the proper lock is not held when notify() is called, an IllegalMonitorStateException is thrown. This exception is a subclass of RuntimeException, so a try-catch construct is not necessary and is rarely used. notify()方法用來通知在其訊號量上等待的所有其它線程。如果有一個以上的線程等待,則會選擇其中一個進行通知,其它繼續等待,如果沒有線程等待,則此方法無效。調用notify()並不象wait()那樣釋放鎖,而是等待鎖完成後自己釋放。如果調用notify()是沒有持有適當的鎖,會拋出IllegalMonitorStateException(RuntimeException的子類,不必try/catch)
notifyAll()public
finalnative void notifyAll()
throws IllegalMonitorStateException // RuntimeException
The notifyAll() method works the same as notify() (see above) with one important exception: When notifyAll() is invoked,
all the threads waiting on the object are notified, not just one. The advantage of notifyAll() is that you don’t have to be concerned about which
one of the waiting threads will be notified—they will
all be notified. The disadvantage is that it might be wasteful (in terms of processor resources) to notify all the waiting threads if only one will actually be able to proceed. When in doubt, err on the side of safety over speed and use notifyAll() instead of notify(). notifyAll()通知鎖上的所有等待線程,其缺點顯而易見,如果沒有必要通知所有等待線程,可能會浪費處理器資源。基於安全而不是速度考慮,應該使用notifyAll(),而不是notify()。
wait()public
final void wait() throws InterruptedException,
IllegalMonitorStateException // RuntimeException
The wait() method is used to put the current thread to sleep until it is notified or interrupted. Before invoking wait(), a thread must get exclusive access to the object-level lock for the object. Just after entering wait(), the current thread
releases the lock. Before returning from wait(), the thread competes with the other threads to reacquire the lock. If the proper lock is not held when wait() is called, an IllegalMonitorStateException is thrown. This exception is a subclass of RuntimeException, so a try-catch construct is not necessary and is rarely used.
If the waiting thread is interrupted, it competes to reacquire the lock and throws an InterruptedException from within wait(). This exception is
not a subclass of RuntimeException, so a try-catch construct is required.
wait()將當前線程sleep直至被notified或interrupted。調用wait()之前,線程必須排他獲得對象的object-level lock。一進入wati(),當前線程就釋放object-level lock。在wait()返回之前,線程重新和其它線程競爭獲得此鎖。如果沒有持有適當的鎖就調用了wait()會拋出IllegalMonitorStateException。如果等待的線程被interrrupted,會重新獲得此鎖,然後拋出InterrruptedException,此異常非RuntimeException的子類,所以必須要try/catch。
wait(long)public final native void wait(long msTimeout)
throws InterruptedException,
IllegalMonitorStateException, // RuntimeException
IllegalArgumentException // RuntimeException
The wait(long) method is used to put the current thread to sleep until it is notified, interrupted, or the specified timeout elapses. Other than the timeout, wait(long) behaves the same as wait() (see above). The argument msTimeout specifies the maximum number of milliseconds that the thread should wait for notification. If msTimeout is 0, the thread will never time out (just like wait()). If the argument is less than 0, an IllegalArgumentException will be thrown. IllegalArgumentException is a subclass of RuntimeException, so a try-catch block is not required and is rarely used. 讓線程等待一定的時間,毫秒,如果參數設為0,則無時間限制,如果參數小於0,拋出IllegalArgumentException異常,此異常為RuntimeException的子類,不需要try-catch
If the specified number of milliseconds elapses before the waiting thread is notified or interrupted, it competes to reacquire the lock and returns from wait(long). There is no way for the caller to determine whether a notification or a timeout occurred because no information (void) is returned from wait(long). 如果設定的時間內等待線程沒有被notified/interrupted,會重新獲得鎖然後從wait()返回。調用者沒有辦法知道是被通知還是逾時,因為wait(long)沒有傳回值。
wait(long, int)public final void wait(long msTimeout, int nanoSec)
throws InterruptedException,
IllegalMonitorStateException, // RuntimeException
IllegalArgumentException // RuntimeException
The wait(long, int) method works just like wait(long, int) (see above) with the exception that nanoseconds can be added to the timeout value. The argument nanoSec is added to msTimeout to determine the total amount of time that the thread will wait for notification before returning. A nanosecond is one-billionth of a second (10E-9), and most common implementations of the Java VM don’t truly support this fine a resolution of time. For this reason, the use of the method is currently quite rare. 同,wait(long),不過時間設定更精確,可設定納秒數,不過一般虛擬機器未實現此方法。
When to Use notifyAll() Instead of notify()The fundamental difference between notify() and notifyAll() is that if more than one thread is simultaneously waiting for notification, notify() will provide notification to only one of the waiting threads, whereas notifyAll() will provide notification to all of them. If your code is well defended against early notifications (discussed later), notifyAll() is generally the better choice. The major disadvantage of notifyAll() is that it is wasteful of processor resources if all but one of the notified threads will end up waiting again. This is a situation that is difficult to guarantee. If your code synchronizes on this either through synchronized blocks or the synchronized method modifier, you can’t be sure that some code external to the class won’t synchronize and wait on a reference to the object. If that happens, notify() might signal the thread running that external code instead of the thread that you intended. Consider a situation where you have a class, ClassX, with two methods: 同一個鎖有不同的加鎖方式,類內部給this加鎖,外部類還可以給此類的執行個體加鎖,多個線程等待此鎖,notify()不一定會通知到期望的線程。 類ClassX中有以下兩個synchronized方法,其中包括了wait/notifypublic
synchronized void waitUntilTrue() throws
InterruptedException { while (
value == false ) { wait(); }}public
synchronized void setValue(boolean newValue) { if ( newValue != value ) {
value = newValue;
notify(); // notifyAll() might be safer... }} 同時,有個外部類ClassY,產生ClassX的執行個體,也此執行個體上wait()In addition, there’s an external class, ClassY, with this code in one of its methods:ClassX cx = new ClassX();cx.setValue(false);// ...synchronized ( cx ) {
cx.wait(); // trouble} If threadA is running inside ClassY, it synchronizes on cx and invokes wait(). If threadB invokes waitUntilTrue(), it is now also waiting for notification. If threadC invokes setValue() and passes true (a new value), threadC will only notify one thread because notifyAll() wasn’t used. There’s no way to be sure whether threadA or threadB will be notified. In this situation, notifyAll() would have guaranteed that they would both be notified. 此時如果有三個線程:threadA在ClassY運行,同步cx並等待;threadB調用cx的waitUntilTrue()等待;threadC調用cx的setValue(),它只能通知threadA,B中的一個,沒有方法確定哪一個被通知。notifyAll()保證二者都被通知。 It is generally safe to use notify() only when you can guarantee that only one thread will ever be waiting for notification. This is a relatively unusual occurrence. 只有一個線程時,使用notify() If you’re not sure whether you need to use notify() or notifyAll(), use notifyAll(). It might be a little wasteful, but it’s safer. 如果不確定使用哪一個,就使用notifyAll()吧