當前位置:編程學習大全網 - 編程語言 - 線程子類如何實現多線程機制

線程子類如何實現多線程機制

來自:開發者在線

Java多線程編程的詳細分析

首先,了解多線程

多線程是壹種允許在壹個程序中同時執行多個指令流的機制。每個指令流稱為壹個線程,彼此獨立。

線程,也叫輕量級進程,和進程壹樣有獨立的執行控制,操作系統負責調度。不同的是,線程沒有獨立的存儲空間,而是與自己進程中的其他線程共享壹個存儲空間,這使得線程之間的通信比進程簡單得多。

多線程的執行是並發的,也就是邏輯上的“同時”,不管是不是物理上的“同時”。如果系統只有壹個CPU,是不可能真正“同時”的,但是因為CPU的速度非常快,用戶是感覺不到區別的,所以我們也不用在意,只要想象壹下每個線程都在同時執行就可以了。

多線程與傳統單線程在編程上最大的區別在於,由於每個線程的控制流是相互獨立的,所以每個線程之間的代碼是亂序執行的,由此產生的線程調度、同步等問題將在以後討論。

第二,用Java實現多線程。

我們不妨想象壹下,為了創建壹個新線程,我們需要做些什麽。顯然,我們必須指定這個線程要執行的代碼,而這就是我們在Java中實現多線程所需要做的壹切!

太神奇了!Java是如何做到這壹點的?穿越類!作為壹種完全面向對象的語言,Java提供了java.lang.Thread類來方便多線程編程。這個類提供了很多方法方便我們控制自己的線程,我們以後的討論將集中在這個類上。

那麽,我們如何向Java提供我們希望線程執行的代碼呢?讓我們來看看線程類。Thread類最重要的方法是run(),由Thread類的方法start()調用,提供我們線程要執行的代碼。為了指定我們自己的代碼,我們只需要覆蓋它!

方法1:繼承Thread類,重寫run()方法。我們可以在創建的Thread類的子類中覆蓋run(),並添加要由線程執行的代碼。這裏有壹個例子:

公共類MyThread擴展線程

{

int count= 1,number

公共MyThread(int num)

{

number = num

System.out.println

(“創建線程”+數字);

}

公共無效運行(){

while(true) {

System.out.println

(“線程”+數字+”:計數“+計數);

if(++count== 6)返回;

}

}

公共靜態void main(String args[])

{

for(int I = 0;

I〈5;i++)新MyThread(i+1)。start();

}

}

這種方法簡單,符合大家的習慣,但是也有壹個很大的缺點,就是如果我們的類已經繼承了壹個類(比如Applet類),就不能再繼承Thread類了。如果我們不想創建壹個新的類,我們應該做什麽?

我們不妨探索壹種新的方法:不創建Thread class的子類,而是直接使用,所以只能把我們的方法作為參數傳遞給Thread class的壹個實例,有點像回調函數。但是Java沒有指針,我們只能傳遞包含這個方法的類的壹個實例。

那麽如何限制這個類包含這個方法呢?當然是用界面啦!抽象類雖然可以滿足,但是需要繼承,我們之所以采用這種新方法,並不是為了避免繼承帶來的限制。)

Java提供了java.lang.Runnable接口來支持這種方法。

方法2:實現Runnable接口。

Runnable接口只有壹個方法,run()。我們聲明我們的類實現了Runnable接口並提供了這個方法,把我們的線程代碼寫入其中,就完成了這部分任務。但是Runnable接口對線程沒有任何支持,我們還必須創建壹個thread類的實例,這個實例是通過Thread類的構造函數public Thread(Runnable target)來實現的;去實現。這裏有壹個例子:

公共類MyThread實現Runnable

{

int count= 1,number

公共MyThread(int num)

{

number = num

system . out . println(" create thread "+$ NUMBER);

}

公共無效運行()

{

while(真)

{

System.out.println

(“線程”+數字+”:計數“+計數);

if(++count== 6)返回;

}

}

公共靜態void main(String args[])

{

for(int I = 0;I〈5;

i++)new Thread(new MyThread(I+1))。start();

}

}

