Java是一種可以撰寫跨平臺應用軟件的面向?qū)ο蟮某绦蛟O(shè)計語言。Java 技術(shù)具有卓越的通用性、高效性、平臺移植性和安全性,廣泛應用于PC、數(shù)據(jù)中心、游戲控制臺、科學超級計算機、移動電話和互聯(lián)網(wǎng),同時擁有全球最大的開發(fā)者專業(yè)社群。
給你JAVA學習路線:html-css-js-jq-javase-數(shù)據(jù)庫-jsp-servlet-Struts2-hibernate-mybatis-spring4-springmvc-ssh-ssm
基礎(chǔ)篇
一、進程和線程概念
在開始java線程的學習前,必須要了解計算機中的進程的概念。那什么是進程呢?其與線程有何關(guān)系?下面看下其概念的核心內(nèi)涵:
1. 進程:每個進程都有獨立的代碼和數(shù)據(jù)空間(進程上下文),進程間的切換會有較大的開銷,一個進程包含1-n個線程。
2. 線程:同一類線程共享代碼和數(shù)據(jù)空間,每個線程有獨立的運行棧和程序計數(shù)器(PC),線程切換開銷小。
3. 關(guān)系:很顯然,進程內(nèi)包含一或多個線程,且線程間共享進程所擁有的代碼和數(shù)據(jù)空間。
同時,了解進程和線程的含義時,還需要了解如下事實:
· 線程和進程生命周期一樣,分為五個階段:創(chuàng)建、就緒、運行、阻塞、終止。
· 多進程是指操作系統(tǒng)能同時運行多個任務(程序),比如windows中同時運行計算器和畫圖程序等。
· 多線程是指在同一程序中有多個任務流在執(zhí)行,且每個任務流的執(zhí)行非串行順序執(zhí)行的。
二、Java中創(chuàng)建線程的方式
理解力線程和進程后,我們看看如何用Java語言來創(chuàng)建線程。
在java中要想實現(xiàn)多線程,有兩種手段,一種是繼續(xù)Thread類,另外一種是實現(xiàn)Runable接口。
2.1 擴展java.lang.Thread類創(chuàng)建線程
代碼清單-1:繼承Thread類創(chuàng)建的線程
小編推薦一個學Java的學習裙【 六五零,五五四,六零七 】,無論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來了解一起進步一起學習!裙內(nèi)有開發(fā)工具,很多干貨和技術(shù)資料分享!
控制臺輸出的運行結(jié)果如下(注,每次運行的結(jié)果可能不一樣):
圖-1 MyThread線程運行結(jié)果圖
【說明:】
程序啟動運行的時候(運行程序),java虛擬機會立即創(chuàng)建主線程,主線程main在main()調(diào)用時候被創(chuàng)建。隨著調(diào)用main方法里的兩個對象的start方法,另外兩個線程也啟動了,這樣,整個應用就在多線程下運行。
注意:start()方法的調(diào)用后并不是立即執(zhí)行多線程代碼,而是使得該線程變?yōu)榭蛇\行態(tài)(Runnable),什么時候運行是由操作系統(tǒng)決定的。
從程序運行的結(jié)果可以發(fā)現(xiàn),多線程程序是亂序執(zhí)行。因此,只有亂序執(zhí)行的代碼才有必要設(shè)計為多線程。
Thread.sleep()方法調(diào)用目的是不讓當前線程獨自霸占該進程所獲取的CPU資源,以留出一定時間給其他線程執(zhí)行的機會。實際上所有的多線程代碼執(zhí)行順序都是不確定的,每次執(zhí)行的結(jié)果都是隨機的。
另外,如果一個線程對象的start方法被重復調(diào)用,那么程序運行時會拋出異常。比如把代碼清單-1main方法中的第二個線程對象改為:
MyThread mTh2=mTh1;//new MyThread("Thread-B");
其運行結(jié)果如下:
圖-2:重復調(diào)用線程對象的start方法結(jié)果
2.2 實現(xiàn)java.lang.Runnable接口創(chuàng)建線程
代碼清單-2:實現(xiàn)Runnable接口創(chuàng)建線程
運行上述程序,其控制臺輸出結(jié)果(每次運行的結(jié)果不盡相同)如下:
圖-2:通過接口實現(xiàn)的線程運行參考結(jié)果
【說明:】
MyRunnableThread類通過實現(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是進行多線程編程的基礎(chǔ)。
三、Thread和Runnable的區(qū)別
如果一個線程類繼承Thread,則不適合線程類對象內(nèi)的資源共享。但是如果實現(xiàn)了Runable接口的話,則很容易的實現(xiàn)資源共享。代碼清單如下:
清單-3:繼承Thread線程類(和清單-1類似)
清單-4:執(zhí)行線程代碼清單
圖-3:運行結(jié)果如下:
從上面可以看出,不同的線程之間count是不同的(其實也是不同的線程對象),這對于賣票系統(tǒng)來說就會有很大的問題。當然,這里可以用同步來實現(xiàn)。
接下來我們用Runnable接口來實現(xiàn)看看:
清單-5:實現(xiàn)Runnable接口的線程
代碼清單-6:執(zhí)行實現(xiàn)了Runnable接口的線程類
運行結(jié)果如下:
圖-4:實現(xiàn)資源共享
這里要注意每個線程都是用同一個實例化對象,如果不是同一個,效果就和上面的一樣了!
【總結(jié):】
實現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
1):適合多個相同的程序代碼的線程去處理同一個資源;
2):可以避免java中的單繼承的限制;
3):增加程序的健壯性,代碼可以被多個線程共享,代碼和數(shù)據(jù)獨立;
另外,這里提醒說明一下:main方法其實也是一個線程。在java中所有的線程都是同時啟動的,至于什么時候,哪個先執(zhí)行,完全看誰先得到CPU的資源。
在java中,每次程序運行至少啟動2個線程。一個是main線程(前文提到過的主線程),一個是垃圾收集線程。因為每當使用java命令執(zhí)行一個類的時候,實際上都會啟動一個JVM,而每一個JVM就是在操作系統(tǒng)中啟動了一個進程。
四、線程狀態(tài)轉(zhuǎn)換
小編推薦一個學Java的學習裙【 六五零,五五四,六零七 】,無論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來了解一起進步一起學習!裙內(nèi)有開發(fā)工具,很多干貨和技術(shù)資料分享!
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),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
五、線程調(diào)度
關(guān)于線程的調(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 分配給線程的默認優(yōu)先級,取值為5。
Thread類的setPriority()和getPriority()方法分別用來設(shè)置和獲取線程的優(yōu)先級。
每個線程都有默認的優(yōu)先級。主線程的默認優(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)中,應該僅僅使用Thread類有以下三個靜態(tài)常量作為優(yōu)先級,這樣能保證同樣的優(yōu)先級采用了同樣的調(diào)度方式。
2、線程睡眠:Thread.sleep(long millis)方法,使線程轉(zhuǎn)到阻塞狀態(tài)。millis參數(shù)設(shè)定睡眠的時間,以毫秒為單位。當睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()平臺移植性好。
3、線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價于調(diào)用 wait(0) 一樣。
4、線程讓步:Thread.yield() 方法,暫停當前正在執(zhí)行的線程對象,把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。
5、線程加入:join()方法,等待其他線程終止。在當前線程中調(diào)用另一個線程的join()方法,則當前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個進程運行結(jié)束,當前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
6、線程喚醒:Object類中的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實現(xiàn)做出決定時發(fā)生。線程通過調(diào)用其中一個 wait 方法,在對象的監(jiān)視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監(jiān)視器上等待的所有線程。
注意:
Thread中suspend()和resume()兩個方法在JDK1.5中已經(jīng)廢除,不再介紹。因為有死鎖傾向。
請持續(xù)關(guān)注,后續(xù)多線程內(nèi)容,還會推出API參考說明以及同步和數(shù)據(jù)傳遞相關(guān)內(nèi)容和演示案例。


