Java多線程(三)關于多線程管理的相關函數說明

接著前面的多線程(二)的內容,下面我們接著來探討多個線程創建之后,關于線程調度和管理的一些方法。

先來簡單介紹下線程調度###

對于計算機的CPU(以單核為例),在任意時刻只能執行一條指令,每個線程只有獲得CPU的使用權才能執行指令。當有多個處于可運行狀態的線程在等待CPU,JVM的一項任務就是負責線程的調度。JVM按照特定機制為多個線程分配CPU的使用權過程就是線程調度。

線程調度的兩種模型:分時調度模型和搶占式調度模型。
① 分時調度模型是指讓所有的線程輪流獲得cpu的使用權,并且平均分配每個線程占用的CPU的時間片,這個比較好理解。
② JVM采用搶占式調度模型,就是讓線程搶奪CPU資源,運行順序是不確定的,優先權高的線程,會有一定幾率優先占用CPU。處于運行狀態的線程會一直運行下去,直至它不得不放棄CPU。比如線程運行完畢、線程阻塞、運行被打斷。關于線程的多種運行狀態詳見:Java中的多線程(二)線程的創建及線程的生命周期。當線程被中斷時,CPU會保存當前線程的狀態,以備此線程被喚醒時繼續執行。

線程管理之設置線程優先級###

Java線程優先級共有10個級別,優先級較高的線程會獲得較多的運行機會,取值范圍是從1到10。如果小于1或大于10,則JDK拋出異常IllegalArgumentException()。Thread類有以下三個靜態常量:
① static final int MAX_PRIORITY :值為10,代表最高優先級。
② static final int MIN_PRIORITY :值為1,代表最低優先級。
③ static final int NORM_PRIORITY:值為5,代表默認優先級。

這里要注意的是:
① 并不是說優先級較高的線程一定會在優先級較低的線程之前運行,優先級高這里是指獲得較多的運行機會。
② 優先級高的線程會大部分先執行完,并不一定會全部執行完畢。
③ 子線程的優先級是跟父類優先級是一樣的。

設置和獲取優先級方法:
thread.setPriority(Thread.MIN_PRIORITY)和 thread.getPriority()

線程管理之守護線程###

我們在程序中創建的線程默認都是用戶線程(User Thread)。與用戶線程對應則是守護線程(Daemon Thread),也可稱之為后臺線程,它的作用就是為其它線程提供服務的。守護線程使用的情況較少,舉例來說:JVM的垃圾(GC)回收線程就是守護線程。

需要注意的是:
① 當所有的用戶線程都結束退出的時候,守護線程也就沒啥可服務的了,隨著線程的結束而結束。如果JVM只剩下守護線程,虛擬機就會退出。
② 守護線程會隨時中斷,因此不要在如輸入輸出流,數據庫連接等場合使用守護線程。
③ 守護線程并非是JVM內部可提供,我們自己可以根據需要來設定守護線程。可以通過isDaemon和setDaemon方法來判斷和設置一個線程為守護線程。
④ 守護線程必須在start方法前設置,否則會拋出IllegalThreadStateException異常。
⑤ 一個守護線程創建的子線程依然是守護線程。

package com.Dan;

public class Main {

    public static void main(String[] args) {
        // write your code here
        DaemonThread daemonThread = new DaemonThread();
        // 設置為守護線程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}

class DaemonThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + " start");

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " end");
    }
}
// Thread-0 start
// 子線程需要執行1秒,因為設置被設置為守護線程,因此主線程不會等待子線程執行結束,而是提前退出。
// 所有用戶線程都退出了,守護線程也就退出了,因此并沒有打印end。

線程管理之常用方法###

一些簡單方法#####

public static int activeCount():返回當前線程的線程組中活動線程的數目。
public static Thread currentThread():返回對當前正在執行的線程對象的引用。
public long getId():返回該線程的標識符,線程 ID 是唯一的。
public final void setName(String name):改變線程名稱。
public final String getName():返回該線程的名稱。
public String toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。
public void start():使該線程開始執行,Java 虛擬機調用該線程的 run 方法。

下面是一些比較重要的方法:

sleep()方法 線程睡眠#####

sleep方法作用就是讓當前線程休眠,交出CPU,讓CPU去執行其他的任務。當前線程是指this.currentThread()返回的線程。sleep方法使線程轉到阻塞狀態,當睡眠結束后,就轉為可運行狀態。

package com.Dan;

/**
 * Created by daniel on 17/3/27.
 */
public class 多線程博客三 {
    public static void main(String[] args) {

        System.out.println(Thread.currentThread().getName()+"主線程開始執行。");
        TaskThread thread1 = new TaskThread("線程-A-");
        TaskThread thread2 = new TaskThread("線程-B-");
        thread1.start();
        thread2.start();
        System.out.println(Thread.currentThread().getName()+ "主線程運行結束。");
    }
}

class TaskThread extends Thread{
    private String name;

