[Java并發編程:Thread類的使用](http://www.cnblogs.com/dolphin0520/p/3920357.html)
[TOC=1,3]
Java并發編程:Thread類的使用
在前面2篇文章分別講到了線程和進程的由來、以及如何在Java中怎么創建線程和進程。今天我們來學習一下Thread類,在學習Thread類之前,先介紹與線程相關知識:線程的幾種狀態、上下文切換,然后接著介紹Thread類中的方法的具體使用。
以下是本文的目錄大綱:
一.線程的狀態
二.上下文切換
三.Thread類中的方法
若有不正之處,請多多諒解并歡迎批評指正。
請尊重作者勞動成果,轉載請標明原文鏈接:
? http://www.cnblogs.com/dolphin0520/p/3920357.html
## 一.線程的狀態
在正式學習Thread類中的具體方法之前,我們先來了解一下線程有哪些狀態,這個將會有助于后面對Thread類中的方法的理解。
線程從創建到最終的消亡,要經歷若干個狀態。一般來說,線程包括以下這幾個狀態:創建(new)、就緒(runnable)、運行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。
當需要新起一個線程來執行某個子任務時,就創建了一個線程。但是線程創建之后,不會立即進入就緒狀態,因為線程的運行需要一些條件(比如內存資源,在前面的JVM內存區域劃分一篇博文中知道程序計數器、Java棧、本地方法棧都是線程私有的,所以需要為線程分配一定的內存空間),只有線程運行需要的所有條件滿足了,才進入就緒狀態。
當線程進入就緒狀態后,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之后,線程便真正進入運行狀態。
線程在運行狀態過程中,可能有多個原因導致當前線程不繼續運行下去,比如用戶主動讓線程睡眠(睡眠一定的時間之后再重新執行)、用戶主動讓線程等待,或者被同步塊給阻塞,此時就對應著多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。
當由于突然中斷或者子任務執行完畢,線程就會被消亡。
下面這副圖描述了線程從創建到消亡之間的狀態:

在有些教程上將blocked、waiting、time waiting統稱為阻塞狀態,這個也是可以的,只不過這里我想將線程的狀態和Java中的方法調用聯系起來,所以將waiting和time waiting兩個狀態分離出來。
## 二.上下文切換
對于單核CPU來說(對于多核CPU,此處就理解為一個核),CPU在一個時刻只能運行一個線程,當在運行一個線程的過程中轉去運行另外一個線程,這個叫做線程上下文切換(對于進程也是類似)。
由于可能當前線程的任務并沒有執行完畢,所以在切換時需要保存線程的運行狀態,以便下次重新切換回來時能夠繼續切換之前的狀態運行。舉個簡單的例子:比如一個線程A正在讀取一個文件的內容,正讀到文件的一半,此時需要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,我們不希望線程A又從文件的開頭來讀取。
因此需要記錄線程A的運行狀態,那么會記錄哪些數據呢?因為下次恢復時需要知道在這之前當前線程已經執行到哪條指令了,所以需要記錄程序計數器的值,另外比如說線程正在進行某個計算的時候被掛起了,那么下次繼續執行的時候需要知道之前掛起時變量的值時多少,因此需要記錄CPU寄存器的狀態。所以一般來說,線程上下文切換過程中會記錄程序計數器、CPU寄存器狀態等數據。
說簡單點的:對于線程的上下文切換實際上就是?存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。
雖然多線程可以使得任務執行的效率得到提升,但是由于在線程切換時同樣會帶來一定的開銷代價,并且多個線程會導致系統資源占用的增加,所以在進行多線程編程時要注意這些因素。
## 三.Thread類中的方法
通過查看java.lang.Thread類的源碼可知:

Thread類實現了Runnable接口,在Thread類中,有一些比較關鍵的屬性,比如name是表示Thread的名字,可以通過Thread類的構造器中的參數來指定線程名字,priority表示線程的優先級(最大值為10,最小值為1,默認值為5),daemon表示線程是否是守護線程,target表示要執行的任務。
下面是Thread類中常用的方法:
以下是關系到線程運行狀態的幾個方法:
1)start方法
start()用來啟動一個線程,當調用start方法后,系統才會開啟一個新的線程來執行用戶定義的子任務,在這個過程中,會為相應的線程分配需要的資源。
2)run方法
run()方法是不需要用戶來調用的,當通過start方法啟動一個線程之后,當線程獲得了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。
3)sleep方法
sleep方法有兩個重載版本:
~~~
sleep(long millis) //參數為毫秒
sleep(long millis,int nanoseconds) //第一參數為毫秒,第二個參數為納秒
~~~
sleep相當于讓線程睡眠,交出CPU,讓CPU去執行其他的任務。
但是有一點要非常注意,sleep方法不會釋放鎖,也就是說如果當前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法訪問這個對象。看下面這個例子就清楚了:
~~~
public class Test {
private int i = 10;
private Object object = new Object();
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.start();
thread2.start();
}
class MyThread extends Thread{
@Override
public void run() {
synchronized (object) {
i++;
System.out.println("i:"+i);
try {
System.out.println("線程"+Thread.currentThread().getName()+"進入睡眠狀態");
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("線程"+Thread.currentThread().getName()+"睡眠結束");
i++;
System.out.println("i:"+i);
}
}
}
}
~~~
? 輸出結果:

