系統化學習多線程(一)

大綱

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

學習不能快速成功,但一定可以快速入門
整體課程思路:
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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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 https://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

線程基礎相關的方法定義就先到這裏,下一篇我們將進入線程同步.

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

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

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!