天天看點

Java多線程程序線程Java的線程對象線程的同步機制

windows等作業系統均支援多線程程序的并發處理機制。作業系統支援多線程,使多個程式能夠并發執行,以改善資源使用率和提高系統效率;作業系統支援多線程,能夠減少程式并發時所付出的時間和空間開銷,使得開發粒度更細,并發性更好。

程序是一個程式關于某個資料集合的一次執行過程,是作業系統進行資源配置設定和保護的基本機關。程序具有以下特性:

①結構性。程序包含了資料集合和運作于其上的程式。每個程序至少由三要素組成:程式塊、資料塊和程序控制塊。程序控制塊(process control block, pcb)描述和記錄程序的動态變化過程,使程序能正确運作。

②獨立性。程序既是系統中資源配置設定和保護的基本機關,也是系統排程的獨立機關(單線程程序),每個程序都以各自獨立的速度在cpu上運作。

③動态性。程序是程式在資料集合上的一次執行過程,是動态概念。它的生命周期在多個狀态間變化,由建立而産生,由排程而執行,因等待條件而阻塞,由撤銷而消亡。程式是一組有序指令序列,是靜态概念,程式作為一種系統資源是永久存在的。

④并發性。程序的并發性是指一組程序的執行在時間上是重疊的。

⑤互動性。多個程序可以共享變量,通過共享變量實作互相通信,多個程序之間能夠協作完成一個任務。

線程是程序中能夠獨立執行的實體(控制流),是處理器排程和配置設定的基本機關。線程是程序的組成部分,每個程序内允許包含多個并發執行的線程。同一個程序中的所有線程共享程序獲得的記憶體空間和資源,但不擁有資源。

支援多線程的程序成為多線程程序。

線程的主要特性如下:

①結構性。線程是作業系統排程的基本機關,具有唯一的辨別符合線程控制塊,其中包含排程所需的一切資訊。

②動态性。線程是動态的,而且有狀态變化。當建立一個程序時,同時至少為其建立一個線程,需要時再建立其他線程。終止一個程序将導緻程序中的所有線程終止。

③并發性。同一程序的多個線程可在一個或多個處理器上并發或并行的執行,程序之間的并發執行演變為線程之間的并發執行。在單處理器上,從宏觀上看,在一個時間段中有幾個線程都處于運作狀态;在微觀上看,任意時刻僅有一個線程在處理器上運作。并發的實質是一個處理器在多個線程之間的多路複用,是對有限的實體資源強制行使多使用者共享,消除計算機不見之間的互等現象,提高系統資源使用率。

④共享性。同一程序的所有線程共享但部擁有程序的狀态和資源,且駐留在程序的記憶體空間中,可以通路相同的資料。所有線程之間需要有通信和同步機制。

線程在其生命周期中經曆着狀态的變化,線程狀态包括5種:建立、就緒、運作、阻塞、終止。

就緒(ready)态——程序具備運作條件,等待系統配置設定處理器以便運作。

運作(running)态——進場占用處理器正在運作。

阻塞(blocked)态——程序不具備運作條件,正在等待某個事件的完成。

線程在執行過程中的任一時刻,處于一種狀态,根據運作條件在多個狀态之間轉變。一個程序建立後處于就緒态,運作中因等待條件處于阻塞态。

任一時刻隻有一個線程能夠占用一個處理器運作,按照什麼原則決定就緒隊列中的哪個線程能夠獲得處理器就是線程排程的任務。

線程排程的功能就是按照某種原則選擇一個線程使它獲得處理器運作。線程排程是作業系統的核心部分,線程排程政策的優劣直接影響到作業系統的性能。

線程排程采用剝奪方式,當一個線程正在處理器上執行時,作業系統可以根據規定的原則剝奪它的處理器使用權,而把處理器配置設定給其他線程使用。常用的剝奪原則有兩種:一是高優先級線程可以剝奪低優先級線程運作;二是當運作線程時間使用完後被剝奪處理器。

順序程式設計方法是指,程式子產品按照語句次序順序執行,其特性為:①執行的順序性;②運作環境的封閉性;③執行結果的确定性;④計算結果的可再現性。

