系統化學習多執行緒(一)

大綱

————————-學前必讀———————————-

學習不能快速成功,但一定可以快速入門
整體課程思路:
1.實踐為主,理論化偏少
2.課程筆記有完整的案例和程式碼,(為了學習效率)再開始之前我會簡單粗暴的介紹知識點案例思路,
有基礎的同學聽了之後可以直接結合筆記寫程式碼,
如果沒聽懂再向下看影片,我會手把手編寫程式碼和演示測試結果;
3.重要提示,學編程和學游泳一樣,多實踐學習效率才高,理解才透徹;
4.編碼功底差的建議每個案例程式碼寫三遍,至於為什麼…<<賣油翁>>…老祖宗的智慧

————————————————————————-

 1.執行緒

1.1.什麼是執行緒

執行緒(英語:thread)是作業系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條執行緒指的是進程中一個單一順序的控制流,一個進程中可以並發多個執行緒,每條執行緒並行執行不同的任務。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內核執行緒(kernel thread),而把用戶執行緒(user thread)稱為執行緒。(來自百度百科)

一個進程可以有很多執行緒,每條執行緒並行執行不同的任務。

1.2.多執行緒hello word

需求:模擬在計算上一邊聽歌一邊打遊戲

三種實現方案如下:

TestDemo

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo01.GameRunnable;
 4 import com.wfd360.thread.demo01.MusicRunnable;
 5 import com.wfd360.thread.demo02.GameThread;
 6 import com.wfd360.thread.demo02.MusicThread;
 7 import org.junit.Test;
 8 
 9 /**
10  * @author 姿勢帝-部落格園
11  * @address //www.cnblogs.com/newAndHui/
12  * @WeChat 851298348
13  * @create 05/03 5:27
14  * @description 需求分析:
15  * 1.模擬一邊打遊戲一邊聽音樂,在控制台列印輸出模擬
16  * 2.把兩個業務封裝成獨立的執行緒,實現介面Runnable或繼承Thread,通過看源碼你會發現Thread類實現了介面Runnable,使用本質上這兩種方法時一樣的。
17  * 3.Thread類提供兩個方法,執行緒主題方法run,啟動執行緒方法start
18  */
19 public class TestDemo {
20     /**
21      * 方式1:實現介面Runnable
22      */
23     @Test
24     public void testRunnable() throws InterruptedException {
25         System.out.println("-------test start-------");
26         // 實例對象
27         MusicRunnable music = new MusicRunnable();
28         GameRunnable game = new GameRunnable();
29         // 創建執行緒
30         Thread musicThread = new Thread(music);
31         Thread gameThread = new Thread(game);
32         // 啟動執行緒
33         musicThread.start();
34         gameThread.start();
35         System.out.println("--------等待其他執行緒執行--------------");
36         Thread.sleep(5 * 1000);
37         System.out.println("-------test end-------");
38     }
39 
40     /**
41      * 方式2:繼承Thread
42      */
43     @Test
44     public void testThread() throws InterruptedException {
45         System.out.println("-------test start-------");
46         // 創建執行緒
47         MusicThread musicThread = new MusicThread();
48         GameThread gameThread = new GameThread();
49         // 啟動執行緒
50         musicThread.start();
51         gameThread.start();
52         System.out.println("--------等待其他執行緒執行--------------");
53         Thread.sleep(5 * 1000);
54         System.out.println("-------test end-------");
55     }
56 
57     /**
58      * 方式3:簡寫,這種寫法一般我們在做模擬測試的使用,在正式程式碼中建議不使用,可讀性較差
59      */
60     @Test
61     public void testThreadSimple() throws InterruptedException {
62         System.out.println("-------test start-------");
63         // 創建執行緒
64         Thread musicThread = new Thread(() -> {
65             for (int i = 0; i < 100; i++) {
66                 System.out.println("=======聽音樂中============" + i);
67             }
68         });
69         Thread gameThread = new Thread(() -> {
70             for (int i = 0; i < 100; i++) {
71                 System.out.println("=======打遊戲中============" + i);
72             }
73         });
74         // 啟動執行緒
75         musicThread.start();
76         gameThread.start();
77         System.out.println("--------等待其他執行緒執行--------------");
78         Thread.sleep(5 * 1000);
79         System.out.println("-------test end-------");
80     }
81 }


 實現介面Runnable

 1 package com.wfd360.thread.demo01;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 5:31
 8  * @description
 9  */
