原帖地址:http://lavasoft.blog.51cto.com/62575/27069

Java多線程編程總結(jié)
 
下面是Java線程系列博文的一個編目:
 
Java線程:新特征-鎖(上) Java線程:大總結(jié)
 
 
-------------------------------------------------------------------------------------------------
 
下面的內(nèi)容是很早之前寫的,內(nèi)容不夠充實,而且是基于Java1.4的內(nèi)容,Java5之后,線程并發(fā)部分?jǐn)U展了相當(dāng)多的內(nèi)容,因此建議大家看上面的系列文章的內(nèi)容,與時俱進(jìn),跟上Java發(fā)展的步伐。
 
-------------------------------------------------------------------------------------------------
 
 
一、認(rèn)識多任務(wù)、多進(jìn)程、單線程、多線程
要認(rèn)識多線程就要從操作系統(tǒng)的原理說起。
 
以前古老的DOS操作系統(tǒng)(V 6.22)是單任務(wù)的,還沒有線程的概念,系統(tǒng)在每次只能做一件事情。比如你在copy東西的時候不能rename文件名。為了提高系統(tǒng)的利用效率,采用批處理來批量執(zhí)行任務(wù)。
 
現(xiàn)在的操作系統(tǒng)都是多任務(wù)操作系統(tǒng),每個運行的任務(wù)就是操作系統(tǒng)所做的一件事情,比如你在聽歌的同時還在用MSN和好友聊天。聽歌和聊天就是兩個任務(wù),這個兩個任務(wù)是“同時”進(jìn)行的。一個任務(wù)一般對應(yīng)一個進(jìn)程,也可能包含好幾個進(jìn)程。比如運行的MSN就對應(yīng)一個MSN的進(jìn)程,如果你用的是 windows系統(tǒng),你就可以在任務(wù)管理器中看到操作系統(tǒng)正在運行的進(jìn)程信息。
 
一般來說,當(dāng)運行一個應(yīng)用程序的時候,就啟動了一個進(jìn)程,當(dāng)然有些會啟動多個進(jìn)程。啟動進(jìn)程的時候,操作系統(tǒng)會為進(jìn)程分配資源,其中最主要的資源是內(nèi)存空間,因為程序是在內(nèi)存中運行的。在進(jìn)程中,有些程序流程塊是可以亂序執(zhí)行的,并且這個代碼塊可以同時被多次執(zhí)行。實際上,這樣的代碼塊就是線程體。線程是進(jìn)程中亂序執(zhí)行的代碼流程。當(dāng)多個線程同時運行的時候,這樣的執(zhí)行模式成為并發(fā)執(zhí)行。
 
多線程的目的是為了最大限度的利用CPU資源。
 
Java編寫程序都運行在在Java虛擬機(JVM)中,在JVM的內(nèi)部,程序的多任務(wù)是通過線程來實現(xiàn)的。每用java命令啟動一個java 應(yīng)用程序,就會啟動一個JVM進(jìn)程。在同一個JVM進(jìn)程中,有且只有一個進(jìn)程,就是它自己。在這個JVM環(huán)境中,所有程序代碼的運行都是以線程來運行。
 
一般常見的Java應(yīng)用程序都是單線程的。比如,用java命令運行一個最簡單的HelloWorld的Java應(yīng)用程序時,就啟動了一個 JVM進(jìn)程,JVM找到程序程序的入口點main(),然后運行main()方法,這樣就產(chǎn)生了一個線程,這個線程稱之為主線程。當(dāng)main方法結(jié)束后,主線程運行完成。JVM進(jìn)程也隨即退出 。
 
對于一個進(jìn)程中的多個線程來說,多個線程共享進(jìn)程的內(nèi)存塊,當(dāng)有新的線程產(chǎn)生的時候,操作系統(tǒng)不分配新的內(nèi)存,而是讓新線程共享原有的進(jìn)程塊的內(nèi)存。因此,線程間的通信很容易,速度也很快。不同的進(jìn)程因為處于不同的內(nèi)存塊,因此進(jìn)程之間的通信相對困難。
 
實際上,操作的系統(tǒng)的多進(jìn)程實現(xiàn)了多任務(wù)并發(fā)執(zhí)行,程序的多線程實現(xiàn)了進(jìn)程的并發(fā)執(zhí)行。多任務(wù)、多進(jìn)程、多線程的前提都是要求操作系統(tǒng)提供多任務(wù)、多進(jìn)程、多線程的支持。
 
