版權聲明:原創做品,容許轉載,轉載時請務 必以超連接形式標明文章
原始出處 、做者信息和本聲明。不然將追究法律責任。
http://lavasoft.blog.51cto.com/62575/27069
|
Java 多線程編程總結
-------------------------------------------------------------------------------------------------
下面的內容是很早以前寫的,內容不夠充實,並且是基於Java1.4的內容,Java5以後,線程併發部分擴展了至關多的內容,所以建議你們看 上面的系列文章的內容,與時俱進,跟上Java發展的步伐。
-------------------------------------------------------------------------------------------------
1、認識多任務、多進程、單線程、多線程
要認識多線程就要從操做系統的原理提及。
之前古老的DOS操做系統(V 6.22)是單任務的,尚未線程的概念,系統在每次只能作一件事情。好比你在copy東西的時候不能rename文件名。爲了提升系統的利用效率,採用 批處理來批量執行任務。
如今的操做系統都是多任務操做系統,每一個運行的任務就是操做系統所作的一件事情,好比你在聽歌的同時還在用MSN和好友聊天。聽歌和聊天就是兩 個任務,這個兩個任務是「同時」進行的。一個任務通常對應一個進程,也可能包含好幾個進程。好比運行的MSN就對應一個MSN的進程,若是你用的是 windows系統,你就能夠在任務管理器中看到操做系統正在運行的進程信息。
通常來講,當運行一個應用程序的時候,就啓動了一個進程,固然有些會啓動多個進程。啓動進程的時候,操做系統會爲進程分配資源,其中最主要的資 源是內存空間,由於程序是在內存中運行的。在進程中,有些程序流程塊是能夠亂序執行的,而且這個代碼塊能夠同時被屢次執行。實際上,這樣的代碼塊就是線程 體。線程是進程中亂序執行的代碼流程。當多個線程同時運行的時候,這樣的執行模式成爲併發執行。
多線程的目的是爲了最大限度的利用CPU資源。
Java編寫程序都運行在在Java虛擬機(JVM)中,在JVM的內部,程序的多任務是經過線程來實現的。每用java命令啓動一個java 應用程序,就會啓動一個JVM進程。在同一個JVM進程中,有且只有一個進程,就是它本身。在這個JVM環境中,全部程序代碼的運行都是以線程來運行。
通常常見的Java應用程序都是單線程的。好比,用java命令運行一個最簡單的HelloWorld的Java應用程序時,就啓動了一個 JVM進程,JVM找到程序程序的入口點main(),而後運行main()方法,這樣就產生了一個線程,這個線程稱之爲主線程。當main方法結束後, 主線程運行完成。JVM進程也隨即退出 。
對於一個進程中的多個線程來講,多個線程共享進程的內存塊,當有新的線程產生的時候,操做系統不分配新的內存,而是讓新線程共享原有的進程塊的 內存。所以,線程間的通訊很容易,速度也很快。不一樣的進程由於處於不一樣的內存塊,所以進程之間的通訊相對困難。
實際上,操做的系統的多進程實現了多任務併發執行,程序的多線程實現了進程的併發執行。多任務、多進程、多線程的前提都是要求操做系統提供多任 務、多進程、多線程的支持。
在Java程序中,JVM負責線程的調度。線程調度是值按照特定的機制爲多個線程分配CPU的使用權。
調度的模式有兩種:分時調度和搶佔式調度。分時調度是全部線程輪流得到CPU使用權,並平均分配每一個線程佔用CPU的時間;搶佔式調度是根據線 程的優先級別來獲取CPU的使用權。JVM的線程調度模式採用了搶佔式模式。
所謂的「併發執行」、「同時」其實都不是真正意義上的「同時」。衆所周知,CPU都有個時鐘頻率,表示每秒中能執行cpu指令的次數。在每一個時 鍾週期內,CPU實際上只能去執行一條(也有可能多條)指令。操做系統將進程線程進行管理,輪流(沒有固定的順序)分配每一個進程很短的一段是時間(不必定 是均分),而後在每一個線程內部,程序代碼本身處理該進程內部線程的時間分配,多個線程之間相互的切換去執行,這個切換時間也是很是短的。所以多任務、多進 程、多線程都是操做系統給人的一種宏觀感覺,從微觀角度看,程序的運行是異步執行的。
用一句話作總結:雖然操做系統是多線程的,但CPU每一時刻只能作一件事,和人的大腦是同樣的,呵呵。
2、Java與多線程
Java語言的多線程須要操做系統的支持。
Java 虛擬機容許應用程序併發地運行多個執行線程。Java語言提供了多線程編程的擴展點,並給出了功能強大的線程控制API。
在Java中,多線程的實現有兩種方式:
擴展java.lang.Thread類
實現java.lang.Runnable接口
每一個線程都有一個優先級,高優先級線程的執行優先於低優先級線程。每一個線程均可以或不能夠標記爲一個守護程序。當某個線程中運行的代碼建立一個 新 Thread 對象時,該新線程的初始優先級被設定爲建立線程的優先級,而且當且僅當建立線程是守護線程時,新線程纔是守護程序。
當 Java 虛擬機啓動時,一般都會有單個非守護線程(它一般會調用某個指定類的 main 方法)。Java 虛擬機會繼續執行線程,直到下列任一狀況出現時爲止:
調用了 Runtime 類的 exit 方法,而且安全管理器容許退出操做發生。
非守護線程的全部線程都已中止運行,不管是經過從對 run 方法的調用中返回,仍是經過拋出一個傳播到 run 方法以外的異常。
3、擴展java.lang.Thread類
/**
* File Name: TestMitiThread.java
* Created by: IntelliJ IDEA.
* Copyright: Copyright (c) 2003-2006
* Company: Lavasoft(
[url]http://lavasoft.blog.51cto.com/[/url])
* Author: leizhimin
* Modifier: leizhimin
* Date Time: 2007-5-17 10:03:12
* Readme: 經過擴展Thread類實現多線程
*/
public class TestMitiThread {
public static void main(String[] rags) {
System.out.println(Thread.currentThread().getName() + " 線程運行開始!");
new MitiSay("A").start();
new MitiSay("B").start();
System.out.println(Thread.currentThread().getName() + " 線程運行結束!");
}
}
class MitiSay extends Thread {
public MitiSay(String threadName) {
super(threadName);
}
public void run() {
System.out.println(getName() + " 線程運行開始!");
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + getName());
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + " 線程運行結束!");
}
}
運行結果:
main 線程運行開始!
main 線程運行結束!
A 線程運行開始!
0 A
1 A
B 線程運行開始!
2 A
0 B
3 A
4 A
1 B
5 A
6 A
7 A
8 A
9 A
A 線程運行結束!
2 B
3 B
4 B
5 B
6 B
7 B
8 B
9 B
B 線程運行結束!
說明:
程序啓動運行main時候,java虛擬機啓動一個進程,主線程main在main()調用時候被建立。隨着調用MitiSay的兩個對象的 start方法,另外兩個線程也啓動了,這樣,整個應用就在多線程下運行。
在一個方法中調用Thread.currentThread().getName()方法,能夠獲取當前線程的名字。在mian方法中調用該方 法,獲取的是主線程的名字。
注意:start()方法的調用後並非當即執行多線程代碼,而是使得該線程變爲可運行態(Runnable),何時運行是由操做系統決定 的。
從程序運行的結果能夠發現,多線程程序是亂序執行。所以,只有亂序執行的代碼纔有必要設計爲多線程。
Thread.sleep()方法調用目的是不讓當前線程獨自霸佔該進程所獲取的CPU資源,以留出必定時間給其餘線程執行的機會。
實際上全部的多線程代碼執行順序都是不肯定的,每次執行的結果都是隨機的。
4、實現java.lang.Runnable接口
/**
* 經過實現 Runnable 接口實現多線程
*/
public class TestMitiThread1 implements Runnable {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " 線程運行開始!");
TestMitiThread1 test = new TestMitiThread1();
Thread thread1 = new Thread(test);
Thread thread2 = new Thread(test);
thread1.start();
thread2.start();
System.out.println(Thread.currentThread().getName() + " 線程運行結束!");
}
public void run() {
System.out.println(Thread.currentThread().getName() + " 線程運行開始!");
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + Thread.currentThread().getName());
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 線程運行結束!");
}
}
運行結果:
main 線程運行開始!
Thread-0 線程運行開始!
main 線程運行結束!
0 Thread-0
Thread-1 線程運行開始!
0 Thread-1
1 Thread-1
1 Thread-0
2 Thread-0
2 Thread-1
3 Thread-0
3 Thread-1
4 Thread-0
4 Thread-1
5 Thread-0
6 Thread-0
5 Thread-1
7 Thread-0
8 Thread-0
6 Thread-1
9 Thread-0
7 Thread-1
Thread-0 線程運行結束!
8 Thread-1
9 Thread-1
Thread-1 線程運行結束!
說明:
TestMitiThread1類經過實現Runnable接口,使得該類有了多線程類的特徵。run()方法是多線程程序的一個約定。全部的 多線程代碼都在run方法裏面。Thread類實際上也是實現了Runnable接口的類。
在啓動的多線程的時候,須要先經過Thread類的構造方法Thread(Runnable target) 構造出對象,而後調用Thread對象的start()方法來運行多線程代碼。
實際上全部的多線程代碼都是經過運行Thread的start()方法來運行的。所以,無論是擴展Thread類仍是實現Runnable接口 來實現多線程,最終仍是經過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。
5、讀解Thread類API
static int MAX_PRIORITY
線程能夠具備的最高優先級。
static int MIN_PRIORITY
線程能夠具備的最低優先級。
static int NORM_PRIORITY
分配給線程的默認優先級。
構造方法摘要
Thread(Runnable target)
分配新的 Thread 對象。
Thread(String name)
分配新的 Thread 對象。
方法摘要
static Thread currentThread()
返回對當前正在執行的線程對象的引用。
ClassLoader getContextClassLoader()
返回該線程的上下文 ClassLoader。
long getId()
返回該線程的標識符。
String getName()
返回該線程的名稱。
int getPriority()
返回線程的優先級。
Thread.State getState()
返回該線程的狀態。
ThreadGroup getThreadGroup()
返回該線程所屬的線程組。
static boolean holdsLock(Object obj)
當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true。
void interrupt()
中斷線程。
static boolean interrupted()
測試當前線程是否已經中斷。
boolean isAlive()
測試線程是否處於活動狀態。
boolean isDaemon()
測試該線程是否爲守護線程。
boolean isInterrupted()
測試線程是否已經中斷。
void join()
等待該線程終止。
void join(long millis)
等待該線程終止的時間最長爲 millis 毫秒。
void join(long millis, int nanos)
等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒。
void resume()
已過期。 該方法只與 suspend() 一塊兒使用,但 suspend() 已經遭到反對,由於它具備死鎖傾向。有關更多信息,請參閱爲什麼 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。
void run()
若是該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;不然,該方法不執行任何操做並返回。
void setContextClassLoader(ClassLoader cl)
設置該線程的上下文 ClassLoader。
void setDaemon(boolean on)
將該線程標記爲守護線程或用戶線程。
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
設置當線程因爲未捕獲到異常而忽然終止,而且沒有爲該線程定義其餘處理程序時所調用的默認處理程序。
void setName(String name)
改變線程名稱,使之與參數 name 相同。
void setPriority(int newPriority)
更改線程的優先級。
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
設置該線程因爲未捕獲到異常而忽然終止時調用的處理程序。
static void sleep(long millis)
在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)。
static void sleep(long millis, int nanos)
在指定的毫秒數加指定的納秒數內讓當前正在執行的線程休眠(暫停執行)。
void start()
使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
void stop()
已過期。 該方法具備固有的不安全性。用 Thread.stop 來終止線程將釋放它已經鎖定的全部監視器(做爲沿堆棧向上傳播的未檢查 ThreadDeath 異常的一個天然後果)。若是之前受這些監視器保護的任何對象都處於一種不一致的狀態,則損壞的對象將對其餘線程可見,這有可能致使任意的行爲。stop 的許多使用都應由只修改某些變量以指示目標線程應該中止運行的代碼來取代。目標線程應按期檢查該變量,而且若是該變量指示它要中止運行,則從其運行方法依 次返回。若是目標線程等待很長時間(例如基於一個條件變量),則應使用 interrupt 方法來中斷該等待。有關更多信息,請參閱《爲什麼不同意使用 Thread.stop、Thread.suspend 和 Thread.resume?》。
void stop(Throwable obj)
已過期。 該方法具備固有的不安全性。請參閱 stop() 以得到詳細信息。該方法的附加危險是它可用於生成目標線程未準備處理的異常(包括若沒有該方法該線程不太可能拋出的已檢查的異常)。有關更多信息,請參閱 爲什麼 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。
void suspend()
已過期。 該方法已經遭到反對,由於它具備固有的死鎖傾向。若是目標線程掛起時在保護關鍵系統資源的監視器上保持有鎖,則在目標線程從新開始之前任何線程都不能訪問 該資源。若是從新開始目標線程的線程想在調用 resume 以前鎖定該監視器,則會發生死鎖。這類死鎖一般會證實本身是「凍結」的進程。有關更多信息,請參閱爲什麼 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。
String toString()
返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。
static void yield()
暫停當前正在執行的線程對象,並執行其餘線程。
6、線程的狀態轉換圖
線程在必定條件下,狀態會發生變化。線程變化的狀態轉換圖以下:
一、新建狀態(New):新建立了一個線程對象。
二、就緒狀態(Runnable):線程對象建立後,其餘線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運 行,等待獲取CPU的使用權。
三、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
四、阻塞狀態(Blocked):阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。 阻塞的狀況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其餘阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當 sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入就緒狀態。
五、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
7、線程的調度
一、調整線程優先級:Java線程有優先級,優先級高的線程會得到較多的運行機會。
Java線程的優先級用整數表示,取值範圍是1~10,Thread類有如下三個靜態常量:
static int MAX_PRIORITY
線程能夠具備的最高優先級,取值爲10。
static int MIN_PRIORITY
線程能夠具備的最低優先級,取值爲1。
static int NORM_PRIORITY
分配給線程的默認優先級,取值爲5。
Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。
每一個線程都有默認的優先級。主線程的默認優先級爲Thread.NORM_PRIORITY。
線程的優先級有繼承關係,好比A線程中建立了B線程,那麼B將和A具備相同的優先級。
JVM提供了10個線程優先級,但與常見的操做系統都不能很好的映射。若是但願程序能移植到各個操做系統中,應該僅僅使用Thread類有如下 三個靜態常量做爲優先級,這樣能保證一樣的優先級採用了一樣的調度方式。
二、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒爲單位。當睡眠結束後,就轉爲就緒(Runnable)狀態。 sleep()平臺移植性好。
三、線程等待:Object類中的wait()方法,致使當前的線程等待,直到其餘線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行爲等價於調用 wait(0) 同樣。
四、線程讓步:Thread.yield() 方法,暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先級的線程。
五、線程加入:join()方法,等待其餘線程終止。在當前線程中調用另外一個線程的join()方法,則當前線程轉入阻塞狀態,直到另外一個進程 運行結束,當前線程再由阻塞轉爲就緒狀態。
六、線程喚醒:Object類中的notify()方法,喚醒在此對象監視器上等待的單個線程。若是全部線程都在此對象上等待,則會選擇喚醒其 中一個線程。選擇是任意性的,並在對實現作出決定時發生。線程經過調用其中一個 wait 方法,在對象的監視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其餘全部線程進行競爭;例如,喚醒的線程 在做爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。相似的方法還有一個notifyAll(),喚醒在此對象監視器上等待的全部線程。
注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,再也不介紹。由於有死鎖傾向。
七、常見線程名詞解釋
主線程:JVM調用程序mian()所產生的線程。
當前線程:這個是容易混淆的概念。通常指經過Thread.currentThread()來獲取的進程。
後臺線程:指爲其餘線程提供服務的線程,也稱爲守護線程。JVM的垃圾回收線程就是一個後臺線程。
前臺線程:是指接受後臺線程服務的線程,其實前臺後臺線程是聯繫在一塊兒,就像傀儡和幕後操縱者同樣的關係。傀儡是前臺線程、幕後操縱者是後臺線 程。由前臺線程建立的線程默認也是前臺線程。能夠經過isDaemon()和setDaemon()方法來判斷和設置一個線程是否爲後臺線程。
本文出自 「熔 巖」 博客,請務必保留此出處http://lavasoft.blog.51cto.com/62575/27069php |