并發程式設計方法是指,将一個程式分為若幹可同時執行的程式子產品,每個程式子產品和它執行時所處理的資料結合組成一個程序。作業系統以程序作為系統資源配置設定的基本機關,以線程系統排程的基本機關。其特性如下:

①并發執行的線程之間不具有順序性。線程由作業系統排程執行,不會按照語句的書寫順序執行。

②運作環境不再是封閉的,一個線程的執行可能影響其他線程的執行結果。(計算過程不可再現)

③共享變量的多個線程(成為互動線程)之間實作線程通信,能夠協作完成一個任務,也會出現與時間有關的錯誤。

④并發多線程程式設計的優點是,提高了系統性能,具體表現為快速切換線程、減少系統管理開銷、線程通信易于實作、并發按程式提高、節省記憶體空間。

java支援内置的多線程機制。java語言包中的runnable接口約定線程的執行方法,thread類提供建立、管理和控制線程對象的方法。

在java中可有兩種方式實作多線程,一種是繼承thread類,一種是實作runnable接口;thread類是在java.lang包中定義的。一個類隻要繼承了thread類同時覆寫了本類中的run()方法就可以實作多線程操作了,但是一個類隻能繼承一個父類,這是此方法的局限。

在jdk的安裝路徑下,src.zip是全部的java源程式,通過此代碼找到thread中的start()方法的定義,可以發現此方法中使用了private native void start0();其中native關鍵字表示可以調用作業系統的底層函數,那麼這樣的技術成為jni技術(java native interface)。

在實際開發中一個多線程的操作很少使用thread類,而是通過runnable接口完成。但是在使用runnable定義的子類中沒有start()方法,隻有thread類中才有。此時觀察thread類,有一個構造方法:public thread(runnable targer)此構造方法接受runnable的子類執行個體,也就是說可以通過thread類來啟動runnable實作的多線程。(start()可以協調系統的資源)

兩種實作方式的差別和聯系:

在程式開發中隻要是多線程肯定永遠以實作runnable接口為主,因為實作runnable接口相比繼承thread類有如下好處:

①适合多個相同的程式代碼的線程去處理同一個資源(适合于資源共享)

②可以避免java中的單繼承的限制

③增加程式的健壯性,代碼可以被多個線程共享,代碼和資料獨立。

用一個買票程式來說明兩種實作方式的差別:

繼承thread類:

輸出結果為:

實作runnable接口:

如果多次執行,可以發現上述程式可能出現賣出編号為0的票的情況,這裡涉及線程的同步機制,将在後續文章中提到。

java提供10個等級的線程優先級,分别用1~10表示,優先級最低為1,最高為10,預設值是5。thread類聲明了以下三個表示優先級的公有靜态常量:

public static final int min_priority = 1; //最低優先級

public static final int max_priority = 2; //最高優先級

public static final int norm_priority = 3; //預設優先級

每個線程兌現建立時自動獲得預設優先級5,調用setpriority()方法可以改變線程對象的優先級。

6種線程狀态:

①建立态(new)。已建立,未啟動。

②運作态(runnable)。從作業系統角度看,處于建立态的線程啟動後,進入就緒态,再由作業系統排程執行而成為運作态。由于線程排程由作業系統控制和管理,程式無法控制,無法區分就緒态和運作态。是以,從程式設計角度看,線程啟動後即進入運作态runnable。進入運作态的線程執行其run()方法。

③阻塞态(blocked)和等待态。一個運作态的線程因某種原因不嫩繼續運作時,進入阻塞态或等待态。等待态有兩種:waiting(等待時間不确定)和timed_waiting(等待時間确定)。

④終止态(terminated)。線程對象停止運作未被撤銷時是終止态。

thread類中改變和判斷線程狀态的方法:

start() 建立态到運作态。

isalive() 判斷線程是否為活動狀态。當一個線程未被終止時,傳回true,此時線程處于運作态、阻塞态、等待态之一。但一個線程未啟動或已終止時,傳回false。

sleep() 方法使目前程序停止執行若幹毫秒,線程由運作态進行等待态,睡眠時間到,線程可再次進行運作态。

interrupt()方法為目前線程設定一個中斷标記,以便于run()方法運作時使用isinterrupted()能夠檢測到。此時,線程愛sleep()之類的方法中被阻塞時,由sleep()方法抛出java.lang.interruptedexception,線程中斷異常,可捕獲這個異常進行中斷處理操作。

