1. 程式人生 > >Java中的線程(一)

Java中的線程(一)

java線程

一、線程與進程

談到線程,那就不得不提進程,很久之前其實並沒有線程,只有進程,當一個程序需要運行的時候,必然需要使用系統資源和CPU,

因此進程就擔任了對一個應用程序進行資源分配以及CPU調度這兩項職責。後來,為了進一步提高並發執行和資源利用的效率,提出了

線程的概念,將進程作了細分,進程將負責資源的分配,線程來負責CPU的調度。因此可以做以總結,進程是進程是可並發執行的程

序在一個數據集上的一次執行過程,它是系統進行資源分配的基本單位, 線程為進程所有,作為調度執行的基本單位,一個進程可以

有一個或多個線程,他們共享所屬進程所擁有的資源。

二、Java中的進程與線程

我們知道Java程序的運行必須依托於JVM,每當我們運行一個Java程序,都會啟動一個JVM進程,該JVM的生命周期與程序的生命周期

一致,也就是當程序運行結束或遇到異常退出時,JVM進程也就退出了,JVM就一個職責,運行Java程序。

當JVM運行java程序時,他會創建一個主線程,這個主線程執行的入口是main()方法。JVM虛擬機執行代碼的任務全部由線程完成,

每個線程具有自己單獨的程序計數器和方法調用棧。


程序計數器:當線程執行下一個方法時,程序計數器指向方法區中下一條要執行的字節碼指令。

方法調用棧:簡稱方法棧,用來跟蹤線程運行中一系列的方法調用過程,棧中的元素成為棧幀。每當線程調用一個方法的時候,

就會向方法棧壓入一個新幀。幀用來存儲方法的參數、局部變量和運算過程中的臨時數據。

棧幀由以下三個部分組成:

局部變量區:存放局部變量和方法參數

操作數棧:是線程的工作區,用來存放運算過程中生成的臨時數據。

棧數據區:為線程執行指令提供相關的信息,包括如何定位到位於堆區和方法區的特定數據,以及如何正常退出方法或者異常終端方法

就以下代碼分析java線程的運行過程:

public class Sample{  
		private int a = 0;  
		public int method(){  
					int b;  
			a++;  
			return a;  
		}  
		  
		public static void main(String args[]){  
			Sample s =new Sample();  
				s.method();  
					System.out.println(a);  
		}  
	}


當啟動JVM運行上面程序時,JVM首先創建一個主線程,該線程具有自己的程序計數器和方法棧,開始進入main()方法,進入之後他會將

main()方法的棧幀壓入方法棧,將方法的局部變量,參數,以及運算過程的臨時數據分別存入棧幀中的局部變量區和操作數棧,同理進入method

後會壓入method()的棧幀,主線程能根據method()方法的棧幀的棧數據區中的有關信息,正確定位到堆區的Sample對象的實例變量a,並把它的

值加1,method執行完成之後就彈出method()的棧幀繼續指向main()方法。

三、Java線程的創建方法(4中)

繼承Thread類

實現Runnable接口

線程池

實現Callable接口

Runnable方式的好處(區別):

(1)、將線程的任務從線程的子類中分離出來,進行了單獨的封裝。按照面向對象的思想將任務

封裝成對象

(2)、避免了java單繼承的局限性

所以創建線程的第二種方式較為常用

四、線程的狀態(5種)

1.新建狀態(New)

條件:通過New語句創建

特點:僅在堆區中被分配了內存

2.就緒狀態(Runnable)

條件:當一個線程對象創建後,其他線程調用它的start()方法

特點:Java虛擬機會為它創建方法調用棧和程序計數器,該狀態線程位於可運行池中, 等待獲得CPU的使用權。

3.運行狀態(Running)

條件:Runnable狀態的線程搶占到CPU進入Running狀態(只有Runnable狀態才可轉到運行狀態)

特點:獨占一個CPU

4.阻塞狀態(Blocked)

阻塞狀態具體可分以下三種情況:

Blocked in object’s wait pool (位於對象等待池中的阻塞狀態)