    public TaskThread(String name) {
        this.name = name;
    }
    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + "子線程運行開始。");
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "運行: " + i);
            try {
                sleep((long) Math.random() * 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "子線程運行結束!");
    }
}

// main主線程開始執行。
// Thread-0子線程運行開始。
// Thread-1子線程運行開始。
// 線程-B-運行: 0
// main主線程運行結束。
// 線程-A-運行: 0
// 線程-B-運行: 1
// 線程-A-運行: 1
// 線程-B-運行: 2
// 線程-A-運行: 2
// 線程-B-運行: 3
// 線程-A-運行: 3
// 線程-B-運行: 4
// 線程-A-運行: 4
// Thread-1子線程運行結束。
// Thread-0子線程運行結束。

// sleep方法使當前線程休眠,交出CPU,讓CPU去執行其他的任務。這里的主線程,早在子線程開始后就馬上結束了。
join()線程加入#####

在當前線程中調用另一個線程的join方法,則當前線程轉入阻塞狀態,直到另一個進程運行結束,當前線程再由阻塞轉為可運行狀態。如果調用此方法時,另一個線程已經運行完畢,那就接著運行當前線程。

package com.Dan;

/**
 * Created by daniel on 17/3/27.
 */
public class ThreadJoin {
    
    public static void main(String[] args) {
        
        Thread thread = new Thread(new Runnable() { //匿名對象
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + " 子線程 " + i);
                }
            }
        });
        
        thread.start();
        
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                try {
                    thread.join(); // 此時調用Thread線程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " 主線程 " + i);
        }
    }
}

// main 主線程 0
// main 主線程 1
// main 主線程 2
// main 主線程 3
// main 主線程 4
// Thread-0 子線程 0
// Thread-0 子線程 1
// Thread-0 子線程 2
// Thread-0 子線程 3
// Thread-0 子線程 4
// Thread-0 子線程 5
// Thread-0 子線程 6
// Thread-0 子線程 7
// Thread-0 子線程 8
// Thread-0 子線程 9
// main 主線程 5
// main 主線程 6
// main 主線程 7
// main 主線程 8
// main 主線程 9
yield()線程讓步#####

暫停當前正在運行的線程,把運行機會讓給其它的線程。這里的暫停,并不是讓線程轉到阻塞或等待狀態,而是返回可運行狀態,等待被調度運行。需要注意的是,此時讓步的線程是可運行狀態,它有可能會被再次運行。

package com.Dan;

/**
 * Created by daniel on 17/3/24.
 */

public class JavaEveryDay0324 extends Thread{

    public static void main(String[] args) {
        JavaEveryDay0324 test1 = new JavaEveryDay0324();
        JavaEveryDay0324 test2 = new JavaEveryDay0324();
        test1.start();
        test2.start();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "  1");

        yield();
        System.out.println(Thread.currentThread().getName() + "  2");
    }
}

// Thread-0  1
// Thread-1  1
// Thread-0  2
// Thread-1  2
// 線程test1在輸出1后,執行yield()方法進入可運行狀態,然后將CPU讓給線程test2。同樣線程test2運行輸出1后,執行yield()方法也進入了可運行狀態,將CPU又讓給線程test1,線程test1繼續執行,打印輸出2,然后線程test2執行輸出2。

// Thread-0  1
// Thread-0  2
// Thread-1  1
// Thread-1  2
// 也有可能出現這種情況,多運行幾次,肯定會有的。

// Thread-0  1
// Thread-1  1
// Thread-1  2
// Thread-0  2
// 又或者是這一種。只要記住它返回的是可運行狀態,不是阻塞,也不是等待。那么不同的結果就能解釋清楚了。
** interrupt()線程中斷信號**#####

interrupt():這里只談中斷信號,中斷線程后續博客會更新。它向線程發送一個中斷信號,強制結束調用該方法的線程當前狀態,讓線程在無限等待時(如死鎖時)能拋出異常。

package com.Dan;

/**
 * Created by daniel on 17/3/27.
 */
public class ThreadInterrupt {

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000000); // 別想醒過來了
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println("拋異常嘍吆唉哈咦。");
                }
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + " 子線程 " + i);
                }
            }
        });

        thread.start();

        for (int i = 0; i < 6; i++) {
            if (i == 2) {
                thread.interrupt(); // 終止當前線程的狀態,并拋出個異常: e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "輸出:" + i);
        }
    }
}

// main輸出:0
// main輸出:1
// 拋異常嘍吆唉哈咦。
// main輸出:2
// Thread-0 子線程 0
// Thread-0 子線程 1
// Thread-0 子線程 2
// Thread-0 子線程 3
// Thread-0 子線程 4
// Thread-0 子線程 5
// main輸出:3
// main輸出:4
// Thread-0 子線程 6
// Thread-0 子線程 7
// Thread-0 子線程 8
// Thread-0 子線程 9
// main輸出:5

** wait()線程等待**#####

這里先簡單介紹下wait方法,我們會在下一篇博客探討線程同步相關知識的時候,再來探討它。