10 public class GameRunnable implements Runnable {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("=======打遊戲中============" + i);
15         }
16     }
17 }

GameRunnable

 1 package com.wfd360.thread.demo01;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 5:29
 8  * @description
 9  */
10 public class MusicRunnable implements Runnable {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("=======聽音樂中============"+i);
15         }
16     }
17 }

MusicRunnable

 繼承Thread

 1 package com.wfd360.thread.demo02;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 6:00
 8  * @description
 9  */
10 public class GameThread extends Thread {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("-------遊戲中----------"+i);
15         }
16     }
17 }

GameThread

GameThread

 1 package com.wfd360.thread.demo02;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 6:00
 8  * @description
 9  */
10 public class MusicThread extends Thread {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("-------音樂中----------" + i);
15         }
16     }
17 }


 總結

啟動執行緒兩種方式:

    1.通過繼承Thread類

    2.實現Runnable介面

 使用哪種方式更好?

區別: 

一個類如果繼承了其他類,就無法在繼承Thread類,在Java中,一個類只能繼承一個類,而一個類如果實現了一個介面,還可以實現其他介面,介面是可以多實現的,所以說

Runable的擴展性更強,但是繼承的方式更簡單,個人建議一般情況下使用Thread;

實現介面Runnable或繼承Thread,通過看源碼你會發現Thread類實現了介面Runnable,使用本質上這兩種方法是一樣的

啟動執行緒流程:

    創建啟動執行緒的方式一:繼承Thread類

       1.將業務方法封裝成執行緒對象,自定義類t extends Thread類; 

       2.覆寫run方法: 覆寫第一步中的run方法;

       3.創建自定義對象t

       4.啟動執行緒 t.start();

   創建啟動執行緒方式二:實現Runnable介面

      1.將業務方法封裝成執行緒對象,自定義類t implements Runnable介面;

      2.實現第一步中的run方法

      3.創建自定義對象t

      4.啟動執行緒 new Thread(t).start();

1.3.對主執行緒與創建執行緒執行順序的理解

問題:
直接寫一個簡單的HelloWorld 程式,有沒有執行緒?
==>有一個主執行緒,在垃圾回收的時候,有gc 執行緒。

 1 package com.wfd360.thread;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * @author 姿勢帝-部落格園
 7  * @address //www.cnblogs.com/newAndHui/
 8  * @WeChat 851298348
 9  * @create 05/04 11:09
10  * @description <p>
11  * 問題:
12  * 直接寫一個簡單的HelloWorld 程式,有沒有執行緒?
13  * ==>有一個主執行緒,在垃圾回收的時候,有gc 執行緒。
14  * 結論:一旦執行緒啟動起來之後就是獨立的,和創建環境沒有關係;
15  * 啟動執行緒不能直接調用run方法,必須調用start方法;
16  * </p>
17  */
18 public class TestDemo02 {
19     /**
20      * 如果把創建執行緒放在循環語句的 下 面,會交替出現嗎
21      * ==>否,因為主執行緒執行完成後才會啟動hello執行緒
22      *
23      * @throws Exception
24      */
25     @Test
26     public void test1() throws Exception {
27         System.out.println("---test start-------");
28         // 執行主執行緒
29         for (int i = 0; i < 100; i++) {
30             System.out.println("-----test1--------" + i);
31         }
32         // 啟動hello執行緒
33         new HelloThread().start();
34         System.out.println("=======等待執行完成===========");
35         Thread.sleep(5 * 1000);
36         System.out.println("---test end-------");
37     }
38 
39     /**
40      * 如果把創建執行緒放在循環語句的 上 面,會交替出現嗎
41      * ==>可能會,可能不會,可能出現for循環完之後,執行緒還沒有啟動完;
42      *
43      * @throws Exception
44      */
45     @Test
46     public void test2() throws Exception {
47         System.out.println("---test start-------");
48         // 啟動hello執行緒
49         new HelloThread().start();
50         // 執行主執行緒
51         for (int i = 0; i < 100; i++) {
52             System.out.println("-----test1--------" + i);
53         }
54         System.out.println("=======等待執行完成===========");
55         Thread.sleep(5 * 1000);
56         System.out.println("---test end-------");
57     }
58 
59     /**
60      * 採用內部類的方式定義一個hello執行緒對象
61      */
62     class HelloThread extends Thread {
63         @Override
64         public void run() {
65             for (int i = 0; i < 100; i++) {
66                 System.out.println("-----HelloThread--------" + i);
67             }
68         }
69     }
70 }

