文章目錄
15、多線程總結
最近很認真的學習完了多線程,現在總結一波。加強自己的理解!(因爲可能有錯可以被指出嘛😱)
15.1 什麼是多線程?
15.1.1 第一步:瞭解進程與線程的區別
我們先來看張圖,按照①②③去讀一下這個流程,我們就能在腦海裏形成一個模糊概念。
下面開始進行文字介紹:
進程是啥呢?
-
進程是指在系統中正在運行的一個應用程序
-
每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內
比如同時打開迅雷、Xcode,系統就會分別啓動2個進程。
什麼是線程呢?
- 就是進程裏邊一個負責程序執行的控制單元(就是某個功能啊,某個任務啊,也可以理解爲執行路徑)
- 進程的任務都由線程來控制,執行;
- 一個進程沒有線程也就是沒有任務、沒有執行路徑,所以一個進程至少擁有一個線程!
例子:
線程的串行
- 1個線程中任務的執行是串行的。順序進行
假設一個線程任務是煲水煮飯,那它就必須先煲水,煲完水,才能去煮飯
這就產生了一個效率問題了,我們現實中肯定是煲水和煮飯一起幹的啦!上圖中,文件也是可以同時開始下載的。這就有了多線程。
15.1.2 多線程概念
概念
- 1、每條線程擁有自己的運行內容——線程任務
- 2、當一個進程開啓了多條線程,每條線程可以並行(同時)執行,它們線程執行的任務不同,這就是多線程體現
- 3、開啓多個線程就能同時執行多個任務
- 4、多線程技術可以提高程序的執行效率
比如我們實現一個hello的代碼
java虛擬機就會創建很多線程,其中有一個是Main線程,來執行我們的代碼——打印hello world;
其他線程例如GC垃圾回收線程等。當Main線程執行完,我們的GC垃圾回收機制可能還沒執行完。就好像我們360殺毒完畢,清理垃圾還沒行!
並行的實質(多線程的實質)
這也帶來了一個很常見的問題:線程開啓過多會卡
比如我們打開360,把裏面的殺毒,文件清理,垃圾清理,驅動更新全打開
然後又打開酷狗聽歌,下載歌,打開百度網盤下載文件等,開啓了好多的線程,cpu在這些線程裏邊隨機切換,每個線程平均被切換到的概率降低,我們就會覺得卡了,這個時候cpu切換到同一個線程的任務時間變長,自然也就是卡了,會耗費大量cpu資源
這個圖標數字一下飛昇!
這個時候,就出現了傳說中的多核技術——4核、8核等,每個cpu自由調度切換線程。
多線程的好處、弊端
- 好處:解決了多部分任務同時運行
- 弊端:正如上面所示,當線程開啓的多了,就會降低cpu的效率。
15.2 線程的創建(兩種方式)
15.2.1 方式一:繼承Thread類
四步實現多線程
- 創建一個繼承Thread類的子類
- 覆寫run()方法,實現線程任務的封裝
- 創建多個線程對象
- 每個對象都開啓線程——start()方法
代碼演示
package ThreadTest;
/*
目的:練習使用多線程類創建多線程
@Override
public void run() {//Thread的run方法源碼
if (target != null) {
target.run();
}
}
*/
//1、創建繼承Thread的子類
class Animal extends Thread {
private String name;
public Animal(String name) {
super(name);
this.name = name;
}
//2、覆寫run方法
public void run() {
// System.out.println(4/0);,其他線程發生異常不影響主線程
for (int i = 0; i < 10; i++)
System.out.println("大家好,我叫" + name + "...線程名" + Thread.currentThread().getName());
//show();
}
void show() {
for (int i = -9999; i < 9999; i++) {
}
System.out.print("大家好,我叫" + name + "..." + Thread.currentThread().getName());
}
}
public class Practice1 {
public static void main(String[] args) {
//3、創建線程子類對象
Animal test2 = new Animal("旺財");//創建對象時就已經安排好線程名字了
Animal test1 = new Animal("小強");
//4、每個線程對象開啓線程!
test1.start();//線程1
test2.start();//線程2
//System.out.println(3/0);//主線程發生異常不影響其他線程,其他線程發送異常也不影響主線程
for (int i = 0; i < 10; i++)
System.out.println("haha" + i + "...線程名+" + Thread.currentThread().getName());//主線程,如果去掉循環,其實也是隨機的
//三個線程隨機運行,搶奪資源!
}
}
運行結果
思考:直接使用run()和使用start()方法有區別
run方法無法開啓線程,相當於正常的對象調用方法;start()開啓線程運行任務。
線程對象.getName()與Thread.currentThread.getName()
第一是返回的是線程對象默認的一個線程名,第二個返回的是正在運行的線程名,調用的是底層代碼
15.2.2 方式二:實現Runnable接口
Runnable接口是用來封裝線程任務的一個接口,裏面只有一個抽象run方法
,用於封裝線程任務。
步驟
- 創建一個類實現Runnable接口
- 必須重寫run()方法,否則該類要聲明爲抽象類而且無法實現多線程
- 創建該類對象,調用Thread的有參構造方法,參數爲Runnable類及其子類對象
- 開啓線程
代碼演示
package ThreadTest;
public class Practice2 implements Runnable {//1、實現Runnbale接口
public void show() {
for (int i = 0; i < 20; i++)
System.out.println("大家好,我是show!" + i);
}
@Override
public void run() {//2、寫run方法,封裝線程任務
show();
}
public static void main(String[] args) {
//創建Runnable子類對象
Practice2 test = new Practice2();
//調用Thread有參構造方法
Thread thread = new Thread(test);
//開啓線程
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("現在是主線程執行" + i);//線程之間互相不影響,並且隨機切換。
}
thread.stop();//進入線程的凍結時期,隨機凍結
for (int i = 0; i < 20; i++) {
System.out.println("現在是主線程執行" + i);//線程之間互相不影響,並且隨機切換。
}
}
}
運行結果:
15.2.3 兩種方法的區別及其優劣性
15.2.4 多線程是如何執行的(剖析內存中的情況)
15.3 線程的狀態淺析
自己畫了一張圖:
## 15.4 多線程的使用及其方法介紹
###
## 15.5 多線程裏邊的問題
### 15.5.1 多次開啓同一個線程會怎麼樣?
## 15.6 多線程中的線程安全問題(同步的引出)
### 15.6.1 挖煤搬礦例子
### 15.6.2 單例設計模式下的線程安全問題
## 15.7 同步裏邊的線程安全問題