Java應(yīng)該是最近幾年用的比較多的編程語言,特別是對于網(wǎng)絡(luò)上的應(yīng)用,Java的面向?qū)ο筇匦钥梢院喕⒓铀賾?yīng)用程序開發(fā)。另外,網(wǎng)上如此多的開源程序、框架也值得自己借鑒。開始吧,Java之旅。

       多線程編程應(yīng)該是現(xiàn)在的程序所必需的。為了充分利用CPU(單核、多核)的計算能力,多線程編程是必不可少的。另外,Java的GUI編程也少不了多線程編程。本文應(yīng)該隨著自己對Java的多線程編程的深入持續(xù)更改。本文參考的文獻(xiàn)(文章)列于文章最后。

       一般來說,當(dāng)運(yùn)行一個應(yīng)用程序的時候,就啟動了一個進(jìn)程,當(dāng)然有些會啟動多個進(jìn)程。啟動進(jìn)程的時候,操作系統(tǒng)會為進(jìn)程分配資源,其中最主要的資源是內(nèi)存空間,因?yàn)槌绦蚴窃趦?nèi)存中運(yùn)行的。在進(jìn)程中,有些程序流程塊是可以亂序執(zhí)行的,并且這個代碼塊可以同時被多次執(zhí)行。實(shí)際上,這樣的代碼塊就是線程體。線程是進(jìn)程中亂序執(zhí)行的代碼流程。當(dāng)多個線程同時運(yùn)行的時候,這樣的執(zhí)行模式稱為并發(fā)執(zhí)行。 

Java編寫程序都運(yùn)行在在Java虛擬機(jī)(JVM)中,在JVM的內(nèi)部,程序的多任務(wù)是通過線程來實(shí)現(xiàn)的。每用java命令啟動一個java應(yīng)用程序,就會啟動一個JVM進(jìn)程。在同一個JVM進(jìn)程中,有且只有一個進(jìn)程,就是它自己。在這個JVM環(huán)境中,所有程序代碼的運(yùn)行都是以線程來運(yùn)行。

一般常見的Java應(yīng)用程序都是單線程的。比如,用java命令運(yùn)行一個最簡單的HelloWorld的Java應(yīng)用程序時,就啟動了一個JVM進(jìn)程,JVM找到程序程序的入口點(diǎn)main(),然后運(yùn)行main()方法,這樣就產(chǎn)生了一個線程,這個線程稱之為主線程。

對于一個進(jìn)程中的多個線程來說,多個線程共享進(jìn)程的內(nèi)存塊,當(dāng)有新的線程產(chǎn)生的時候,操作系統(tǒng)不分配新的內(nèi)存,而是讓新線程共享原有的進(jìn)程塊的內(nèi)存。因此,線程間的通信很容易,速度也很快。不同的進(jìn)程因?yàn)樘幱诓煌膬?nèi)存塊,因此進(jìn)程之間的通信相對困難。

實(shí)際上,操作的系統(tǒng)的多進(jìn)程實(shí)現(xiàn)了多任務(wù)并發(fā)執(zhí)行,程序的多線程實(shí)現(xiàn)了進(jìn)程的并發(fā)執(zhí)行。多任務(wù)、多進(jìn)程、多線程的前提都是要求操作系統(tǒng)提供多任務(wù)、多進(jìn)程、多線程的支持。 

在Java程序中,JVM負(fù)責(zé)線程的調(diào)度。線程調(diào)度是值按照特定的機(jī)制為多個線程分配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ā)(concurrency)執(zhí)行”、“同時”其實(shí)都不是真正意義上的“同時”。眾所周知,CPU都有個時鐘頻率,表示每秒中能執(zhí)行cpu指令的次數(shù)。在每個時鐘周期內(nèi),CPU實(shí)際上只能去執(zhí)行一條(也有可能多條)指令。操作系統(tǒng)將進(jìn)程線程進(jìn)行管理,輪流(沒有固定的順序)分配每個進(jìn)程很短的一段是時間(不一定是均分),然后在每個線程內(nèi)部,程序代碼自己處理該進(jìn)程內(nèi)部線程的時間分配,多個線程之間相互的切換去執(zhí)行,這個切換時間也是非常短的。因此多任務(wù)、多進(jìn)程、多線程都是操作系統(tǒng)給人的一種宏觀感受,從微觀角度看,程序的運(yùn)行是異步執(zhí)行的。

Java 虛擬機(jī)允許應(yīng)用程序并發(fā)地運(yùn)行多個執(zhí)行線程。Java語言提供了多線程編程的擴(kuò)展點(diǎn),并給出了功能強(qiáng)大的線程控制API。

 

在Java中,多線程的實(shí)現(xiàn)有兩種方式:

  1. 繼承java.lang.Thread類。覆蓋run方法。
  2. 實(shí)現(xiàn)java.lang.Runnable接口。實(shí)現(xiàn)run方法。

實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來運(yùn)行的。因此,不管是擴(kuò)展Thread類還是實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。

線程在一定條件下,狀態(tài)會發(fā)生變化。線程變化的狀態(tài)轉(zhuǎn)換圖如下:

 

1、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。

2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,變得可運(yùn)行,等待獲取CPU的使用權(quán)。

3、運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。

4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時停止運(yùn)行。直到線程進(jìn)入就緒狀態(tài),才有機(jī)會轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:

(一)、等待阻塞:運(yùn)行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。

(二)、同步阻塞:運(yùn)行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。

(三)、其他阻塞:運(yùn)行的線程執(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)先級高的線程會獲得較多的運(yùn)行機(jī)會。

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í)行機(jī)會讓給相同或者更高優(yōu)先級的線程。

 

5、線程加入:join()方法,等待其他線程終止。在當(dāng)前線程中調(diào)用另一個線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個進(jìn)程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。

 

6、線程喚醒:Object類中的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實(shí)現(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)廢除,不再介紹。因?yàn)橛兴梨i傾向。

常見線程名詞解釋

主線程:JVM調(diào)用程序mian()所產(chǎn)生的線程。

當(dāng)前線程:這個是容易混淆的概念。一般指通過Thread.currentThread()來獲取的進(jìn)程。

后臺線程:指為其他線程提供服務(wù)的線程,也稱為守護(hù)線程。JVM的垃圾回收線程就是一個后臺線程。

前臺線程:是指接受后臺線程服務(wù)的線程,其實(shí)前臺后臺線程是聯(lián)系在一起,就像傀儡和幕后操縱者一樣的關(guān)系。傀儡是前臺線程、幕后操縱者是后臺線程。由前臺線程創(chuàng)建的線程默認(rèn)也是前臺線程。可以通過isDaemon()和setDaemon()方法來判斷和設(shè)置一個線程是否為后臺線程。

在JavaSE5.0中添加了新的并行工具包,java.util.concurrent,java.util.concurrent.atomic,java.util.concurrent.locks。這三個新包的加入為java的多線程編程提供了更加方便的工具。目前正在學(xué)習(xí)中。

待續(xù)...

參考:

  1. Java多線程編程總結(jié) http://lavasoft.blog.51cto.com/62575/27069

文章來源:http://tangwei312tom.blog.163.com/blog/static/224731922008836409851