在Java程序中,JVM負(fù)責(zé)線程的調(diào)度。線程調(diào)度是值按照特定的機制為多個線程分配CPU的使用權(quán)。
調(diào)度的模式有兩種:分時調(diào)度和搶占式調(diào)度。分時調(diào)度是所有線程輪流獲得CPU使用權(quán),并平均分配每個線程占用CPU的時間;搶占式調(diào)度是根據(jù)線程的優(yōu)先級別來獲取CPU的使用權(quán)。JVM的線程調(diào)度模式采用了搶占式模式。
 
所謂的“并發(fā)執(zhí)行”、“同時”其實都不是真正意義上的“同時”。眾所周知,CPU都有個時鐘頻率,表示每秒中能執(zhí)行cpu指令的次數(shù)。在每個時鐘周期內(nèi),CPU實際上只能去執(zhí)行一條(也有可能多條)指令。操作系統(tǒng)將進(jìn)程線程進(jìn)行管理,輪流(沒有固定的順序)分配每個進(jìn)程很短的一段是時間(不一定是均分),然后在每個線程內(nèi)部,程序代碼自己處理該進(jìn)程內(nèi)部線程的時間分配,多個線程之間相互的切換去執(zhí)行,這個切換時間也是非常短的。因此多任務(wù)、多進(jìn)程、多線程都是操作系統(tǒng)給人的一種宏觀感受,從微觀角度看,程序的運行是異步執(zhí)行的。
 
用一句話做總結(jié):雖然操作系統(tǒng)是多線程的,但CPU每一時刻只能做一件事,和人的大腦是一樣的,呵呵。
 
 
二、Java與多線程
 
Java語言的多線程需要操作系統(tǒng)的支持。
 
Java 虛擬機允許應(yīng)用程序并發(fā)地運行多個執(zhí)行線程。Java語言提供了多線程編程的擴展點,并給出了功能強大的線程控制API。
 
在Java中,多線程的實現(xiàn)有兩種方式:
擴展java.lang.Thread類
實現(xiàn)java.lang.Runnable接口
 
 
每個線程都有一個優(yōu)先級,高優(yōu)先級線程的執(zhí)行優(yōu)先于低優(yōu)先級線程。每個線程都可以或不可以標(biāo)記為一個守護(hù)程序。當(dāng)某個線程中運行的代碼創(chuàng)建一個新 Thread 對象時,該新線程的初始優(yōu)先級被設(shè)定為創(chuàng)建線程的優(yōu)先級,并且當(dāng)且僅當(dāng)創(chuàng)建線程是守護(hù)線程時,新線程才是守護(hù)程序。
 
當(dāng) Java 虛擬機啟動時,通常都會有單個非守護(hù)線程(它通常會調(diào)用某個指定類的 main 方法)。Java 虛擬機會繼續(xù)執(zhí)行線程,直到下列任一情況出現(xiàn)時為止:
 
調(diào)用了 Runtime 類的 exit 方法,并且安全管理器允許退出操作發(fā)生。
非守護(hù)線程的所有線程都已停止運行,無論是通過從對 run 方法的調(diào)用中返回,還是通過拋出一個傳播到 run 方法之外的異常。
 
 
三、擴展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類實現(xiàn)多線程
 */
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() + " 線程運行結(jié)束!");
    }
}
 
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() + " 線程運行結(jié)束!");
    }
}
 
運行結(jié)果:
 
main 線程運行開始!
main 線程運行結(jié)束!
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 線程運行結(jié)束!
2 B
3 B
4 B
5 B
6 B
7 B
8 B
9 B
B 線程運行結(jié)束!
 
說明:
程序啟動運行main時候,java虛擬機啟動一個進(jìn)程,主線程main在main()調(diào)用時候被創(chuàng)建。隨著調(diào)用MitiSay的兩個對象的start方法,另外兩個線程也啟動了,這樣,整個應(yīng)用就在多線程下運行。
 
在一個方法中調(diào)用Thread.currentThread().getName()方法,可以獲取當(dāng)前線程的名字。在mian方法中調(diào)用該方法,獲取的是主線程的名字。
 
注意:start()方法的調(diào)用后并不是立即執(zhí)行多線程代碼,而是使得該線程變?yōu)榭蛇\行態(tài)(Runnable),什么時候運行是由操作系統(tǒng)決定的。
從程序運行的結(jié)果可以發(fā)現(xiàn),多線程程序是亂序執(zhí)行。因此,只有亂序執(zhí)行的代碼才有必要設(shè)計為多線程。
Thread.sleep()方法調(diào)用目的是不讓當(dāng)前線程獨自霸占該進(jìn)程所獲取的CPU資源,以留出一定時間給其他線程執(zhí)行的機會。
實際上所有的多線程代碼執(zhí)行順序都是不確定的,每次執(zhí)行的結(jié)果都是隨機的。
 
 
四、實現(xiàn)java.lang.Runnable接口
 