interrupt()隻是為線程設定一個中斷标記,并沒有中斷線程運作,該方法沒有抛出異常。一個線程被設定了中斷标記後仍可運作,isalive()傳回true。 當抛出一個interruptedexception異常時,記錄該線程中斷情況的标記将會被清除,這樣再調用isinterrupted()将傳回false。

互動線程間存在兩種關系:競争關系和協作關系。

對于競争關系的互動線程間需要采用線程互斥方式解決共享資源沖突問題;對于協作關系的互動線程間需要采用線程同步方式解決線程間通信及因執行速度不一緻而引起的不同步問題。

互動的并發線程是指他們共享某些變量,一個線程的執行可能影響到其他線程的執行結果,互動的并發線程之間具有制約關系。

無關線程間并發執行時,不會産生于時間有關的錯誤。

資源競争出現了兩個問題:一個是死鎖(deadlock)問題,一組線程如果都獲得了部分資源,還想得到其他線程所占用的資源,最終所有的線程将陷入死鎖;另一個是饑餓(starvation)問題,一個線程由于其他線程總是優先于它而被無限期拖延。

線程互斥是解決線程間競争關系的手段。線程互斥(mutual exclusion)是指若幹個線程要使用同一共享資源時,任何時刻最多允許一個線程去使用,其他要使用該資源的線程必須等待,直到占有資源的線程釋放該資源。

共享變量代表的資源成為臨界資源(critical resource),并發線程中與共享變量有關的程式段成為臨界區(critical section)。由于與同一變量有關的臨界區分散在各有關線程的程式段中,而各線程的執行速度不可預知,是以,作業系統對共享一個變量的若幹線程各自進入臨界區有以下3個排程原則:

①一次至多一個線程能夠在它的臨界區内

②不能讓一個線程無限期的停留在它的臨界區内

③不能強迫一個線程無限地等待進入它的臨界區。特别地,進入臨界區的任一線程不能妨礙等待進入的其他線程的進展。

把臨界區的排程原則總結成四句話:無空等待、有空讓進、擇一而入、算法可行。

作業系統提供“互斥鎖”機制實作并發線程互斥地進入臨界區,對共享資源進行操作。

java提供關鍵字synchronized用于聲明一段程式為臨界區,使線程對臨界資源采用互斥使用方式。synchronized有兩種用法:聲明一條語句,或者聲明一個方法。

①同步語句

synchronized(對象)

語句

其中,對象是多個線程共同操作的公共變量,即需要被鎖定的臨界資源。

②同步方法

synchronized 方法聲明

同步方法的方法體成為臨界區,互斥使用(鎖定)的是調用該方法的對象。

當合作線程中的一個到達協調點後,在尚未得到其夥伴線程發來的信号之前應先阻塞自己,直到其他合作線程發來協調信号後方被喚醒并繼續執行。這種協作線程之間互相等待對方消息或信号的協調關系成為線程同步。

線程同步是解決線程間協作關系的手段。線程同步(synchronization)是指兩個以上線程基于某個條件來協調它們的活動。一個線程的執行依賴于另一個線程的信号,當一個線程沒有得到來自于另一個線程的信号時則需要等待,直到信号到達才被喚醒。

線程互斥是一種特殊的線程同步機制,即逐次使用互斥共享資源,也是對線程使用資源次序上的一種協調。

作業系統實作線程同步有一種成為信号量和pv操作。測試信号量狀态的操作成為p操作,改變信号量狀态的操作稱為v操作,這兩種操作是互斥的,并且執行時不能被打斷。多個線程之間彼此根據信号量的狀态确定誰該執行。

java.lang.object類提供wait()、notify()、和notifyall()方法實作線程間通信。

注意: 無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖—-而且同步方法很可能還會被其他線程的對象通路。 每個對象隻有一個鎖(lock)與之相關聯。 實作同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,是以盡量避免無謂的同步控制。

synchronized作為函數修飾符時,它鎖定的是調用這個同步方法對象。也就是說,當一個對象p1在不同的線程中執行這個同步方法時,它們之間會形成互斥,達到同步的效果。但是這個對象所屬的class所産生的另一對象p2卻可以任意調用這個被加了synchronized關鍵字的方法。