嚴格來說,創建壹個Thread子類的實例也是可行的,但是必須註意,這個子類壹定不能覆蓋Thread類的run方法,否則線程會執行子類的run方法,而不是我們用來實現Runnable接口的類的run方法。妳不妨試試這個。

使用Runnable接口實現多線程,使我們能夠將所有代碼包含在壹個類中,有利於封裝。它的缺點是我們只能使用壹組代碼。如果我們想創建多個線程並讓每個線程執行不同的代碼,我們仍然需要創建額外的類。如果是這樣,在大多數情況下,可能不如直接從多個類繼承線程緊湊。

綜上所述,兩種方法各有優勢,大家可以靈活運用。

下面我們壹起研究壹下多線程使用中的壹些問題。

三、線程的四種狀態

1.新狀態:線程已創建但未執行(未調用start())。

2.可執行狀態:線程可以執行,盡管它不壹定正在執行。CPU時間可以隨時分配給線程,從而使其執行。

3.死亡狀態:正常情況下,run()的返回導致線程死亡。調用stop()或destroy()也有同樣的效果,但不建議這樣做。前者將生成壹個異常,後者將在不釋放鎖的情況下強制終止。

4.阻塞狀態:線程將不會被分配CPU時間,並且無法執行。

第四,線程的優先級

線程的優先級代表線程的重要性。當同時有多個線程處於可執行狀態並等待CPU時間時,線程調度系統根據每個線程的優先級來決定CPU時間分配給誰。優先級高的線程獲得CPU時間的機會更大,優先級低的線程沒有機會,但機會更小。

您可以調用線程類方法getPriority()和setPriority()來訪問線程優先級。線程優先級介於1(MIN_PRIORITY)和10(MAX_PRIORITY)之間,默認值為5(NORM_PRIORITY)。

動詞 (verb的縮寫)線程同步

由於同壹個進程的多個線程共享同壹個存儲空間,在帶來便利的同時,也帶來了嚴重的訪問沖突問題。Java語言提供了壹種特殊的機制來解決這種沖突,有效地避免了同壹個數據對象被多個線程同時訪問。

因為我們可以通過私有關鍵字保證數據對象只能被方法訪問,所以我們只需要為方法提出壹套機制,就是同步關鍵字,它包括兩種用途:同步方法和同步塊。

1.同步方法:通過在方法聲明中添加synchronized關鍵字來聲明同步方法。比如:

public synchronized void access val(int new val);

Synchronized方法控制對類成員變量的訪問:每個類實例對應壹個鎖,每個synchronized方法只有獲得調用該方法的類實例的鎖才能執行;否則,它所屬的線程將被阻塞。方法壹旦執行,就會獨占鎖,直到從方法返回,然後被阻塞的線程就可以獲得鎖,重新進入可執行狀態。

這種機制保證了對於同壹時刻的每個類實例,其所有聲明為synchronized的成員函數中,至多有壹個處於可執行狀態(因為至多有壹個可以獲得該類實例對應的鎖),從而有效地避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法都聲明為synchronized即可)。

在Java中,不僅有壹個類實例,而且每個類都有壹個鎖,所以我們也可以將類的靜態成員函數聲明為synchronized,以控制其對類的靜態成員變量的訪問。

synchronized方法的缺點:如果壹個大的方法被聲明為synchronized,會大大影響效率。通常,如果線程類的方法run()被聲明為synchronized,它將永遠不會成功調用該類的任何synchronized方法,因為它已經在線程的整個生命周期中運行。當然,我們可以通過將訪問類成員變量的代碼放在壹個特殊的方法中,聲明為synchronized,在main方法中調用來解決這個問題,但是Java為我們提供了壹個更好的解決方案,那就是synchronized塊。

2.synchronized塊:通過synchronized關鍵字聲明synchronized塊。語法如下:

同步(同步對象)

{

//允許訪問控制的代碼

}