/**
 * 通過實現(xiàn) Runnable 接口實現(xiàn)多線程
 */
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() + " 線程運行結(jié)束!");
    }
 
    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() + " 線程運行結(jié)束!");
    }
}
 
運行結(jié)果:
 
main 線程運行開始!
Thread-0 線程運行開始!
main 線程運行結(jié)束!
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 線程運行結(jié)束!
8 Thread-1
9 Thread-1
Thread-1 線程運行結(jié)束!
 
說明:
TestMitiThread1類通過實現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個約定。所有的多線程代碼都在run方法里面。Thread類實際上也是實現(xiàn)了Runnable接口的類。
在啟動的多線程的時候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對象,然后調(diào)用Thread對象的start()方法來運行多線程代碼。
實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是擴展Thread類還是實現(xiàn)Runnable接口來實現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。
 
五、讀解Thread類API
 
static int MAX_PRIORITY
          線程可以具有的最高優(yōu)先級。
static int MIN_PRIORITY
          線程可以具有的最低優(yōu)先級。
static int NORM_PRIORITY
          分配給線程的默認(rèn)優(yōu)先級。
 
構(gòu)造方法摘要
Thread(Runnable target)
          分配新的 Thread 對象。
Thread(String name)
          分配新的 Thread 對象。
 
 
方法摘要
static Thread currentThread()
          返回對當(dāng)前正在執(zhí)行的線程對象的引用。
 ClassLoader getContextClassLoader()
          返回該線程的上下文 ClassLoader。
 long getId()
          返回該線程的標(biāo)識符。
 String getName()
          返回該線程的名稱。
 int getPriority()
          返回線程的優(yōu)先級。
 Thread.State getState()
          返回該線程的狀態(tài)。
 ThreadGroup getThreadGroup()
          返回該線程所屬的線程組。
static boolean holdsLock(Object obj)
          當(dāng)且僅當(dāng)當(dāng)前線程在指定的對象上保持監(jiān)視器鎖時,才返回 true。
 void interrupt()
          中斷線程。
static boolean interrupted()
          測試當(dāng)前線程是否已經(jīng)中斷。
 boolean isAlive()
          測試線程是否處于活動狀態(tài)。
 boolean isDaemon()
          測試該線程是否為守護(hù)線程。
 boolean isInterrupted()
          測試線程是否已經(jīng)中斷。
 void join()
          等待該線程終止。
 void join(long millis)
          等待該線程終止的時間最長為 millis 毫秒。
 void join(long millis, int nanos)
          等待該線程終止的時間最長為 millis 毫秒 + nanos 納秒。
 void resume()
          已過時。 該方法只與 suspend() 一起使用,但 suspend() 已經(jīng)遭到反對,因為它具有死鎖傾向。有關(guān)更多信息,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。
 void run()
          如果該線程是使用獨立的 Runnable 運行對象構(gòu)造的,則調(diào)用該 Runnable 對象的 run 方法;否則,該方法不執(zhí)行任何操作并返回。
 void setContextClassLoader(ClassLoader cl)
          設(shè)置該線程的上下文 ClassLoader。
 void setDaemon(boolean on)
          將該線程標(biāo)記為守護(hù)線程或用戶線程。
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
          設(shè)置當(dāng)線程由于未捕獲到異常而突然終止,并且沒有為該線程定義其他處理程序時所調(diào)用的默認(rèn)處理程序。
 void setName(String name)
          改變線程名稱,使之與參數(shù) name 相同。
 void setPriority(int newPriority)
          更改線程的優(yōu)先級。
 void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
          設(shè)置該線程由于未捕獲到異常而突然終止時調(diào)用的處理程序。
static void sleep(long millis)
          在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)。
