在進行多線程程式設計中,比較重要也是比較困難的一個操作就是如何擷取線程中的資訊。大多數人會采取比較常見的一種方法就是将線程中要傳回的結果存儲在一個字段中,然後再提供一個擷取方法将這個字段的内容傳回給該方法的調用者。如以下的returnthreadinfo類:
大家可以看到該類是一個線程類并含有一個初始值為"hello"的字段str以及一個可以傳回str值的方法:getthreadinfo(),而且當這個線程啟動後str會被賦于新值:"hello world!"。現在我想在另外一個類中啟動returnthreadinfo線程,并通過getthreadinfo()方法擷取值為"hello world!"的變量并列印輸出到控制台中。以下給出一個實作該功能的main類:
以上是一個多數熟悉單線程程式設計的人在第一反應下給出的實作方法。但是該類在運作的時候輸出的結果卻不是期望的"hello world!"而是"hello",這是由于線程的競争條件導緻的(由于returnthreadinfo線程和main線程的優先級都為5,是以在很大幾率上returnthreadinfo線程的run()方法還沒有運作,main類就已經運作system.out.println(returnthreadinfo.getthreadinfo());将"hello"輸出了。具體的原理可以參見另一篇文章:"java多線程的幾點誤區")。有的人可能會立即想到把returnthreadinfo線程的優先級設高些(比如最大的10)就可以returnthreadinfo線程的run()方法先運作完,然後main類的system.out.println(returnthreadinfo.getthreadinfo())再運作,這樣輸出的結就一定是期望的"hello world!"了。這種通過調整線程優先級的方法固然可以在某種程度上解決該問題,但是線程争用cpu運作時間的原理卻決不僅僅隻是優先級高低的原因(優先級高的線程并不意味着一定比優先級低的線程先運作,隻是幾率要更大一些)。你并不希望returnthreadinfo線程9999次都比main先運作,卻在最關鍵的一次在main之後再運作。是以下面給出兩種比較常見的擷取線程資訊的方法:
一、輪詢
比較常見的一種解決方案是,讓線程類擷取方法在結果字段設定之前傳回一個标志值。然後主線程定時詢問擷取方法,看是否傳回了标志之外的值。以下給出了具體的實作方法,該方法不斷測試str的值是否為"hello",如果不為"hello"才列印輸出它。例如:
這種方案雖然能起到作用,但是它做了大量不需要做的工作。事實上,還有一種更簡單有效的方法來解決這個問題。
二、回調
輪詢方法最大的特點是主類main不斷詢問線程類是否結束,這實際上大量浪費了運作時間,特别是當線程特别多的時候。是以如果反過來線上程結束時,由線程自己告訴主類main線程已經結束,然後main再擷取并輸出str的值,這樣就避免了輪詢方法所帶來的不必要的系統開銷問題。
在具體的實作過程中,線程可以在結束時通過調用主類中的一個方法來實作告知功能,這種方法叫做回調。這樣主類main就可以在等待線程結束時休息,也就不會占用運作線程的時間。下面是修改後的main類:
相比于前面,我們在main類中添加了一個靜态方法receivestr(string str),該方法是供線程結束之前調用,通過參數str将要傳回的線程資訊傳回給main類并輸出顯示出來。下面是修改後的returnthreadinfo類,該類線上程結束前回調了main.receivestr方法,通知線程已結束。
如果有很多個對象關心線程的傳回的資訊,線程可以儲存一個回調對象清單。某個對象可以通過已經定義的一個對象将自己添加到清單中,表示自己對這些資訊的關注。如果有多個類的執行個體關心這些資訊,也可以定義一個interface,在interface中聲名回調方法,然後這些類都實作這個接口。其實這是典型的java處理事件的方法,這麼做可以使得回調更靈活,可以處理涉及更多線程、對象和類的情況。稍後會給出這種模仿事件處理模型的回調的實作方法。