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中的線程(一)