TestDemo02

結論:一旦執行緒啟動起來之後就是獨立的,和創建環境沒有關係;
啟動執行緒不能直接調用run方法,必須調用start方法;

 1.4.對sleep方法的理解

package com.wfd360.thread;

/**
 * @author 姿勢帝-部落格園
 * @address //www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 05/04 11:34
 * @description <p>
 * Thread類的方法:
 * static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行);
 * </p>
 */
public class TestSleep {
    /**
     * 做一個簡易倒計時,10秒鐘,控制台每一秒輸出一個數字,如10,9,8,7.....0
     */
    public static void main(String[] args) throws Exception {
        System.out.println("---test start-------");
        for (int i = 10; i >= 0; i--) {
            Thread.sleep(1 * 1000);
            System.out.println(i);
        }
        System.out.println("---test end-------");
    }
}

1.5.執行緒名稱的設置與獲取

繼承方式

簡單需求:使用多執行緒模擬多窗口售票

 1 package com.wfd360.thread.demo03Ticket;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/04 11:55
 8  * @description <p>
 9  * 模擬多執行緒售票
10  * </p>
11  */
12 public class TicketThread extends Thread {
13     // 假定票總是100張
14     private static Integer num = 100;
15 
16     @Override
17     public void run() {
18         // 只要有票就一直售票
19         while (num > 0) {
20             System.out.println("正在出售第" + num + "張票");
21             --num;
22         }
23         System.out.println("===售票結束===");
24     }
25 }

TicketThread

test

/**
     * 測試模擬三個窗口售票
     * @throws InterruptedException
     */
    @Test
    public void testTicketThread() throws InterruptedException {
        System.out.println("---test start-------");
        // 模擬多3個窗口售票
        TicketThread ticketThread1 = new TicketThread();
        TicketThread ticketThread2 = new TicketThread();
        TicketThread ticketThread3 = new TicketThread();
        // 啟動執行緒售票
        ticketThread1.start();
        ticketThread2.start();
        ticketThread3.start();
        System.out.println("======等待售票============");
        Thread.sleep(5 * 1000);
        System.out.println("---test end-------");
    }

結果:

1.在售票過程中不能區分售出的票是那個窗口售出的,解決通過執行緒名稱判斷

2.有重複售出的票(後面的執行緒同步解決)

解決第一個問題,設置獲取執行緒名稱,通過Thread對象裡面自帶的getName,setName方法

 具體程式碼

設置執行緒名稱

 獲取執行緒名稱

 上面講了繼承的方式獲取執行緒名稱,那麼實現介面Runnable的方式怎麼獲取設置勒

繼承Thread的方式,可以通過getName的方式獲取當前執行緒的名稱?
那使用Runnable的方式,能通過getName獲取嘛?

getName方法是Thread類的,但是TicketThread現在並沒有繼承Thread類,而是實現了Runnable介面.

問題:如果實現Runnable介面,怎麼獲取執行緒名稱?

 思考:TicketThread類裡面的程式碼要執行,它肯定存在於某個執行緒中, 就比如寫個helloword列印語句,是不是也處於一個主執行緒中,那這裡怎麼獲取執行緒名稱?
通過動態獲取,當程式正在執行的時候,獲取當前正在執行的執行緒名稱。怎麼獲取?
在Thread類裡面有個靜態的方法currentThread() 方法,返回當前正在執行的執行緒引用;

Thread.currentThread().getName

那怎麼設置執行緒名稱?

Thread類裡面有個name欄位,相當於Thread類把它包裝了一下:

通過源碼可以發現,構造方法裡面還有可以傳一個名字:

具體實現程式碼如下

 

 總結:

繼承方式設置\獲取執行緒名稱通過 Thread對象裡面的 setName,getName方法;

實現介面方式設置名稱通過 new Thread(‘執行緒實例對象’, “執行緒名稱”),獲取執行緒名稱通過:Thread.currentThread().getName

1.6.Thread的join方法

