為了完成多個任務,常創建多個執行緒,它們可能毫不相關,但有時它們完成的任務在某種程度上有一定的關系,此時就需要執行緒之間有一些互動,在Java中,使用一對方法wait()和notify()/notifyAll()實作執行緒的互動,
同步問題的提出
作業系統中的生產者消費者問題,就是一個經典的同步問題,舉一個例子,有兩個人,一個人在刷盤子,另一個人在烘干,這兩個人各自代表一個執行緒,他們之間有一個共享的物件 --- 盤架,刷好而等待烘干的盤子放在盤架上,兩個人在沒有事做事都愿意歇著,顯然,盤架上有刷好的盤子時,烘干的人才能開始作業;而如果刷盤子的人刷的太快,刷好的盤子占滿了盤架時,他就不能再繼續作業了,而要等到盤架上有空位置才行,
這個示例要說明的問題是,生產者生產一個產品后就放入共享物件中,而不管共享物件中是否有產品,消費者從共享物件中取用產品,但不檢測是否已經取過,
若共享物件中只能存放一個資料,可能出現以下問題(執行緒不同步的情況下):
- 生產者比消費者快時,消費者會漏掉一些資料沒有取到,
- 消費者比生產者快時,消費者取相同的資料,
在java語言中,可以用wait()和notify()/notifyAll()方法來協調執行緒間的運行速度關系,這些方法都定義在java.lang.Object類中,
解決方法
為了解決執行緒運行速度問題,Java提供了一種建立在物件實體之上的互動方法,Java中的每個物件實體都有兩個執行緒佇列和他相連,第一個用來排列等待鎖定標志的執行緒,第二個則用來實作wait()和notify()的互動機制,
類java.lang.Object中定義了三個方法wait()和notify()/notifyAll(),
wait方法導致當前的執行緒等待,它的作用是讓當先執行緒釋放其所持有的“物件互斥鎖”,進入wait佇列(等待佇列);而notify()/notifyAll()方法的作用是喚醒一個或所有正在等待佇列中等待的執行緒,并將它(們)移入用一個“物件互斥鎖”佇列,notify()/notifyAll()方法和wait()方法都只能在被宣告為synchronized的方法或代碼中呼叫,方法notify()最多只能釋放等待佇列中的第一個執行緒,如果有多個執行緒在等待,則其他的執行緒將繼續留在佇列中,notifyAll()方法能夠釋放所有等待執行緒,
再來看看前面刷盤子的例子,執行緒t1代表刷盤子,執行緒t2代表烘干,它們都有對盤架drainingBoard的訪問權,假設執行緒t2(烘干執行緒)想要進行烘干作業,而此時盤架時空的,則應表示如下:
if(drainingBoard.isEmpty())
drainingBoard.wait(); //盤架空時則等待
當執行緒t2執行了wait()呼叫后,它不可以再執行,并加入到物件drainingBoard的等待佇列中,在有執行緒將它從這個佇列釋放之前,它不能再次運行,
那么,烘干執行緒怎樣才能重新運行呢?這應該有洗刷執行緒t1來通知它已經有作業可以做了,運行drainingBoard的notify呼叫可以做到這一點:
drainingBoard.addItem(); //放入一個盤子
drainingBoard.notify();
此時,drainingBoard的等待佇列中第一個阻塞執行緒由佇列中釋放出來,并可重新參加運行的競爭,
注意,在這里使用notify呼叫時,沒有考慮是否有正在等待的執行緒,事實上,應該只有在增加盤子后使得盤架不再空時才執行這個呼叫,如果等待佇列中沒有阻塞執行緒時呼叫了方法notify(),則這個呼叫不做任何作業,notify()呼叫不會被保留到以后再發生效用,
使用這個機制,程式能夠非常簡單的協調洗刷執行緒和烘干執行緒,而且并不需要了解這些執行緒的身份,每當執行一項作業,使得另一個執行緒能夠開始作業,就通知物件drainingBoard(呼叫notify());每當由于盤架慷訓滿而不能繼續作業時,就等待物件drainingBoard(呼叫wait()),
在呼叫一個物件的wait(),notify()/notifyAll()時,必須首先持有該物件的鎖定標志,因此這些方法必須在同步程式塊中呼叫,這樣,應該將代碼改寫如下:
synchronized(drainingBoard) {
if(drainingBoard.isEmpty())
? drainingBoard.wait();
}
和
synchronized(drainingBoard) {
? drainingBoard.addItem();
? drainingBoard.notify();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/74587.html
標籤:Java