從上面輸出結果可以看出,當Thread-0進入睡眠狀態之后,Thread-1并沒有去執行具體的任務。只有當Thread-0執行完之后,此時Thread-0釋放了對象鎖,Thread-1才開始執行。
注意,如果調用了sleep方法,必須捕獲InterruptedException異常或者將該異常向上層拋出。當線程睡眠時間滿后,不一定會立即得到執行,因為此時可能CPU正在執行其他的任務。所以說調用sleep方法相當于讓線程進入阻塞狀態。
4)yield方法
調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。
注意,調用yield方法并不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。
5)join方法
join方法有三個重載版本:
~~~
join()
join(long millis) //參數為毫秒
join(long millis,int nanoseconds) //第一參數為毫秒,第二個參數為納秒
~~~
? 假如在main線程中,調用thread.join方法,則main方法會等待thread線程執行完畢或者等待一定的時間。如果調用的是無參join方法,則等待thread執行完畢,如果調用的是指定了時間參數的join方法,則等待一定的事件。
看下面一個例子:
~~~
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("進入線程"+Thread.currentThread().getName());
Test test = new Test();
MyThread thread1 = test.new MyThread();
thread1.start();
try {
System.out.println("線程"+Thread.currentThread().getName()+"等待");
thread1.join();
System.out.println("線程"+Thread.currentThread().getName()+"繼續執行");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("進入線程"+Thread.currentThread().getName());
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("線程"+Thread.currentThread().getName()+"執行完畢");
}
}
}
~~~
? 輸出結果:

可以看出,當調用thread1.join()方法后,main線程會進入等待,然后等待thread1執行完之后再繼續執行。
實際上調用join方法是調用了Object的wait方法,這個可以通過查看源碼得知:

wait方法會讓線程進入阻塞狀態,并且會釋放線程占有的鎖,并交出CPU執行權限。
由于wait方法會讓線程釋放對象鎖,所以join方法同樣會讓線程釋放對一個對象持有的鎖。具體的wait方法使用在后面文章中給出。
6)interrupt方法
interrupt,顧名思義,即中斷的意思。單獨調用interrupt方法可以使得處于阻塞狀態的線程拋出一個異常,也就說,它可以用來中斷一個正處于阻塞狀態的線程;另外,通過interrupt方法和isInterrupted()方法來停止正在運行的線程。
下面看一個例子:
~~~
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("進入睡眠狀態");
Thread.currentThread().sleep(10000);
System.out.println("睡眠完畢");
} catch (InterruptedException e) {
System.out.println("得到中斷異常");
}
System.out.println("run方法執行完畢");
}
}
}
~~~
? 輸出結果:

從這里可以看出,通過interrupt方法可以中斷處于阻塞狀態的線程。那么能不能中斷處于非阻塞狀態的線程呢?看下面這個例子:
~~~
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(i<Integer.MAX_VALUE){
System.out.println(i+" while循環");
i++;
}
}
}
}
~~~
? 運行該程序會發現,while循環會一直運行直到變量i的值超出Integer.MAX_VALUE。所以說直接調用interrupt方法不能中斷正在運行中的線程。
但是如果配合isInterrupted()能夠中斷正在運行的線程,因為調用interrupt方法相當于將中斷標志位置為true,那么可以通過調用isInterrupted()判斷中斷標志是否被置位來中斷線程的執行。比如下面這段代碼:
~~~
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(!isInterrupted() && i<Integer.MAX_VALUE){
System.out.println(i+" while循環");
i++;
}
}
}
}
~~~
? 運行會發現,打印若干個值之后,while循環就停止打印了。
但是一般情況下不建議通過這種方式來中斷線程,一般會在MyThread類中增加一個屬性 isStop來標志是否結束while循環,然后再在while循環中判斷isStop的值。
~~~
class MyThread extends Thread{
private volatile boolean isStop = false;
@Override
public void run() {
int i = 0;
while(!isStop){
i++;
}
}
public void setStop(boolean stop){
this.isStop = stop;
}
}
~~~
? 那么就可以在外面通過調用setStop方法來終止while循環。
7)stop方法
stop方法已經是一個廢棄的方法,它是一個不安全的方法。因為調用stop方法會直接終止run方法的調用,并且會拋出一個ThreadDeath錯誤,如果線程持有某個對象鎖的話,會完全釋放鎖,導致對象狀態不一致。所以stop方法基本是不會被用到的。
8)destroy方法
destroy方法也是廢棄的方法。基本不會被使用到。
以下是關系到線程屬性的幾個方法:
1)getId
用來得到線程ID
2)getName和setName
用來得到或者設置線程名稱。
3)getPriority和setPriority
用來獲取和設置線程優先級。
4)setDaemon和isDaemon
用來設置線程是否成為守護線程和判斷線程是否是守護線程。
守護線程和用戶線程的區別在于:守護線程依賴于創建它的線程,而用戶線程則不依賴。舉個簡單的例子:如果在main線程中創建了一個守護線程,當main方法運行完畢之后,守護線程也會隨著消亡。而用戶線程則不會,用戶線程會一直運行直到其運行完畢。在JVM中,像垃圾收集器線程就是守護線程。
Thread類有一個比較常用的靜態方法currentThread()用來獲取當前線程。
在上面已經說到了Thread類中的大部分方法,那么Thread類中的方法調用到底會引起線程狀態發生怎樣的變化呢?下面一幅圖就是在上面的圖上進行改進而來的:
?
參考資料:
《Java編程思想》
[http://zy19982004.iteye.com/blog/1626916](http://zy19982004.iteye.com/blog/1626916)
[http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html#navigation](http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html#navigation)
[http://www.blogjava.net/vincent/archive/2008/08/23/223912.html](http://www.blogjava.net/vincent/archive/2008/08/23/223912.html)
[http://iteye.blog.163.com/blog/static/1863080962012111424544215/](http://iteye.blog.163.com/blog/static/1863080962012111424544215/)
[http://blog.csdn.net/lifei128/article/details/20363257](http://blog.csdn.net/lifei128/article/details/20363257)
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)