(圖解)進程與線程及創建線程的兩種方式 --繼承 Thread、實現Runnable 接口(附豐富案例)

前言:

在創建線程之前,我們先來侃侃線程是什麼?說到線程,就不得不提進程,那麼進程是什麼呢?
如果你的電腦卡頓過,我相信一定出現過下面的畫面:

在這裏插入圖片描述
可以清晰的看到,左上角寫着進程 二字(可以自己 Ctrl + Alt + dot嘗試一下) 。
我們用的QQ,騰訊視頻,and so on 中對我們的電腦來說都是一個進程
那線程呢?想想一下,我們在 B 站看大佬的視頻的時候是不是會有彈幕和字幕等一些其他的東西,這些就是線程: 進程中包括多個線程,且每個進程中至少要有一個線程,要不這個進程有什麼意義呢?

多線程:

既然線程是一個一個的,那麼多線程就是一起的唄(可以這樣簡單的理解)。多線程就是多條線路各自執行,相互獨立,比如彈幕就是隻管彈幕,字幕只管字幕,兩條線程各走各的。
在這裏插入圖片描述
從這個圖中我們就可以看出:多條線路的出現可以有效防止交通的堵塞,而且提升了效率,同樣的,在我們的程序中也存在着同樣的多線程。

多線程是怎麼運作的呢?

現在看不懂沒關係,先理解單線程和多線程之間的關係。
單線程的運作方式:

package thread;

public class Demo01 extends Thread {

    @Override
    public void run() {
        // run() 方法線程體
        for (int i = 0; i < 100; i++) {
            System.out.println("我在看代碼。。。。。。。。");
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Demo01();

		// 調用 run() 方法
        t1.run();

        for (int i = 0; i < 100; i++) {
            System.out.println("我是main線程。。。。。。。。。。。。。。");
        }

    }
}

單線程結果:

在這裏插入圖片描述
多線程運作方式:

package thread;

public class Demo01 extends Thread {

    @Override
    public void run() {
        // run() 方法線程體
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代碼。。。。。。。。");
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Demo01();
        // 啓動線程後:由 JVM 調用 run() 方法
        t1.start();
        for(int i = 0; i < 2000; i ++) {
            System.out.println("主線程。。。。。。。。。。。。。。");
        }

    }
}

效果圖:
在這裏插入圖片描述
從上面程序運行的結果來看,可以得到下圖(來自狂神PPT):
在這裏插入圖片描述
上圖就是單線程和多線程的本質區別,可以發現,多線程效率大大提高了。
在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如 主線程、gc線程(守護線程,後續會講到)。
main() 稱之爲主線程,爲系統的入口,用於執行整個程序。

方式一:繼承 Thread:

​ a、自定義線程類繼承 Thread 類

​ b、重寫 run() 方法,編寫線程執行體

​ c、編寫線程對象,調用 start() 方法啓動線程(由 JVM 調用 run 方法)。

package thread;

// 線程開啓不一定立即執行,由CPU調度執行

// 繼承 Thread 類
public class Demo01 extends Thread {

    @Override
    public void run() {
        // run() 方法線程體,每個線程需要做的事
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代碼。。。。。。。。");
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Demo01();
        // 啓動線程後:由 JVM 調用 run() 方法
        t1.start();
        for(int i = 0; i < 2000; i ++) {
            System.out.println("主線程。。。。。。。。。。。。。。");
        }

    }
}

案例: 下載圖片

package thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class Demo02 extends Thread {

    private String url;
    private String name;

    public Demo02(){}
	// 構造方法進行初始化,賦初值,每個線程都會調用
    public Demo02(String url,String name) {
        this.name = name;
        this.url = url;
    }

    @Override
    public void run() {
        // 通過下載器進行下載圖片
        Download picture = new Download();
        picture.downLoad(url,name);
        System.out.println("下載的文件名是: " + name);
    }

    public static void main(String[] args) {

        // 多線程同步下載
        Demo02 t1 = new Demo02("http://pic1.sc.chinaz.com/files/pic/pic9/202005/apic25353.jpg","圖片1");
        Demo02 t2 = new Demo02("http://pic1.sc.chinaz.com/files/pic/pic9/202005/apic25353.jpg","圖片2");
        Demo02 t3 = new Demo02("http://pic1.sc.chinaz.com/files/pic/pic9/202005/apic25353.jpg","圖片3");

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

    }

}

// 下載器
class Download{
        public  void downLoad(String url,String name) {
            try {
                // 需要先導入 commons-io.jar
                FileUtils.copyURLToFile(new URL(url), new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO異常。。。。。。。。。。");
            }
        }
}

效果圖:
在這裏插入圖片描述
通過上圖,我們可以發現,三個線程的運行順序是隨機的,看CPU心情,得到的結果也是隨機的,想一下,你原來一個線程下載三個圖片,和現在 三個線程分別下載各自的圖片,哪個更快呢?

方式二: 實現 Runnable 接口:

這種方式底層是用 靜態代理模式 實現的,不瞭解的小夥伴可以重點學習一下,進行對比。

a、實現 Runnable 接口。
b、重寫 run() 方法。
c、利用靜態代理模式進行調用、啓動。

package thread;

// 用 Runnable 接口實現多線程
public class Demo03 implements Runnable{
    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            System.out.println("我是線程小弟666666666666666666666666。。。。。。。。。。。。");
        }

    }

    public static void main(String[] args) {
        Demo03 t = new Demo03(); // 通過接口實現的類,類又產生對象
        new Thread(t).start();   // 靜態代理模式 
        for (int i = 0; i < 2000; i++) {
            System.out.println("我是主線程老大。。。。。。。。");
        }
    }

}



案例: 龜兔賽跑

package thread;

public class Demo05 implements Runnable {


    private static String winner = null;

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            boolean flag = false;
            System.out.println(Thread.currentThread().getName() + "跑到了第" + i + "步。。。。。。。。。。");
            if (winner != null) {
                System.out.println(winner + "獲得了勝利!!!");
                break;
            }
            // 讓小白兔睡一覺
            if (Thread.currentThread().getName().equals("小白兔")) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           getVictory(i);

        }
    }

    public boolean getVictory(int num) {
        if (num == 100) {
            winner = Thread.currentThread().getName();
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Runnable race = new Demo05();

        // 兩個線程同步進行
        new Thread(race, "小白兔").start();
        new Thread(race, "乖烏龜").start();
    }

}

Thread and Runnable:

繼承 Thread 類:

​	子類繼承Thread 類具備多線程能力。

​	啓動線程: 子類對象.start()

​	不建議使用: 避免 OOP 單繼承的侷限性

實現Runnable 接口:

​	實現接口 Runnable 具有多線程能力。

​	啓動線程: 傳入目標對象 + Thread對象.start() 

​	推薦使用: 避免單繼承侷限性,靈活方便,**方便同一個對象被多個線程使用。**

​	(100 張票 3 個窗口一起賣)

後記:

到此,關於多線程的基本創建就介紹完了,還有一種 實現 Collable 接口的方式也可以實現多線程,目前本人能力有限,還未學習,聽大佬說也不太常用,一般 Runnable 接口比較常用,大家比較提倡面向接口開發。
如果本文對您有幫助,別忘了點個贊哦。嘻嘻,感覺不錯的可以點個關注,後續會持續更新文章,如果有寫的不到位的地方或者有疑問的地方,歡迎指出來,大家共同進步。

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