Java — Thread Signaling

原文:http://tutorials.jenkov.com/java-concurrency/thread-signaling.html

線程信號的目的是爲了線程間能夠相互發送信號,並且也能使線程去等待其他線程的信號。例如一個線程B可能等待一個來至於線程A的信號,用來表明數據已經處理好了。

Signaling via Shared Objects

一種簡單的線程通信(線程間進行信號的發送)是使用共享相同的對象變量。

    public class MySignal {
        protected boolean hasDataToProcess = false;
        public synchronized boolean hasDataToProcess() {
            return this.hasDataToProcess;
        }
        public synchronized void setHasDataToProcess(boolean hasData) {
            this.hasDataToProcess = hasData;
        }
    }

多個線程必須共享同一個變量。

Busy Wait

一個線程一直在等待另外一個線程改變共享變量的爲其期望值,稱爲 Busy Wait。

protected MySignal sharedSignal = ...
...
while(!sharedSignal.hasDataToProcess()){
  //do nothing... busy waiting
}

wait(),notify() and notifyAll()

Busy waiting 不是一種非常有效地利用CPU來運行等待線程,即容易造成CPU的浪費,除非等待線程所耗費的時間非常短。更好的方式waiting thread 能夠sleep或者inactive當接受到等待信號時。
Java中使用wait(),notify(),notifyAll()來完成這些工作。

A thread that calls wait() on any object becomes inactive until another thread calls notify() on that object. In order to call either wait() or notify the calling thread must first obtain the lock on that object. In other words, the calling thread must call wait() or notify() from inside a synchronized block. Here is a modified version of MySignal called MyWaitNotify that uses wait() and notify().

也就是說,wait、notify、notifyAll必須加上synchronized關鍵字,且所對象必須是用一個對象。

    public class MyWaitNotify {
        MonitorObject myMonitorObject = new MonitorObject();
        public void doWait() {
            synchronized (myMonitorObject) {
                try {
                    myMonitorObject.wait();
                } catch (InterruptedException e) {...}
            }
        }
        public void doNotify() {
            synchronized (myMonitorObject) {
                myMonitorObject.notify();
            }
        }
    }

Missed Signals

如果一個線程在調用wait時先調用了notify,容易出現信號丟失的情況,直接導致線程waiting forever

    public class MyWaitNotify2 {
        MonitorObject myMonitorObject = new MonitorObject();
        boolean wasSignalled = false;
        public void doWait() {
            synchronized (myMonitorObject) {
                if (!wasSignalled) {
                //  In fact it only calls wait() if no signal was received in between the previous doWait() call and this.
                    try {
                        myMonitorObject.wait();
                    } catch (InterruptedException e) {...}
                }
                //clear signal and continue running.
                wasSignalled = false;
            }
        }
        public void doNotify() {
            synchronized (myMonitorObject) {
                wasSignalled = true;
                myMonitorObject.notify();
            }
        }
    }

Spurious Wakeups

For inexplicable reasons it is possible for threads to wake up even if notify() and notifyAll() has not been called. This is known as spurious wakeups. Wakeups without any reason.

    public class MyWaitNotify3 {
        MonitorObject myMonitorObject = new MonitorObject();
        boolean wasSignalled = false;
        public void doWait() {
            synchronized (myMonitorObject) {
            // 線程可能被一些未知的原因而喚醒,所以必須使用循環
                while (!wasSignalled) {
                    try {
                        myMonitorObject.wait();
                    } catch (InterruptedException e) {...}
                }
                //clear signal and continue running.
                wasSignalled = false;
            }
        }
        public void doNotify() {
            synchronized (myMonitorObject) {
                wasSignalled = true;
                myMonitorObject.notify();
            }
        }
    }

Multiple Threads Waiting for the Same Signals

The while loop is also a nice solution if you have multiple threads waiting, which are all awakened using notifyAll(), but only one of them should be allowed to continue. Only one thread at a time will be able to obtain the lock on the monitor object, meaning only one thread can exit the wait() call and clear the wasSignalled flag. Once this thread then exits the synchronized block in the doWait() method, the other threads can exit the wait() call and check the wasSignalled member variable inside the while loop. However, this flag was cleared by the first thread waking up, so the rest of the awakened threads go back to waiting, until the next signal arrives.

Don’t call wait() on constant String’s or global objects

不要使用String類型去做鎖對象,因爲在編譯期間很多常量String都會直接放入常量池中,被共享。

    public class MyWaitNotify{
        String myMonitorObject = "";
        boolean wasSignalled = false;
        public void doWait(){
            synchronized(myMonitorObject){
                while(!wasSignalled){
                    try{
                        myMonitorObject.wait();
                    } catch(InterruptedException e){...}
                }
                //clear signal and continue running.
                wasSignalled = false;
            }
        }
        public void doNotify(){
            synchronized(myMonitorObject){
                wasSignalled = true;
                myMonitorObject.notify();
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章