1. 程式人生 > >關於執行緒暫停或指示執行緒準備暫停的十種方式介紹

關於執行緒暫停或指示執行緒準備暫停的十種方式介紹

文章來源:

參考內容:

前言:

在協作式執行緒排程器或者搶佔式執行緒排程器上經常需要確保所有執行緒自身定期的暫停,這樣其他的執行緒才可以有執行的機會.為了能讓其他執行緒有機會執行,一個執行緒有10種方式可以暫停或者指示它準備暫停.主要分為下面幾種方式:

(1)可以對I/O阻塞 (2)可以對同步物件阻塞 (3)可以放棄 (4)可以休眠 (5)可以連線另一個執行緒

(6)可以等待一個物件 (7)可以結束 (8)可以被更高階優先順序執行緒搶佔

要檢查你編寫的每一個run()方法,確保這些條件之一會以合理的頻率出現.最後兩種可能性已經廢棄不用,因為他們會讓物件處於不一致的狀態,所以我們來看能夠讓執行緒稱為虛擬機器中協作的一員的其他8種方法.

一、阻塞

1 不論是對I/O阻塞還是對鎖阻塞,都不會釋放執行緒已經擁有的鎖.

任何時候執行緒必須停下來等待它沒有的資源時,就會發生阻塞.要讓網路程式中的執行緒自動放棄CPU控制權,最常見的方式是對I/O阻塞.由於CPU比網路和磁碟快得多,網路程式經常會等待資料從網路到達或向網路傳送資料時阻塞.即使只阻塞幾毫秒,這一點時間也足夠其他執行緒用來完成重要的任務.