void join() 方法 :等待該執行緒終止
void join(long millis) 方法 :等待該執行緒終止的時間最長為millis毫秒

需求: 當主執行緒運行到20的時候(i =20)的時候,讓JoinThread執行緒加進來直到執行完成,在執行主執行緒.

 1 package com.wfd360.thread;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * @author 姿勢帝-部落格園
 7  * @address //www.cnblogs.com/newAndHui/
 8  * @WeChat 851298348
 9  * @create 05/04 6:31
10  * @description
11  */
12 public class Test05Join {
13     /**
14      * 需求:
15      * 當主執行緒for循環到i=20時,等JoinThread執行緒執行完成後,在執行for循環的執行緒
16      * @throws InterruptedException
17      */
18     @Test
19     public void testJoinThread() throws InterruptedException {
20         System.out.println("---test start-------");
21         // 開啟執行緒
22         JoinThread thread = new JoinThread();
23         thread.start();
24         // 循環列印執行緒
25         for (int i = 0; i < 100; i++) {
26             System.out.println("======testJoinThread=========="+i);
27             Thread.sleep(1);
28             if (i==20){
29                 // 等執行緒JoinThread執行完成
30                 thread.join();
31             }
32         }
33         System.out.println("=============等待執行緒執行完成===================");
34         Thread.sleep(10*1000);
35         System.out.println("---test end-------");
36     }
37     
38     class JoinThread extends Thread {
39         @Override
40         public void run() {
41             for (int i = 0; i < 100; i++) {
42                 System.out.println("=====JoinThread=======" + i);
43                 // 模擬處理很多業務耗時1毫秒
44                 try {
45                     Thread.sleep(1);
46                 } catch (InterruptedException e) {
47                     e.printStackTrace();
48                 }
49             }
50         }
51     }
52 }

Test05Join

1.7.執行緒優先順序

直接上程式碼

 1 package com.wfd360.thread;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 8:17
 8  * @description <p>
 9  * 1.==>執行緒優先順序的理解:
10  * 執行緒的優先順序和生活中類似,高優先順序執行緒的執行優先於低優先順序執行緒;
11  * 並不是絕對的,可能優先順序高的執行緒優先 比 優先順序低的執行緒先執行,只能說,高優先順序的執行緒優先執行的幾率更多;
12  * (比如兩個執行緒,一個優先順序高,一個優先順序低,如果一共運行一個小時,優先順序高的執行緒執行遠遠大於優先順序低的但是並不是說優先順序高的先執行完,
13  * 在執行優先順序低的)
14  * 2.==>重新設置執行緒優先順序
15  * int getPriority() 返回執行緒的優先順序。
16  * void setPriority(int newPriority) 更改執行緒的優先順序。Java執行緒的優先順序從1到10級別,值越大優先順序越高.
17  * 3.==>執行緒的默認優先順序受創建執行緒的環境影響,默認值5,自定義執行緒的默認優先順序和創建它的環境的執行緒優先順序一致
18  * </p>
19  */
20 public class Test06Priority {
21     /**
22      * 測試獲取執行緒優先順序,設置執行緒優先順序,驗證執行緒優先順序受創建環境影響
23      * @param args
24      */
25     public static void main(String[] args) {
26         Thread threadMain = Thread.currentThread();
27         // 獲取默認優先順序數字
28         System.out.println("main執行緒默認優先順序:" +threadMain.getPriority());// 5
29         // 重新設置默認優先順序數字
30         threadMain.setPriority(8);
31         // 再次重新獲取優先順序數字
32         System.out.println("main執行緒修改後的優先順序:" +threadMain.getPriority());// 8
33         // 創建一個執行緒查看優先順序
34         Thread thread = new Thread();
35         System.out.println("thread執行緒的優先順序:" +thread.getPriority());// 8 受創建環境影響
36     }
37 }

1.8.後台執行緒,即守護執行緒

