Java學習路線:day18 多線程

全部源碼:https://github.com/name365/JavaSE-30Day

轉載自atguigu.com視頻

第8章 多線程

基本概念:程序、進程、線程

  • 程序(program)是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。
  • 進程(process)是程序的一次執行過程,或是正在運行的一個程序。是一個動態的過程:有它自身的產生、存在和消亡的過程。——生命週期
    • 如:運行中的QQ,運行中的MP3播放器程序是靜態的,進程是動態的
    • 進程作爲資源分配的單位,系統在運行時會爲每個進程分配不同的內存區域
  • 線程(thread),進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
    • 若一個進程同一時間並行執行多個線程,就是支持多線程的
    • 線程作爲調度和執行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
    • 一個進程中的多個線程共享相同的內存單元/內存地址空間—》它們從同一堆中分配對象,可以訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統資源可能就會帶來安全的隱患。

在這裏插入圖片描述

進程與線程

在這裏插入圖片描述

  • 單核CPU和多核CPU的理解
    • 單核CPU,其實是一種假的多線程,因爲在一個時間單元內,也只能執行一個線程的任務。例如:雖然有多車道,但是收費站只有一個工作人員在收費,只有收了費才能通過,那麼CPU就好比收費人員。如果有某個人不想交錢,那麼收費人員可以把他“掛起”(晾着他,等他想通了,準備好了錢,再去收費)。但是因爲CPU時間單元特別短,因此感覺不出來。
    • 如果是多核的話,才能更好的發揮多線程的效率。(現在的服務器都是多核的)
    • 一個Java應用程序java.exe,其實至少有三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當然如果發生異常,會影響主線程。
  • 並行與併發
    • 並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事。
    • 併發:一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。

使用多線程的優點

背景:以單核CPU爲例,只使用單個線程先後完成多個任務(調用多個方法),肯定比用多個線程來完成用的時間更短,爲何仍需多線程呢?

  • 多線程程序的優點:
    1. 提高應用程序的響應。對圖形化界面更有意義,可增強用戶體驗。
    2. 提高計算機系統CPU的利用率
    3. 改善程序結構。將既長又複雜的進程分爲多個線程,獨立運行,利於理解和修改

何時需要多線程

  • 程序需要同時執行兩個或多個任務。
  • 程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
  • 需要一些後臺運行的程序時。

線程的創建和使用

線程的創建和啓動

  • Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread類來體現。
  • Thread類的特性
    • 每個線程都是通過某個特定Thread對象的run()方法來完成操作的,經常把run()方法的主體稱爲線程體
    • 通過該Thread對象的start()方法來啓動這個線程,而非直接調用run()

Thread類

  • Thread():創建新的Thread對象
  • Thread(String threadname):創建線程並指定線程實例名
  • Thread(Runnabletarget):指定創建線程的目標對象,它實現了Runnable接口中的run方法
  • Thread(Runnable target, String name):創建新的Thread對象

API中創建線程的兩種方式

  • JDK1.5之前創建新執行線程有兩種方法:
    • 繼承Thread類的方式
    • 實現Runnable接口的方式

創建多線程的方式一:繼承Thread類

/**
 * 多線程的創建,方式一:繼承於Thread類
 * 1.創建一個繼承於Thread類的子類
 * 2.重寫Thread的run()方法 ---> 將此線程的方法聲明在run()中
 * 3.創建Thread類的子對象
 * 4.通過此對象調用start()
 *
 * 例子:遍歷100以內的所有的偶數
 *
 * @author subei
 * @create 2020-05-07 15:28
 */

//1.創建一個繼承於Thread類的子類
class MyThread extends Thread{
    //重寫Thread類的run()
    @Override
    public void run() {
        for(int i = 1;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.創建Thread類的子對象
        MyThread t1 = new MyThread();

        //4.通過此對象調用start():①啓動當前線程 ②調用當前線程的run()
        t1.start();

        //如下操作仍在main線程中執行的
        for(int i = 1;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(i + "***main()***");
            }
        }
    }
}
  • mt子線程的創建和啓動過程

