標籤:java線程 線程的聲明周期 線程調度 sleep方法 yield方法 join方法
線程線程和進程
進程:系統運行程式的最小單位;一個進程最少有一個線程
線程:程式執行任務的最小單位;線程與線程之間並行
一個進程至少有一個線程,在java中這個線程稱為主線程,由系統建立,運行main方法。這樣只有一個線程的程式也被稱為單線程程式。
主線程從程式入口main方法開始執行代碼,執行任意方法中的代碼都是按照自上而下的順序執行的,如果只有一個主線程,又想實現線上聽音樂這個功能的話,就很難實現。因為主線程必須先去下載音樂;下載完成後,在執行播放音樂;這顯然不能滿足當今人們對線上聽音樂的需求。所以現在的程式都是多線程的,就線上聽音樂這個功能而言,可以在main方法中建立一個線程負責下載音樂,在建立一個線程負責播放音樂。這樣在聽音樂的同時,用於還可以進行其他動作。
建立線程
建立線程的兩種方式
1、實現Runnable介面:還可以繼承其他類
2、繼承Thread類:使用簡單
方式一:<span style="font-size: 14px;">實現Runnable介面</span>package thread;/** * 建立MyRunnable類,並實現Runnable介面 * @author 學霸聯盟 - 趙燦 */public class MyRunnable implements Runnable {// 必須重寫Runnable介面中的方法,不重寫會出現語法錯誤// Runnable介面中只聲明了一個方法run()@Overridepublic void run() {// 線程執行的代碼System.out.println("MyRunnable-run");}}
方式二:package thread; /** * 建立MyThread類,並繼承Thread介面 * @author 學霸聯盟 - 趙燦 */public class MyThread extends Thread {/* * Thread類也實現了Runnable介面,並重寫了介面中的run方法 * 所以此處如果沒有重寫run方法,不會出現語法錯誤,但建立的這個線程也就沒有意義了 */@Overridepublic void run() {// 線程執行的代碼System.out.println("MyThread-run");}}
package thread;/** * 建立測試類別Test,測試以上兩種方式建立的線程 * @author 學霸聯盟 - 趙燦 */public class Test {public static void main(String[] args) {/*********** 方式一 ************/// 建立一個MyRunnable類型的對象mrMyRunnable mr = new MyRunnable();// 建立一個線程對象t;並將mr作為參數傳入Thread對象Thread t = new Thread(mr);/* * 注意:啟動線程調用的是Thread類中的start方法,而不是調用run方法; * 如果直接調用run方法,將只表示run方法的調用,不會啟動線程; * 調用start方法啟動線程t,線程啟動後,會自動調用對象mr中的run方法 */t.start();/*********** 方式二 ************/// 建立一個MyThread類型的對象mtMyThread mt = new MyThread();// 因為start方法在Thread類中聲明,MyThread類繼承了Thread類// 所以此處使用對象mt調用的start方法是從父類Thread中繼承的方法mt.start();}}輸出結果:MyRunnable-runMyThread-run或MyThread-runMyRunnable-run
由於多個線程是並存執行的,即同時運行。
就以上代碼而言,會先執行t.start();再執行mt.start();但是由於這裡的代碼非常簡單,在非常非常短的時間內,兩個現場就啟動完成了;而線程啟動後並不是立即執行的,要等待發送器選擇哪個線程執行,所以此處啟動的兩個線程誰先執行並不能確定。所以輸出順序也不確定。
線程的生命週期
建立(初始態):執行new操作後。此時在已經在記憶體中建立出了線程對象,但線程還沒有啟動運行
可運行:調用start()方法後。此時線程已經啟動,但還沒有運行(線程的執行是通過CPU執行完成的),另一種說法是:還沒有獲得CPU的使用權;正在和其他可運行狀態的線程一起等待系統的發送器選取,選中哪個線程,哪個現在使用CPU執行程式,執行一定的時間(CPU的時間片)後,退出對CPU的佔用,轉入可運行狀態,和其他線程一起等待系統發送器的選取。
運行:已獲得CPU的使用權,正在運行
阻塞:執行sleep(int)、yield()、join()、wait()方法後,阻塞狀態的線程不會被發送器選中。只有從阻塞狀態恢複至可運行態以後,才有機會獲得CPU的使用權
死亡:運行結束或執行stop()、destroy()方法後;這兩個方法均已淘汰,不推薦使用;此時線程已經徹底停止運行,釋放了線程所佔用的資源。
線程的調度sleep方法
static void sleep(int)靜態方法,建議使用類名Thread調用;
作用:線程休眠;哪個線程執行Thread.sleep();這條語句,哪個線程就會休眠(阻塞);int類型的參數,代表線程休眠的時間(毫秒),時間結束自動回復至可運行態,和其他線程一起等待發送器的選取
一句話:誰執行,誰休眠。
執行個體:package thread.sleep;/*** 建立SleepDemo類* 用於測試sleep方法* @author 學霸聯盟 - 趙燦*/public class SleepDemo{public static void main(String[] args){ System.out.println("主線程正在執行");System.out.println("主線程休眠開始");// 執行Thread.sleep();可能產生異常,使用try-catch語句捕獲異常try {// 這條語句會被主線程執行,執行後主線程休眠1000毫秒(1秒)Thread.sleep(1000);} catch (InterruptedException e) {// 如果try中的代碼產生異常,程式執行這條輸出語句System.out.println("線程休眠時出現異常");}System.out.println("主線程休眠結束");System.out.println("主線程恢複執行");}}輸出結果:主線程正在執行主線程休眠開始【這裡會休眠(停止)1秒,1秒後繼續輸出以下內容】主線程休眠結束主線程恢複執行
yield方法
static void yield()靜態方法,建議使用類名Thread調用。
作用:線程讓步;哪個線程執行Thread.yield();語句,哪個線程就會把已經獲得的CPU使用權讓出來,並進入可運行狀態,和其他線程一起等待發送器的選取;
一句話:誰執行,誰讓步。
執行個體:package thread.yield;/** * 建立YieldDemo類 * 用於測試線程的讓步執行 * @author 學霸聯盟 - 趙燦 */public class YieldDemo {public static void main(String[] args) {//聲明ThreadA和ThreadB的對象ThreadA ta = new ThreadA();ThreadB tb = new ThreadB();//啟動線程tata.start();//啟動線程tbtb.start();}}/** * 建立ThreadA類,並繼承Thread類 * @author 學霸聯盟 - 趙燦 */class ThreadA extends Thread{//重寫父類中的run方法@Overridepublic void run() {//迴圈30次for (int i = 1; i <= 30; i ++) {/* * 每迴圈一次ThreadA類的對象(本例是ta)就會讓出一次CPU的使用權, * 給其他線程執行(這個例子中是主線程和ThreadB的對象tb) */Thread.yield();//輸出一個字串,為了在結果中可以看到線程ta的執行頻率System.out.println("ThreadA-" + i);}}}/** * 建立ThreadB類,並繼承Thread類 * @author 學霸聯盟 - 趙燦 */class ThreadB extends Thread{//重寫父類中的run方法@Overridepublic void run() {//迴圈30次for (int i = 1; i <= 30; i ++) {//輸出一個字串,為了在結果中可以看到線程tb的執行頻率System.out.println("ThreadB--" + i);}}}輸出結果:輸出順序是不確定的;多數情況下,線程tb會先執行完,線程ta後執行完,而且線程tb的執行頻率要比線程ta高,但不是絕對的
join方法
void join([int] [,int]);非靜態方法,只能使用對象調用。
作用:線程插隊;假設現在有線程A和B,線程A中執行了B.join();語句,線程B會插隊到線程A前執行,線程A會被阻塞
一句話:誰調用,誰插隊;誰執行,誰阻塞。
其中可以傳入一個int類型的參數,代表插隊的毫秒數,如果傳入兩個int類型的參數,代表插隊的毫秒數(左)和納秒數(右);插隊時間結束後,插隊線程和被阻塞線程都回到可運行狀態,等待發送器的選取
執行個體:package thread.join;/** * 建立JoinDemo類 * 用於測試線程的讓步執行 * @author 學霸聯盟 - 趙燦 */public class JoinDemo {public static void main(String[] args) {//建立ThreadA的對象taThreadA ta = new ThreadA();//啟動線程tata.start();}}/** * 建立ThreadA類,並繼承Thread類 * @author 學霸聯盟 - 趙燦 */class ThreadA extends Thread{//重寫父類中的run方法@Overridepublic void run() {//建立ThreadB的對象tb;線程ta運行後便會先執行此句ThreadB tb = new ThreadB();//啟動線程tb;線程tb啟動後,此時線程ta和tb共同等待發送器的選取tb.start();//迴圈30次for (int i = 1; i <= 30; i ++) {/* * 當i等於10時,執行tb.join(); * 使線程tb插隊執行到線程ta前面執行 * 線程ta被阻塞 */if ( i == 10 ) {try {/* * 只需插隊一次即可 * 在i小於10時,線程ta和tb共同等待發送器的選取執行 * i==10時,執行tb.join(); * 執行後,線程ta被阻塞,只執行線程tb; * 直至線程tb執行完,ta才會恢複至可運行態 * 其中ta是代碼的執行者,tb是代碼的調用者 */tb.join();} catch (InterruptedException e) {//輸出棧記憶體中的異常資訊e.printStackTrace();}}//輸出一個字串,為了在結果中可以看到線程ta的執行頻率System.out.println("ThreadA-" + i);}}}/** * 建立ThreadB類,並繼承Thread類 * @author 學霸聯盟 - 趙燦 */class ThreadB extends Thread{//重寫父類中的run方法@Overridepublic void run() {//迴圈30次for (int i = 1; i <= 30; i ++) {//輸出一個字串,為了在結果中可以看到線程tb的執行頻率System.out.println("ThreadB--" + i);}}}由於輸出結果的順序完全不確定,這裡同學們一定要自行測試
本文出自 “學霸聯盟教育官方部落格” 部落格,轉載請與作者聯絡!
I學霸官方免費教程三十九 :Java基礎教程之線程