static void sleep(long millis, int nanos)
          在指定的毫秒數(shù)加指定的納秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)。
 void start()
          使該線程開始執(zhí)行;Java 虛擬機調(diào)用該線程的 run 方法。
 void stop()
          已過時。 該方法具有固有的不安全性。用 Thread.stop 來終止線程將釋放它已經(jīng)鎖定的所有監(jiān)視器(作為沿堆棧向上傳播的未檢查 ThreadDeath 異常的一個自然后果)。如果以前受這些監(jiān)視器保護(hù)的任何對象都處于一種不一致的狀態(tài),則損壞的對象將對其他線程可見,這有可能導(dǎo)致任意的行為。stop 的許多使用都應(yīng)由只修改某些變量以指示目標(biāo)線程應(yīng)該停止運行的代碼來取代。目標(biāo)線程應(yīng)定期檢查該變量,并且如果該變量指示它要停止運行,則從其運行方法依次返回。如果目標(biāo)線程等待很長時間(例如基于一個條件變量),則應(yīng)使用 interrupt 方法來中斷該等待。有關(guān)更多信息,請參閱《為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。
 void stop(Throwable obj)
          已過時。 該方法具有固有的不安全性。請參閱 stop() 以獲得詳細(xì)信息。該方法的附加危險是它可用于生成目標(biāo)線程未準(zhǔn)備處理的異常(包括若沒有該方法該線程不太可能拋出的已檢查的異常)。有關(guān)更多信息,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。
 void suspend()
          已過時。該方法已經(jīng)遭到反對,因為它具有固有的死鎖傾向。如果目標(biāo)線程掛起時在保護(hù)關(guān)鍵系統(tǒng)資源的監(jiān)視器上保持有鎖,則在目標(biāo)線程重新開始以前任何線程都不能訪問該資源。如果重新開始目標(biāo)線程的線程想在調(diào)用 resume 之前鎖定該監(jiān)視器,則會發(fā)生死鎖。這類死鎖通常會證明自己是“凍結(jié)”的進(jìn)程。有關(guān)更多信息,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。
 String toString()
          返回該線程的字符串表示形式,包括線程名稱、優(yōu)先級和線程組。
static void yield()
          暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程。
 
 
六、線程的狀態(tài)轉(zhuǎn)換圖
 
線程在一定條件下,狀態(tài)會發(fā)生變化。線程變化的狀態(tài)轉(zhuǎn)換圖如下:
 
1、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權(quán)。
3、運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進(jìn)入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
 
 
七、線程的調(diào)度
1、調(diào)整線程優(yōu)先級:Java線程有優(yōu)先級,優(yōu)先級高的線程會獲得較多的運行機會。
 
Java線程的優(yōu)先級用整數(shù)表示,取值范圍是1~10,Thread類有以下三個靜態(tài)常量:
static int MAX_PRIORITY
          線程可以具有的最高優(yōu)先級,取值為10。
static int MIN_PRIORITY
          線程可以具有的最低優(yōu)先級,取值為1。
static int NORM_PRIORITY
          分配給線程的默認(rèn)優(yōu)先級,取值為5。
 
Thread類的setPriority()和getPriority()方法分別用來設(shè)置和獲取線程的優(yōu)先級。
 
每個線程都有默認(rèn)的優(yōu)先級。主線程的默認(rèn)優(yōu)先級為Thread.NORM_PRIORITY。
線程的優(yōu)先級有繼承關(guān)系,比如A線程中創(chuàng)建了B線程,那么B將和A具有相同的優(yōu)先級。
JVM提供了10個線程優(yōu)先級,但與常見的操作系統(tǒng)都不能很好的映射。如果希望程序能移植到各個操作系統(tǒng)中,應(yīng)該僅僅使用Thread類有以下三個靜態(tài)常量作為優(yōu)先級,這樣能保證同樣的優(yōu)先級采用了同樣的調(diào)度方式。
 
2、線程睡眠:Thread.sleep(long millis)方法,使線程轉(zhuǎn)到阻塞狀態(tài)。millis參數(shù)設(shè)定睡眠的時間,以毫秒為單位。當(dāng)睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()平臺移植性好。
 
3、線程等待:Object類中的wait()方法,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價于調(diào)用 wait(0) 一樣。
 
4、線程讓步:Thread.yield() 方法,暫停當(dāng)前正在執(zhí)行的線程對象,把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。
 
5、線程加入:join()方法,等待其他線程終止。在當(dāng)前線程中調(diào)用另一個線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個進(jìn)程運行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
 
6、線程喚醒:Object類中的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實現(xiàn)做出決定時發(fā)生。線程通過調(diào)用其中一個 wait 方法,在對象的監(jiān)視器上等待。直到當(dāng)前的線程放棄此對象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進(jìn)行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監(jiān)視器上等待的所有線程。
 
注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經(jīng)廢除,不再介紹。因為有死鎖傾向。
 
7、常見線程名詞解釋
主線程:JVM調(diào)用程序mian()所產(chǎn)生的線程。
當(dāng)前線程:這個是容易混淆的概念。一般指通過Thread.currentThread()來獲取的進(jìn)程。
后臺線程:指為其他線程提供服務(wù)的線程,也稱為守護(hù)線程。JVM的垃圾回收線程就是一個后臺線程。
前臺線程:是指接受后臺線程服務(wù)的線程,其實前臺后臺線程是聯(lián)系在一起,就像傀儡和幕后操縱者一樣的關(guān)系。傀儡是前臺線程、幕后操縱者是后臺線程。由前臺線程創(chuàng)建的線程默認(rèn)也是前臺線程。可以通過isDaemon()和setDaemon()方法來判斷和設(shè)置一個線程是否為后臺線程。