在線程通信中,wait方法經常與notify和notifyAll方法一起使用。,他們并不是Thread類的方法,而是Object類的方法。
① wait:當達到某種狀態的時候,wait方法讓線程進入等待狀態,讓本線程休眠。線程自動釋放其占有的對象鎖,并等待notify。直到有其它線程調用對象的notify方法喚醒該線程,才能繼續獲取對象鎖,繼續運行。
② notify:喚醒一個正在等待當前對象鎖的線程,并讓它拿到對象鎖。
③ notifyAll:喚醒所有正在等待前對象鎖的線程。

需要注意的是:
① 在調用這3個方法的時候,當前線程必須獲得這個對象的鎖,也就是說這三個方法必須在synchronized(Obj){...}內部。
② 在調用notify方法后,并不會馬上釋放對象鎖,而是在synchronized(){}執行結束的時候,自動釋放鎖,JVM會隨機喚醒一個正在等待當前對象鎖的線程,讓他獲得對象鎖,喚醒線程。

** wait()與sleep()方法的區別**#####

① sleep方法是一個靜態方法,作用在當前線程上。而wait方法是一個實例方法,并且只能在其他線程調用本實例的notify方法時被喚醒。
② wait只能在線程同步環境中被調用,會釋放鎖。而sleep不限制使用環境,當在一個Synchronized塊中調用Sleep方法時,線程雖然休眠了,但是對象的鎖并木有被釋放,其他線程無法訪問這個對象。
③ sleep必須捕獲異常,而wait,notify和notifyAll則不需要。
④ 進入等待狀態的線程能夠被notify方法喚醒。sleep休眠時間到了,該線程不一定會立即執行,因為其它線程可能正在運行。
⑤ 如果你需要暫停某個線程一段特定的時間,就使用sleep方法。如果你想要實現線程間通信就使用wait方法。

** 關于 “調用yield()方法后,會選擇同等優先級的線程繼續執行。” 勘誤**#####

看到網上有人說調用yield()方法后,會選擇同等優先級或更高優先級的線程繼續執行。這個觀點是錯誤的。具體執行執行哪個線程是由JVM說了算。請看下面的例子,低優先級的也會被執行。

// 在上面方法基礎上重新修改的
package com.Dan;

/**
 * Created by daniel on 17/3/24.
 */

public class JavaEveryDay0324 extends Thread{

    public static void main(String[] args) {
        JavaEveryDay0324 test3 = new JavaEveryDay0324();
        JavaEveryDay0324 test1 = new JavaEveryDay0324();
        JavaEveryDay0324 test2 = new JavaEveryDay0324();

        test3.setPriority(1);
        test2.setPriority(10);
        test1.setPriority(5);
        test3.start();
        test1.start();
        test2.start();
        System.out.println("線程1ID: "+test1.getId()+" 線程2ID: "+test2.getId()+" 線程3ID: "+test3.getId());

    }
    @Override
    public void run() {


        for (int i = 0; i < 3; i++) {
            System.out.println("線程ID為"+Thread.currentThread().getId()+": " + " 的第①次打印");

            yield();
            System.out.println("線程ID為"+Thread.currentThread().getId() +": " +  " 的第②次打印");
        }
    }
}

// 線程ID為10:  的第①次打印
// 線程ID為11:  的第①次打印
// 線程ID為12:  的第①次打印
// 線程1ID: 11 線程2ID: 12 線程3ID: 10
// 線程ID為10:  的第②次打印
// 線程ID為10:  的第①次打印
// 線程ID為11:  的第②次打印
// 線程ID為11:  的第①次打印
// 線程ID為12:  的第②次打印
// 線程ID為12:  的第①次打印
// 線程ID為10:  的第②次打印
// 線程ID為10:  的第①次打印
// 線程ID為11:  的第②次打印
// 線程ID為11:  的第①次打印
// 線程ID為12:  的第②次打印
// 線程ID為12:  的第①次打印
// 線程ID為10:  的第②次打印
// 線程ID為12:  的第②次打印
// 線程ID為11:  的第②次打印

寫完嘍!ㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏ


知識重在總結和梳理,只有不斷地去學習并運用,才能化為自己的東西。當你能為別人講明白的時候,說明自己已經掌握了。

歡迎轉載,轉載請注明出處!

如果有錯誤的地方,或者有您的見解,還請不嗇賜教!

喜歡的話,麻煩點個贊!

Java中的多線程(一)多線程基礎之進程、線程、并發、并行。
Java中的多線程(二)線程的創建及線程的生命周期

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,477評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,978評論 1 18
  • 該文章轉自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,373評論 3 87
  • 寫在前面的話: 這篇博客是我從這里“轉載”的,為什么轉載兩個字加“”呢?因為這絕不是簡單的復制粘貼,我花了五六個小...
    SmartSean閱讀 4,770評論 12 45
  • 早上去看了一場電影。 這兩天心情一直不好,周末了也一個人在賓館,怕憋出病來,于是早上起來定了9:55的《木乃伊》。...
    Evan0827閱讀 242評論 0 0