前言:
在創建線程之前,我們先來侃侃線程是什麼?說到線程,就不得不提進程,那麼進程是什麼呢?
如果你的電腦卡頓過,我相信一定出現過下面的畫面:
可以清晰的看到,左上角寫着進程 二字(可以自己 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 接口比較常用,大家比較提倡面向接口開發。
如果本文對您有幫助,別忘了點個贊哦。嘻嘻,感覺不錯的可以點個關注,後續會持續更新文章,如果有寫的不到位的地方或者有疑問的地方,歡迎指出來,大家共同進步。