直接看程式碼

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo04Daemon.DaemonThreaad;
 4 
 5 /**
 6  * @author 姿勢帝-部落格園
 7  * @address //www.cnblogs.com/newAndHui/
 8  * @WeChat 851298348
 9  * @create 05/05 9:12
10  * @description <p>
11  * 後台執行緒,即守護執行緒
12  * 後台執行緒:指為其他執行緒提供服務的執行緒,也稱為守護執行緒。JVM的垃圾回收執行緒就是一個後台執行緒。
13  * 需求:嘗試把執行緒標記為後台執行緒或者標記為(前台)執行緒;
14  * Thread類提供的方法:
15  * 方法1: void setDaemon(boolean on) 將該執行緒標記為守護執行緒或用戶執行緒,true為後台執行緒,false為用戶執行緒(前台線下)
16  * 怎樣測試該執行緒是否是守護執行緒?
17  * 方法2:isDaemon()  測試該執行緒是否為守護執行緒. true為後台執行緒,false為用戶執行緒(前台線下)
18  * <p>
19  * 結論1:活動的執行緒(已經在執行的執行緒t.start())不能設置後台執行緒,即主執行緒不能設置為後台執行緒。
20  * 結論2: 自定義執行緒的默認狀態和環境有關,後台執行緒中創建的執行緒默認是後台執行緒,前台執行緒中創建的執行緒為前台執行緒.
21  * 結論3: 前台執行緒執行完後,會直接關閉後台執行緒,即自定義的後台執行緒不一定能執行完成
22  * </p>
23  */
24 public class Test07Daemon {
25     /**
26      * 測試1
27      * 查看主執行緒的狀態,嘗試更改
28      * 結論:活動的執行緒不能設置為後台執行緒
29      *
30      * @param args
31      */
32     public static void main1(String[] args) {
33         Thread threadMain = Thread.currentThread();
34         System.out.println("是後台執行緒么:" + threadMain.isDaemon());// false
35         threadMain.setDaemon(true); // 報錯,活動的執行緒不能設置為後台執行緒
36         System.out.println("修改後是後台執行緒么:" + threadMain.isDaemon());
37     }
38 
39     /**
40      * 測試2
41      * 查看主執行緒中 創建執行緒的狀態,嘗試更改;
42      *
43      * @param args
44      */
45     public static void main2(String[] args) {
46         Thread thread = new Thread();
47         // false
48         System.out.println("是後台執行緒么:" + thread.isDaemon());
49         // 修改為後台執行緒
50         thread.setDaemon(true);
51         System.out.println("修改後是後台執行緒么:" + thread.isDaemon());
52     }
53 
54     /**
55      * 測試3
56      * 查看主執行緒中 創建執行緒的狀態,嘗試更改,讓執行緒處於活動狀態在修改->報錯;
57      *
58      * @param args
59      */
60     public static void main3(String[] args) {
61         DaemonThread thread = new DaemonThread();
62         // 讓執行緒處於活躍狀態
63         thread.start();
64         // false
65         System.out.println("是後台執行緒么:" + thread.isDaemon());
66         // 修改為後台執行緒,報錯,當前已經是活躍狀態(thread.start())不能修改為後台執行緒
67         thread.setDaemon(true);
68         System.out.println("修改後是後台執行緒么:" + thread.isDaemon());
69     }
70 
71     /**
72      * 測試4
73      * 前台執行緒執行完後,會直接關閉後台執行緒,即如果後台執行緒不一定能執行完成
74      * 可以通過修改等待執行時間來觀察DaemonThread執行緒的數組輸出變化
75      *
76      * @param args
77      */
78     public static void main(String[] args) throws InterruptedException {
79         DaemonThread thread = new DaemonThread();
80         // 修改為後台執行緒
81         thread.setDaemon(true);
82         // 讓執行緒處於活躍狀態
83         thread.start();
84         System.out.println("========等待後台執行緒執行============");
85         Thread.sleep(5 * 1000);
86     }
87 }

 1 package com.wfd360.thread.demo04Daemon;
 2 
 3 /**
 4  * @author 姿勢帝-部落格園
 5  * @address //www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 9:27
 8  * @description
 9  */
10 public class DaemonThread extends Thread {
11     @Override
12     public void run() {
13         for (int i = 0; i < 10; i++) {
14             System.out.println("===="+i);
15             try {
16                 Thread.sleep(1000);
17             } catch (InterruptedException e) {
18                 e.printStackTrace();
19             }
20         }
21     }
22 }

DaemonThread

執行緒基礎相關的方法定義就先到這裡,下一篇我們將進入執行緒同步.

//www.cnblogs.com/newAndHui/p/12831089.html

系統化的在線學習:點擊進入學習