synchronized塊是壹個代碼塊,其中的代碼只有被對象syncObject(如上所述,可以是類實例,也可以是類)鎖定後才能執行。具體機理和上面說的壹樣。因為它可以針對任何代碼塊,可以指定任何鎖定的對象,所以具有很高的靈活性。

六、線程阻塞

為了解決* * *共享存儲區的訪問沖突,Java引入了同步機制。現在讓我們檢查多線程對* * *共享資源的訪問。顯然,同步機制是不夠的,因為所需的資源可能沒有準備好隨時被訪問,反之,可能同時有多個資源準備好。為了解決這種情況下的訪問控制問題,Java引入了對阻塞機制的支持。

阻塞是指掛起壹個線程的執行來等待某個條件(比如某個資源準備好了),學過操作系統的同學壹定很熟悉。Java提供了大量的方法來支持阻塞。讓我們逐壹分析。

1.sleep()方法:sleep()允許妳指定壹段以毫秒為單位的時間作為參數,使得線程在指定的時間內進入阻塞狀態,無法獲得CPU時間。在指定的時間之後,線程重新進入可執行狀態。通常,sleep()用於等待資源準備就緒:在測試發現條件不滿足後,線程被阻塞壹段時間,然後重新測試,直到條件滿足。

2.suspend()和resume()方法:這兩種方法壹起使用。suspend()使線程進入阻塞狀態,不會自動恢復。只有調用其對應的resume()時,線程才能重新進入可執行狀態。通常在等待另壹個線程產生結果時使用suspend()和resume():測試發現結果還沒有產生後,線程被阻塞,另壹個線程產生結果後,調用resume()將其恢復。

3.yield()方法:yield()使線程放棄當前分配的CPU時間,但不阻塞線程,即線程仍處於可執行狀態,隨時可能被重新分配CPU時間。調用yield()的效果相當於調度器認為線程已經執行了足夠的時間,可以去另壹個線程了。

4.wait()和notify()方法:這兩種方法壹起使用。wait()使線程進入阻塞狀態,阻塞狀態有兩種形式,壹種允許以毫秒為單位指定壹段時間作為參數,另壹種沒有參數。前者允許線程在調用相應的notify()或超過指定時間時重新進入可執行狀態,後者必須調用相應的notify()。

乍壹看,它們與suspend()和resume()方法對沒有什麽不同,但實際上它們是完全不同的。區別的核心在於,上述所有方法在阻塞時都不會釋放被占用的鎖(如果被占用的話),這個相反的規則是相反的。

上述核心差異導致了壹系列細節上的差異。

首先,上面描述的方法都屬於Thread類,但是這壹對直接屬於Object類,也就是所有對象都有這壹對方法。乍壹看,這是不可思議的,但實際上這是很自然的,因為當這對方法阻塞時,需要釋放被占用的鎖,而鎖是被任何對象占有的。調用任何對象的wait()方法都會導致線程阻塞,並釋放該對象上的鎖。

調用任何對象的notify()方法都會導致通過調用對象的wait()方法而阻塞的隨機選擇的線程解除阻塞(但在獲得鎖之前不會執行)。

其次,上面描述的所有方法都可以在任何地方調用,但這對方法必須在同步的方法或塊中調用,原因很簡單,只有在同步的方法或塊中,當前線程才能持有鎖,鎖才能被釋放。

同理,調用這壹對方法的對象上的鎖必須由當前線程擁有,這樣鎖才能被釋放。因此,這壹對方法調用必須放在這樣壹個同步的方法或塊中,這個方法或塊的鎖定對象就是調用這壹對方法的對象。如果不滿足這個條件,程序仍然可以編譯,但是在運行時會出現壹個IllegalMonitorStateException異常。

wait()和notify()方法的上述特點決定了它們經常與synchronized方法或塊壹起使用。通過與操作系統的進程間通信機進行比較,我們會發現它們的相似之處:同步的方法或塊提供了類似於操作系統原語的功能,它們的執行不會受到多線程機制的幹擾,這相當於block和wakeup原語(兩種方法都聲明為同步)。

它們的結合使我們能夠在操作系統上實現壹系列精巧的進程間通信算法(如信號量算法),解決各種復雜的線程間通信問題。最後,關於wait()和notify()方法有兩點:

首先,通過調用notify()方法解除阻塞的線程是從調用對象的wait()方法阻塞的線程中隨機選擇的。我們無法預測哪個線程會被選中,所以在編程時要特別小心,避免這種不確定性帶來的問題。

第二:除了notify(),還有另壹個方法notifyAll()可以起到類似的作用。唯壹的區別是調用notifyAll()方法將會壹次解鎖所有通過調用對象的wait()方法而被阻塞的線程。當然,只有獲得鎖的線程才能進入可執行狀態。

說到堵,就不能不談死鎖。簡單分析後可以發現,無論是suspend()方法的調用,還是wait()方法的調用,如果沒有指定超時期限,都有可能導致死鎖。不幸的是,Java不支持語言層面的死鎖避免,我們必須小心避免編程中的死鎖。

上面,我們分析了在Java中實現線程阻塞的各種方法。我們重點介紹了wait()和notify()方法,因為它們功能最強大,使用起來也最靈活,但這也導致了它們的效率很低,容易出錯。在實踐中,我們應該靈活運用各種方法,以便更好地實現我們的目標。

七、守護線程

守護線程是壹種特殊的線程,與普通線程不同的是,它不是壹個應用的核心部分。當壹個應用的所有非守護線程都終止時,即使還有壹個守護線程在運行,應用也會終止,反之亦然,只要有壹個非守護線程在運行,應用就不會終止。守護線程通常用於在後臺為其他線程提供服務。

可以通過調用方法isDaemon()判斷壹個線程是否是守護線程,也可以調用方法setDaemon()將壹個線程設置為守護線程。

八、螺紋組

ThreadGroup是Java中特有的概念。在Java中,線程組是壹個類似線程組的對象,每個線程都屬於壹個唯壹的線程組。該線程組是在創建線程時指定的,並且在線程的整個生命周期內不能更改。

通過調用包含線程組類型參數的Thread類構造函數,可以指定線程所屬的線程組。如果未指定,默認情況下,該線程屬於名為system的系統線程組。

在Java中,除了預先構建的系統線程組之外,所有線程組都必須顯式創建。在Java中,除了系統線程組之外,每個線程組都屬於另壹個線程組。創建線程組時,可以指定它所屬的線程組。如果沒有指定,默認情況下它屬於系統線程組。這樣,所有線程組就形成了壹個以系統線程組為根的樹。

Java允許我們同時操作壹個線程組中的所有線程。例如,我們可以通過調用線程組的相應方法來設置所有線程的優先級,也可以啟動或阻塞所有線程。

Java的線程組機制的另壹個重要功能是線程安全。線程組機制允許我們通過分組來區分具有不同安全特性的線程,對不同組的線程進行不同的處理,並通過線程組的層次結構來支持采用不平等的安全措施。

Java的ThreadGroup類提供了大量的方法,方便我們操作線程組樹中的每壹個線程組,以及線程組中的每壹個線程。

九。摘要

在本文中,我們討論了Java多線程編程的所有方面,包括創建線程以及調度和管理多線程。我們深刻認識到多線程編程的復雜性,以及線程切換開銷導致的多線程程序效率低下,這也促使我們認真思考壹個問題:我們需要多線程嗎?什麽時候需要多線程?

多線程的核心在於多個代碼塊的並發執行,本質特征是代碼塊之間的代碼亂序執行。我們的程序是否需要多線程,取決於這是否是它的固有特性。

如果我們的程序根本不需要多個代碼塊並發執行,自然就不需要使用多線程;如果我們的程序要求多個代碼塊並發執行,但不要求亂序,那麽我們可以簡單高效地用壹個循環實現,不需要使用多線程;只有完全符合多線程的特點,多線程機制對線程間通信和線程管理的強大支持才有用武之地,這個時候使用多線程才是值得的。

來自:開發者在線

  • 上一篇:安卓原生7.0系統可以把應用安裝到記憶體卡上嗎?
  • 下一篇:編程前輩
  • copyright 2024編程學習大全網