单线程下的生产者--消费者模式详解,wait和sleep的区别
1. 單線程下的生產者--消費者模式
1.1 該模式下,一個線程生產數據,另一個線程處理數據。當數據還沒被處理,那么生產數據的線程進入等待狀態;如果數據還沒生產,那么處理數據的線程進入等待狀態,代碼及運行結果如下。
// 生產者 class Producer extends Thread{private final List<Object> lst;public Producer(List<Object> lst){this.lst = lst;}@Overridepublic void run() {while(true){synchronized (lst){if(lst.size() > 0){try {lst.wait();} catch (InterruptedException e) {e.printStackTrace();}}lst.add(new Object());System.out.println("生產者:" + lst.size());lst.notify();}}} }// 消費者 class Consumer extends Thread{private final List<Object> lst;public Consumer(List<Object> lst){this.lst = lst;}@Overridepublic void run() {while(true){synchronized (lst){if(lst.size() == 0){try {lst.wait();} catch (InterruptedException e) {e.printStackTrace();}}lst.remove(0);System.out.println("消費者:" + lst.size());lst.notify();}}} }1.2 功能分析。生產者和消費者共享同一個ArrayList對象,生產者往ArrayList放數據,消費者刪除數據。初始時,ArrayList里沒有數據,如果消費者先操作ArrayList對象,判斷ArrayList對象沒有數據后,執行wait方法進入等待狀態并釋放ArrayList對象鎖;此時生產者得到該鎖,判斷ArrayList沒有數據后,往里面添加一個數據,并調用notify方法喚醒進入等待狀態的消費者。如果此時生產者又搶到ArrayList對象鎖,那么判斷ArrayList對象有數據后,會進入等待狀態,前面被喚醒的消費者來消費并喚醒剛進入等待的生產者,后面一直如此循環....
2. wait和notify方法
2.1 wait方法,執行該方法的線程會進入阻塞狀態,而不是可運行狀態,所以不會去搶奪CPU時間片;同時會釋放正在占用的對象鎖。
2.2 關于執行wait方法的線程是否會搶奪CPU時間片,下面通過資源監視器來證明一下。當我沒有運行java程序時,CPU的利用率是6%左右,如下圖。
當java創建10個線程,一個while循環,沒有任何邏輯代碼,CPU利用率達到了100%。
此時我們在while循環中寫上wait方法,我們可以發現,CPU利用率只有剛創建線程時會有短暫的升高,后面線程執行了wait方法進入等待狀態并沒有導致CPU利用率升高,所以執行wait方法的線程不會搶奪CPU時間片。
同理,sleep方法也并不會搶奪CPU時間片,但sleep方法不釋放鎖。
2.3 wait和sleep的區別
① wait是Object的方法,sleep是Thread的方法。
② wait會釋放占有的鎖,但sleep不會釋放鎖。
③ wait必須用在同步代碼塊或同步方法中,sleep不需要。
④ 它們都不會去搶奪CPU時間片
⑤ 調用wait方法時線程會進入waiting狀態;而調用sleep方法時線程會進入timed_waiting狀態;線程等待synchronized同步代碼塊或同步方法時進入blocked狀態。
2.4?notify方法:喚醒等待狀態的線程,將線程從阻塞狀態變為可運行狀態,搶到CPU時間片再執行;同時還要占有鎖才能執行。
3. 多線程下的生產者消費者模式
3.1 我們先使用第一節的消費者和生產者代碼,分別創建兩個消費者和兩個生產者,得到運行結果如下。
3.2 出現一直生產的情況的原因:生產了數據的生產者會喚醒一個正在等待狀態的線程,如果此時喚醒的是另一個生產者,那么又會生產數據,然后該生產者會有喚醒一個等待狀態的線程,如果此時喚醒的又是生產者,那么循環往復,就會一直生產數據....
3.3 既然出現一直生產的情況,是因為被喚醒直接生產數據,那么就在被喚醒后仍然判斷一下是否有數據,如果有,再進入等待狀態,代碼如下。
3.4 運行后,會出現卡死情況,原因:當沒有數據時,兩個消費者會進入等待狀態,此時如果一個生產者占有鎖,它會生產一個數據,如果此時喚醒的是一個等待狀態的生產者,那么生產者還是進入等待,等到自己再次搶到鎖,依然因為有數據而進入等待狀態,也就出現了卡死現象。
3.5 既然是因為都進入了等待狀態,那么每次都用notifyAll方法,把所有等待狀態的線程都喚醒,就不會出現卡死現象了,最終代碼如下。
import java.util.ArrayList; import java.util.List;public class WaitNotifyTest {public static void main(String[] args) {List<Object> lst = new ArrayList<>();Producer pro = new Producer(lst);Consumer con = new Consumer(lst);pro.start();con.start();new Producer(lst).start();new Consumer(lst).start();} }class Producer extends Thread{private final List<Object> lst;public Producer(List<Object> lst){this.lst = lst;}@Overridepublic void run() {while(true){synchronized (lst){while(lst.size() > 0){try {lst.wait();} catch (InterruptedException e) {e.printStackTrace();}}lst.add(new Object());System.out.println("生產者:" + lst.size());lst.notifyAll();}}} }class Consumer extends Thread{private final List<Object> lst;public Consumer(List<Object> lst){this.lst = lst;}@Overridepublic void run() {while(true){synchronized (lst){while(lst.size() == 0){try {lst.wait();} catch (InterruptedException e) {e.printStackTrace();}}lst.remove(0);System.out.println("消費者:" + lst.size());lst.notifyAll();}}} }4. 通過鉤子程序捕獲程序退出以及setUncaughtExceptionHandler方法捕獲線程拋出的運行時異常
總結
以上是生活随笔為你收集整理的单线程下的生产者--消费者模式详解,wait和sleep的区别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 详解java中Thread类,线程和进程
- 下一篇: java——自己实现基础的线程池及带有任