執行緒在進入一個同步方法或程式碼塊時也會阻塞.如果這個執行緒沒有所同步物件的鎖,而其他執行緒擁有這個鎖,這個執行緒就會暫停,直到鎖被釋放為止.如果這個鎖永遠也不會釋放,那麼這個執行緒會永久停止.這種情況很常見,對於一個類中的某個方法,當存在多個執行緒執行當前類的同步方法時,上述描述的場景就會出現,比如說下面的傳送請求的方法:

    /**
     * 傳送請求
     * @param events 請求事件集合
     * @param compressed 是否壓縮
     * @return Lumberjack協議返回內容轉換的響應物件
     * @throws AdapterException
     * @throws IOException
     */
    private synchronized  LumberjackResponse send(List<Event> events, boolean compressed) throws AdapterException, IOException {
        int
numberOfEvents = events.size();// 批量請求的數量 List<String> jsonList = new ArrayList<String>(numberOfEvents);// 請求的json列表 for (Event event : events) { jsonList.add(event.toJSONString()); } String jsonResult; sendWindowSizeFrame(numberOfEvents);// 傳送window頭 sendDataFrames(jsonList, compressed);// 傳送json報文 jsonResult = readResponse(numberOfEvents);// 讀取響應報文 LumberjackResponse response = new LumberjackResponse();// 封裝響應實體 response.setJsonResult(jsonResult); return response; }

上述方法在被多個執行緒呼叫之前,如果當前執行緒沒有所同步物件的鎖,那麼這個執行緒就會暫停,直到鎖被釋放為止.

二、放棄

1 當前執行緒放棄並不會釋放自身擁有的鎖.

2 理想情況下,線上程放棄時不應當做任何同步.

要讓執行緒放棄控制權,第二種方式是顯式的放棄.為此執行緒可以通過呼叫Thread類的yield()靜態方法來做到.這將通知虛擬機器,如果有另一個執行緒準備執行,可以執行該執行緒.有些虛擬機器(特別是在實時作業系統上)會忽略這個提示.

在放棄之前,執行緒應當確保它或與它關聯的Runnable物件處於一致狀態,可以由其他物件使用.放棄並不會釋放這個執行緒擁有的鎖.因此,在理想情況下,線上程放棄時不應當做任何同步.一個執行緒放棄時,如果等待執行的其他執行緒都是因為需要這個執行緒所擁有的同步資源而阻塞,那麼這些執行緒將不能執行.實際上,控制權將回到唯一可以執行的執行緒,即剛剛放棄的這個執行緒,這很大意義上失去了放棄的意義.

在實際中讓執行緒放棄非常簡單.如果執行緒的run()方法只包含一個無線迴圈,那麼只要在迴圈的末尾加一個Thread.yield()呼叫.例如:

public void run(){
    while(true){
        /*
         * 完成執行緒的工作
         */

        Thread.yield();
    }
}

這會使其他有相同優先順序的執行緒有機會執行.如果每次迴圈迭代都要花費很多時間,你可能希望在程式碼的其餘部分散步更多的Thread.yield呼叫.在沒有必要的情況下,這種防範措施效果並不是十分明顯.

三、休眠

1 應當避免在同步方法或塊內讓執行緒休眠

2 進入休眠的執行緒仍然擁有它已經獲得的所有鎖.

休眠是更有力的放棄方式.放棄只是表示執行緒願意暫停,讓其他有相同優先順序的執行緒有機會執行,而進入休眠的執行緒有所不同,不管有沒有其他執行緒準備執行,休眠執行緒都會暫停.這樣一來,不只是其他有相同優先順序的執行緒得到機會,還會給較低優先順序的執行緒一個執行的機會.不過,進入休眠的執行緒仍然擁有它已經獲得的所有鎖.因此,其他需要相同的執行緒會阻塞,及時CPU可用.所以要避免在同步方法或塊內讓執行緒休眠.

通過呼叫下面兩個過載的Thread.sleep()靜態方法之一,執行緒可以進入休眠.第一個方法接收要休眠的毫秒數作為引數.第二個接受毫秒數和毫微妙數.
這裡寫圖片描述
這裡寫圖片描述
雖然多數現代計算機時鐘至少有接近毫秒級的精確度,但是精確度到達毫微秒級的極少.不能保證在任何虛擬機器上都能將實際的休眠時間控制在毫微秒甚至毫秒級.如果本地硬體不支援這個精度,休眠時間將舍入為可測量的最接近的值.例如:下面的run()方法嘗試每五分鐘載入一個頁面,如果失敗,就像web管理員發email提醒這個問題:

public void run(){
    while(true){
        if(!.getPage("http://www.ibiblio.org/")){
            mailError("郵箱地址");
        }
        try{
            Thread.sleep(300000);//五分鐘
        }catch(InterruptedException e){
            break;
        }
    }
}

執行緒不能絕對保證一定會休眠所期望的那麼長時間.有時,在請求呼叫之後過一段時間執行緒才會真正喚醒,因為VM正在忙於做其他的事情.也可能時間還未到,但是存在其他的執行緒完成了一些操作而喚醒了休眠的執行緒.一般情況下,這是通過呼叫休眠現成的interrupted()方法完成來實現的.

這裡寫圖片描述

有些情況下執行緒與Thread物件之間的區別很重要,這裡就是如此.一個執行緒在休眠,並不意味著其他醒著的執行緒不能處理這個執行緒相應的Thread物件(通過它的方法和欄位).具體地,另一個執行緒可以呼叫Thread物件的interrupt()方法,這會讓休眠中的執行緒得到一個InterruptedException異常.再次之後,這個執行緒會被喚醒並正常執行,至少在再次進入休眠之前會正常執行.在前面的例子中,使用了一個InterruptedException來結束一個執行緒,否則這個執行緒會永遠執行下去,丟擲InterruptedException時,就會打破無限迴圈,run()方法結束.相應的執行緒將會停止.使用者選擇選單中的Exit或指示希望程式退出時,使用者介面執行緒會呼叫這個執行緒的interrupt()方法.

四、連線執行緒

一個執行緒可能需要另一個執行緒的結果,這種情況是很常見的.例如,Web瀏覽器在一個執行緒中載入HTML頁面,它可能要生成一個單獨的執行緒來獲取頁面中嵌入的各個圖片.如果IMG元素沒有指定HEIGHT和WIDTH,主執行緒在結束頁面的顯示之前,允許一個執行緒在繼續執行前等待另一個執行緒結束.這些方法是:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

其中:

public final void join() throws InterruptedException

方法無限等待被連線(joined)的執行緒結束.後面兩個方法會等待指定的一段時間,然後會繼續執行,即使被連線的執行緒還沒有結束.與sleep()方法一樣,不能保證毫微秒級的精度.

連線執行緒(即呼叫join()方法的執行緒)等待被連線的執行緒(也就是說,呼叫的是這個執行緒的join()方法)結束.例如,考慮下面的這段程式碼.我們希望找到一個隨機double陣列中的最小數/最大數/中間數.用有序陣列能夠更快的完成.我們生成一個新執行緒對陣列排序,然後連線到這個執行緒等待它的結果.只有當它結束時,才會讀取所需的值.

這裡寫圖片描述

前面第1行到第4行先執行,用隨機數填充陣列.然後第5行建立一個新的SortThread.第6行啟動這個執行緒對陣列排序.在找到陣列的最小數/中間數/最大數之前,需要等待排序執行緒結束.因此,第8行將當前執行緒連線到排序執行緒.至此,執行這些程式碼的執行緒會停止執行.它會等待排序執行緒結束執行.直到排序執行緒結束執行並撤銷之後,第9行到第11行才會獲取最小/中間/最大值.注意在這裡沒有引用暫停的執行緒.並不是呼叫這個Thread物件的join()方法;它沒有作為引數傳遞給該方法,而只是隱式地作為當前執行緒存在.如果是程式main()方法的正常控制流中,可能任何地方都沒有指向這個執行緒的Thread變數.

連線到另一個執行緒的執行緒可以被中斷,如果有其他執行緒呼叫其interrupt()方法,它就會向休眠執行緒一樣被中斷.執行緒將這個呼叫作為一個InterruptedException異常.此後,它會從捕獲這個異常的catch塊開始正常執行.在前面的例子中,如果執行緒被中斷,他將跳過最大值/最小值/中間值的計算,因為如果排序執行緒在結束前被中斷,這些值是不可用的.

五、等待一個物件

執行緒可以等待(wait)一個它鎖定的物件.在等待時,它會釋放這個物件的鎖並暫停,直到它得到其他執行緒的通知.另一個執行緒以某種方式修改這個物件,通知等待物件的執行緒,然後繼續執行.這與連線執行緒不同,並不要求等待執行緒和通知執行緒在另一個執行緒繼續前必須結束.等待會暫停執行,直到一個物件或資源到達某種狀態.連線也會暫停執行,不過是直到一個執行緒結束.

在暫停執行緒的方法中,等待一個物件的做法並不太出名.這是因為它不涉及Thread類的任何方法.實際上,要等待某個特定的物件,希望暫停的執行緒首先必須使用synchronized獲得這個物件的鎖,然後呼叫這個物件的三個過載wait()方法之一:

這裡寫圖片描述

這些方法不在Thread類中,而是在java.lang.Object類中.因此,可以在任何類的任何物件上呼叫這些方法.呼叫其中一個方法時.呼叫它的執行緒會釋放所等待的物件的鎖(但不會釋放它擁有的其他物件的鎖),並進入休眠.執行緒會保持休眠,知道發生以下三種情況之一:

  • 時間到期

  • 執行緒被中斷

  • 物件得到通知

    超時時間(timeout)與sleep()和join()方法中的超時時間相同,即執行緒經過指定的一段時間後(在本地硬體時鐘的精度範圍內)會喚醒.當時間到期時,執行緒會從緊挨著wait()呼叫之後的語句繼續執行.不過,如果執行緒不能立即重新獲得所等待的物件的鎖,該執行緒可能還需要阻塞一段時間.

中斷(Interruption)與sleep()和join()的工作方式相同:其他執行緒呼叫這個執行緒的interrupt()方法.這將導致一個InterruptedException異常,並捕獲這個異常的catch塊內繼續執行.不過,在丟擲異常前重新獲得所等待物件的鎖,所以呼叫interrupt()方法後,該執行緒可能仍要阻塞一段時間.

第三種可能的方法時通知(notification),這是一個新方法.在其他執行緒在這個執行緒所等待的物件上呼叫notify()或notifyAll()方法時,就會發生通知.這兩個方法都在java.lang.Object類中:

這裡寫圖片描述

這兩個方法都必須線上程所等待的物件上呼叫,而不是在Thread本身呼叫.在通知一個物件之前,執行緒必須首先使用同步方法或塊獲得這個物件的鎖.

notify()基本上隨機地從等待這個物件的執行緒列表中選擇一個執行緒,並將它喚醒.notifyAll()方法會喚醒等待指定物件的每一個執行緒.

一旦等到執行緒得到通知,它就試圖重新獲得所等待物件的鎖.如果成功,就會從緊接著wait()呼叫之後的語句繼續執行.如果失敗,它就會對這個物件阻塞,直到可以得到鎖,然後繼續執行緊接著wait()呼叫之後的語句.

六、結束

執行緒要以合理的方式放棄CPU控制權,最後一種方法時結束(finishing).當run()方法返回時,執行緒將撤銷,其他執行緒可以接管CPU.在網路應用程式中,包裝一個阻塞操作的執行緒往往會這樣做,例如從一個伺服器下載一個檔案,這樣應用程式的其他部分就不會被阻塞.

另一方面,如果run()方法太簡單,總是很快就結束,而不會阻塞,那就存在一個很實際的問題:到底有沒有必要生成一個執行緒.虛擬機器在建立和撤銷執行緒時會有很大的開銷.如果執行緒會在極短的時間內結束,那麼使用一次簡單的方法呼叫而不是單獨的執行緒可能會結束的更快.

相關推薦

關於執行暫停指示執行準備暫停方式介紹

文章來源: 參考內容: 前言: 在協作式執行緒排程器或者搶佔式執行緒排程器上經常需要確保所有執行緒自身定期的暫停,這樣其他的執行緒才可以有執行的機會.為了能讓其他執行緒有機會執行,一個執行緒有10種方式可以暫停或者指示它準備暫停.主要分為下面幾種方

三個執行輪流執行順序列印ABC,依次是ABCABCABC......(三方式

1.使用synchronized悲觀鎖 (秋招阿里的一個筆試題,應該寫的比較複雜,然後就沒有然後了o(╥﹏╥)o) public class ThreadThreadp { private int flag = 0; public synch

Java中執行安全的加一(+1)操作的三方式

1.鎖分為樂觀鎖和悲觀鎖,悲觀鎖總是假設每次的臨界區操作會產生衝突,如果多個執行緒同時需要訪問臨界區資源,就寧可犧牲效能讓執行緒進行等待。而樂觀鎖,它會假設對資源的訪問都是沒有衝突的,所有的執行緒都可以在不停頓的狀態下持續執行,如果遇到衝突,樂觀鎖採用的叫做比較交換(CAS

Java多執行併發01——執行的建立與終止,你會幾方式

> 本文開始將開始介紹 Java 多執行緒與併發相關的知識,多謝各位一直以來的關注與支援。關注我的公眾號「Java面典」瞭解更多 Java 相關知識點。 # 執行緒的建立方式 在 Java 中,使用者常用的主動建立執行緒的方式有三種,分別是 **繼承 Thread 類**、**實現 Runnable 介面

代理輸入完整的網頁(用於顯示執行時資料顯示執行結果)

 Sub Initialize '多層結構的使用者名稱的UTF-8編碼後的URL,即是最終需要的字串,例如"翟文輝/東莞市"->"http://fax2.dgoa.cn/login.aspx?dominoauthor=%E7%BF%9F%E6%96%87%E8%BE%8

Unity實用小工具指令碼—載入外部圖片的三方式

一、前言 專案上需要載入多個地圖,每個地圖的貼圖將近200M,所有地圖加起來貼圖將近2G。因此,想著能不能將貼圖放到工程外載入,逐研究了一下,得出了三種載入方式,分別是WWW載入、C#原生的IO載入在轉換成Texture2D和Assetbundle打包和載入。

java通過Http請求訪問網路圖片檔案返回byte陣列的兩方式

第一種方式,使用HttpURLConnection 使用HttpURlConnection傳送一個get請求,開啟一個連線,從連接獲取到流,將流轉成byte陣列 /** * 發起Get請求 * * @param urlStr * @

一個執行控制另一個執行暫停啟動

MainTest類中可以控制執行緒的暫停或繼續執行。 public class MainTest { /** * 這個執行緒操作另一個執行緒的暫停或開始 * @param args */ public static void main(String[] args) {

VC如何對新建立的一個執行暫停重啟

執行AfxBeginThread()函式時,如果成功則返回一個指向新執行緒物件的CWinThread指標,否則為NULL。就是說它會new一個CWinThread物件,而這個物件線上程執行結束時是會自動刪除的。 這樣只要得到這一新執行緒的指標,我們就可以對新執行緒進行操作了

JAVA裡面如何讓一個執行死亡結束

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

談談並行、併發執行

1.CPU的發展趨勢:      核心數目依舊會越來越多,根據摩爾定律,由於單個核心效能提升有著嚴重的瓶頸問題,普通的PC桌面在2018年可能回到24核心。 2.併發和並行的區別:      所有的併發處理都有排隊等候,喚醒和執

Android ActivityThread 主執行UI執行 簡介

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

java之執行建立的兩方式,六狀態和匿名內部類建立子類實現類物件

一.匿名內部類建立子類或實現類物件 new Test(){} 相當於建立了Test類的子類物件 並且沒有類名 建立介面實現類 new 介面名() {};介面實現類的物件 注意 : new 後邊是類或者介面名 大括號內是類或者介面中的方法 public

執行(二)使用多執行準備知識

一、為什麼要使用多執行緒?         【使計算機所有資源在執行任務的時候能夠全部利用上,以提升計算機資源利用率的方式來提升系統執行效率】   CPU的單核執行速度由於硬體技術問題已經遇到瓶頸,而概念性的“光腦”貌似離我們還很遙遠,現在

啟動執行的五方式方法(通過繼承Thread類實現Runnable介面)

package day01; /**  * 啟動執行緒的五種方式方法:  * 本質:繼承Thread類或實現runnable介面  * @author Administrator  *  */ public class ThreadDemo {  /**   * @para

Flask 開啟多程序執行

Flask 預設是單程序,單執行緒阻塞的任務模式,在專案上線的時候可以通過nginx+gunicorn 的方式部署flask任務但是在開發的過程中如果想通過延遲的方式測試高併發怎麼實現呢,其實非常簡單app.run()中可以接受兩個引數,分別是threaded和process

禁止在視窗介面執行中進行執行同步進行耗時的操作。。。

最近工作時,經常碰到窗口出現未響應的現象,後面發現原因是這樣的: 1、使用者點選XX按鈕時,在XX按鈕事件中呼叫了aa.dll中的介面XXXAPI_TEST(); 2、aa.dll中的介面XXXAPI_TEST()內部有進行執行緒同步的操作。 3、因為執行緒同步的原因,導致

執行委託之跨執行問題分析--在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke BeginInvoke(解決方法已更新)

檢視巢狀檢視+groupby+sum+if超慢?檢視巢狀檢視+groupby+sum+if超慢? 炯蕉蔚郝iar貉k湯秤TP2Fx扯訃詬壤撞蝸 《 http://babyknow.baidu.com/article/1376a5480527629546e457877078

JAVA——守護執行使用者執行(setDaemon)

class StopThread implements Runnable { private boolean flag = true; public synchronized voi

執行1:AtomicInteger的使用,多執行疊加疊減

import java.util.concurrent.atomic.AtomicInteger;   publicclass AtomicIntegerTest {       public AtomicInteger inc = new AtomicInteger();       publicv