條件: wait()

 Blocked in object’s lock pool (位於對象鎖池中的阻塞狀態)

條件:等待獲取同步鎖時

 Otherwise Blocked (其他阻塞狀態)

條件:sleep() join() I/O請求(System.out.println()或System.in.read())

5.死亡狀態(Dead)

條件:退出run()方法 (正常執行完退出 或 遇到異常退出)

特點:不會對其他線程造成影響

5、線程的調度

如果我們對線程的調度不加管理,那多線程的運行時序是無規律可循的,實際上就算我們對線程調度進行管理,它們的運行時序也不能百分之百確定的,比如讓一個線程給另外一個線程運行的機會,可采取以下辦法:

  (1)調整線程優先級

Java中提供了10個優先級,取值範圍是整數1~10(關於Java優先級與操作系統優先級之間的映射另外深入學習),Therad類中有3個靜態常量:

l MAX_PRIORITY:取值為10,表示最高優先級

l MIN_PRIORITY:取值為1,表示最低優先級

l NORM_PRIORITY:取值為5,表示默認的優先級

而設置優先級的方法是通過Thread類的setPriority(int)方法

所有處於就緒狀態的線程根據優先級存放在可運行池中,優先級低的線程獲得較少的運行機會,優先級高的線程獲得較多的運行機會。

 (2)讓Running狀態線程調用Thread.sleep()方法

假如讓一個線程調用sleep()方法,你可以給它一個精確的數值1000毫秒,但是從開始執行sleep(1000)方法到該線程再一次進入Running狀態之間的時間確並不是精確的1000毫秒,

因為1000毫秒過後它進入Runnable狀態,而從Runnable狀態到Running狀態的時間是不確定的,這個時間取決於很多因素,比如CPU運算速度,調度算法,當前正在運行的進程或線程等。

 (3)讓Running 狀態線程調用Thread.yield()方法

而最能證明線程管理的不確定性就是Thread.yield()方法了,yield翻譯過來是“屈服”的意思,實際上我們可以把它理解為假裝屈服,因為你稍微慢一點,下一時刻搶占到CPU的還可能是原來的線程。

sleep() 和 yield()方法都是Thread類的靜態方法,都會使當前處於運行狀態的線程放棄CPU,把運行機會讓給別的線程。兩者區別在於:

1.sleep () 方法會給其它線程運行的機會,而不考慮其他線程的優先級,而yield()方法只會給相同優先級或者更高優先級一個運行的機會。

2.當線程執行sleep()方法後將轉到阻塞狀態;當線程執行yield()方法後,將轉到就緒狀態。

3.sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明拋出任何異常。所以sleep()語句要放在try-catch中。

4.sleep()方法比yield()具有更好的可移植性,在實際應用中不能只依靠yield()方法來提高程序的並發性能。

 (4)讓Running狀態線程調用另一個線程的join()方法

當前運行的線程可以調用另一個線程的join()方法,當前運行的線程將轉到阻塞狀態,直至另一個線程運行結束,它才會從阻塞狀態轉到就緒狀態,獲得運行機會。

五、守護線程

概念:為其他線程提供服務的線程,也稱為守護線程。Java虛擬機的垃圾回收線程就是典型的後臺線程,它負責回收其他線程不再使用的內存。

特點:後臺線程與前臺線程相伴相隨,後臺線程可能在前臺線程執行完畢之前結束,如果前臺線程運行結束後,加入後臺線程還在運行,Java虛擬機就會終止後臺線程。

這又反應在後臺線程的概念上,後臺線程是為服務於前臺線程而存在的,倘若沒有前臺線程在執行,後臺線程也就沒有了存在的必要。

主線程在默認情況下是前臺線程,由前臺線程創建的線程在默認情況下也是前臺線程;由後臺線程創建的線程在默認情況下仍然是後臺線程。

將前臺線程設置為後臺線程:調用Thread類的setDaemon(true)方法。

判斷線程是否為後臺線程:調用Thread的isDaemon()方法

本文出自 “12212886” 博客,請務必保留此出處http://12222886.blog.51cto.com/12212886/1963919

Java中的線程(一)