在這裏插入圖片描述
在這裏插入圖片描述

創建過程中的兩個問題說明

//1.創建一個繼承於Thread類的子類
class MyThread extends Thread{
    //重寫Thread類的run()
    @Override
    public void run() {
        for(int i = 1;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.創建Thread類的子對象
        MyThread t1 = new MyThread();

        //4.通過此對象調用start():①啓動當前線程 ②調用當前線程的run()
        t1.start();
        //問題1:我們不能通過直接調用run()的方式啓動線程。
//        t1.run();

        //問題二:再啓動一個線程,遍歷100以內的偶數。不可以還讓已經start()的線程去執行。會報IllegalThreadStateException
//        t1.start();
        //我們需要重現創建一個線程的對象,去start().
        MyThread t2 = new MyThread();
        t2.start();

        //如下操作仍在main線程中執行的
        for(int i = 1;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i + "***main()***");
            }
        }
    }
}

練習1

  • 寫法一
/**
 * 練習:創建兩個分線程,其中一個遍歷100以內的偶數,另一個遍歷100以內的奇數
 *
 *
 * @author subei
 * @create 2020-05-07 16:34
 */
public class ThreadDemo {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        m1.start();

        MyThread2 m2 = new MyThread2();
        m2.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
  • 寫法二
/**
 * 練習:創建兩個分線程,其中一個遍歷100以內的偶數,另一個遍歷100以內的奇數
 *
 *
 * @author subei
 * @create 2020-05-07 16:34
 */
public class ThreadDemo {
    public static void main(String[] args) {

        //創建Thread類的匿名子類的方式
        new Thread(){
            @Override
            public void run() {
                for(int i = 0;i < 100;i++){
                    if(i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for(int i = 0;i < 100;i++){
                    if(i % 2 != 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
    }
}

Thread類的有關方法

/**
 * 測試Thread類的常用方法
 * 1.start():啓動當前線程,執行當前線程的run()
 * 2.run():通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中
 * 3.currentThread(): 靜態方法,返回當前代碼執行的線程
 * 4.getName():獲取當前線程的名字
 * 5.setName():設置當前線程的名字
 * 6.yield():釋放當前CPU的執行權
 * 7.join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以後,線程a才
 *          結束阻塞狀態。
 * 8.stop():已過時。當執行此方法時,強制結束當前線程。
 * 9.sleep(long millitime):讓當前線程“睡眠”指定時間的millitime毫秒)。在指定的millitime毫秒時間內,
 *                          當前線程是阻塞狀態的。
 * 10.isAlive():返回boolean,判斷線程是否還活着
 *
 * @author subei
 * @create 2020-05-07 16:51
 */

class HelloThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 100; i++){

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

            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
//            if(i % 20 == 0){
//                yield();
//            }
        }
    }

    public HelloThread(String name){
        super(name);
    }
}

public class ThreadModeTest {
    public static void main(String[] args) {
        HelloThread h1 = new HelloThread("Thread : 1");

//        h1.setName("線程一");

        h1.start();

        //給主線程命名
        Thread.currentThread().setName("主線程");

        for(int i = 0;i < 100; i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

            if(i == 20){
                try {
                    h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println(h1.isAlive());
    }
}

線程的調度

  • 調度策略

    • 時間片
      在這裏插入圖片描述

    • 搶佔式:高優先級的線程搶佔CPU

  • Java的調度方法

    • 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
    • 對高優先級,使用優先調度的搶佔式策略

線程的優先級

/**
 * - 線程的優先級等級
 *   - MAX_PRIORITY:10
 *   - MIN _PRIORITY:1
 *   - NORM_PRIORITY:5 --->默認優先級
 * - 涉及的方法
 *   - getPriority() :返回線程優先值
 *   - setPriority(intnewPriority) :改變線程的優先級
 *
 *   說明:高優先級的線程要搶佔低優先級線程cpu的執行權。
 *       但是隻是從概率上講,高優先級的線程高概率的情況下被執行。
 *       並不意味着只有當高優先級的線程執行完以後,低優先級的線程纔會被執行。
 *
 *
 * @author subei
 * @create 2020-05-07 17:47
 */

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {

//            try {
//                sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }

            if (j % 2 == 0) {
                System.out.println(getName() + ":" + getPriority() + ":" + j);
            }
        }
    }
    public HelloThread(String name){
        super(name);
    }
}

public class ThreadModeTest {
    public static void main(String[] args) {
        HelloThread h2 = new HelloThread("Thread : 1");
        h2.start();

        //設置分線程的優先級
        h2.setPriority(Thread.MAX_PRIORITY);

        //給主線程命名
        Thread.currentThread().setName("主線程");
        Thread.currentThread().setPriority((Thread.MIN_PRIORITY));

        for(int j = 0;j < 100; j++){
            if(j % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + j);
            }

//            if(j == 20){
//                try {
//                    h2.join();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
        }

        System.out.println(h2.isAlive());
    }
}

練習2

  • 多窗口賣票
/**
 * 例子:創建三個c窗口賣票,總票數爲100張
 *
 * 存在線程的安全問題,待解決。
 *
 * @author subei
 * @create 2020-05-07 18:21
 */

class Windows extends Thread{

    private static int ticket = 100;

    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                System.out.println(getName() + ":賣票,票號爲: " + ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}

public class WindowsTest {
    public static void main(String[] args) {
        Windows t1 = new Windows();
        Windows t2 = new Windows();
        Windows t3 = new Windows();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

練習3

/**
 * 例子:創建三個c窗口賣票,總票數爲100張
 *
 * 存在線程的安全問題,待解決。
 *
 * @author subei
 * @create 2020-05-07 18:21
 */

class Windows extends Thread{

    private static int ticket = 100;

    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                System.out.println(getName() + ":賣票,票號爲: " + ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}

public class WindowsTest {
    public static void main(String[] args) {
        Windows t1 = new Windows();
        Windows t2 = new Windows();
        Windows t3 = new Windows();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

創建多線程的方式二:實現Runnable接口

/**
 * 創建多線程的方式二:實現Runnable接口
 * 1.創建一個實現了Runnable接口得類
 * 2.實現類去實現Runnable中的抽象方法:run()
 * 3.創建實現類的對象
 * 4.將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
 * 5.通過Thread類的對象調用start()
 *
 *
 * @author subei
 * @create 2020-05-07 18:39
 */
//1.創建一個實現了Runnable接口得類
class MThread implements Runnable{

    //2.實現類去實現Runnable中的抽象方法:run()
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        //3.創建實現類的對象
        MThread m1 = new MThread();
        //4.將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
        Thread t1 = new Thread(m1);
        //5.通過Thread類的對象調用start():①啓動線程 ②調用當前線程的run() --> 調用了Runnable類型的target的run()
        t1.start();

        //再啓動一個線程,遍歷100以內的偶數
        Thread t2 = new Thread(m1);
        t2.setName("線程2");
        t2.start();
    }
}

繼承方式和實現方式的聯繫與區別

/**
 *  比較創建線程的兩種方式。
 *  開發中:優先選擇:實現Runnable接口的方式
 *  原因:1. 實現的方式沒有類的單繼承性的侷限性
 *       2. 實現的方式更適合來處理多個線程有共享數據的情況。
 *  
 *  聯繫:public class Thread implements Runnable
 *  相同點:兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中。
 *
 * @author subei
 * @create 2020-05-07 18:39
 */

補充:線程的分類

  • Java中的線程分爲兩類:一種是守護線程,一種是用戶線程。

    它們在幾乎每個方面都是相同的,唯一的區別是判斷JVM何時離開。

    守護線程是用來服務用戶線程的,通過在start()方法前調用**thread.setDaemon(true)**可以把一個用戶線程變成一個守護線程。

    Java垃圾回收就是一個典型的守護線程。

    若JVM中都是守護線程,當前JVM將退出。

    形象理解:兔死狗烹,鳥盡弓藏

    整個Java全棧系列都是筆者自己敲的筆記。寫作不易,如果可以,點個讚唄!✌

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章