06- 線程基礎

多線程基礎

線程進程

進程

每一個正在運行的程序 

通過windows 任務管理器 查看正在運行的程序

  • 特點
1- 進程是系統運行程序的基本單位
2- 每一個進程都有自己獨立的一塊內存空間,一組系統資源
3- 每一個進程內部的數據和狀態都是完全獨立的

線程

概念

線程是進程執行運算的最小單位, 一個進程在其執行過程中可以產生多個線程,線程必須在某個進程中執行.
線程是進程內部的一個執行單元, 是可完成一個獨立任務的順序控制流程,如果在一個進程中同時運行了多個線程,用來完成不同工作, 稱之爲多線程.

多線程的優勢

迅雷多線程下載

  • 最大限度的提高計算機系統的利用效率
  • 可以帶來更好的用戶體驗

線程和進程對比

1- 一個進程至少包含一個線程
2- 系統資源分配給進程, 同一進程中的所有線程共享該進程的所有資源
3- cpu 分配給線程執行機會,而不是分配給進程, 真正運行在cpu上的是線程

CPU 採用時間片段

一個CPU 同一時間只能做一件事

時間片段 : 每隔一段時間去執行一個程序, 時間片段使用完畢之後,再切換到另外一個程序, 因爲這個切換速度很快, 用戶感覺不到CPU 正在切換執行程序.

就好比說播放視頻
視頻 幀率 (本質就是一張一張的圖片)  一秒內切換20 張圖片.
連貫的執行圖片切換,讓用戶感覺是在看視頻.
用戶感受不到時間粒度太細的操作

CPU 隨機選擇 一個線程去執行, 感覺上是多個線程是在爭搶CPU 的執行權

線程的使用

在學習本章節之前, 所有的代碼程序都是單線程的

無論代碼有多複雜, 都是按照順序依次執行的

main 函數線程

書寫代碼, 拋出異常

Exception in thread "main" java.lang.NullPointerException
  • 主線程入口
  • 開啓其它子線程
  • 最後執行各種關閉資源的操作

Thread

第一種創建線程的方法

1- 將一個類聲明爲Thread的子類。 
2- 這個子類應該重寫Thread類的run方法 。 
3- 然後可以分配並啓動子類的實例。

創建線程的步驟

1- 書寫子類繼承Thread 類
2- 重寫 run 方法
3- 創建自定義的線程子類對象
4- 調用start() 方法

案例測試

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++){
            System.out.println("Mythread--"+i);
        }
    }
}

public class Demo2_Thread {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();

        for(int i = 0 ; i < 100 ; i++){
            System.out.println("main--"+i);
        }
    }
}

start()

1- 啓動一條線程

2- 執行該線程的run()方法

run() 和 start() 區別

run 僅僅是 線程類中的一個普通方法

如果使用線程對象調用run方法, 僅僅是在main 線程中 執行run() 方法中的代碼

start() 纔可以真正的啓動線程

start() 可以自動去執行run() 方法

細節

多個線程執行, 不是真正的並行

線程每次執行的時長是由CPU 隨機分配的時間片決定的

Thread常用方法

優先級

優先級高 有更高的機率可以獲取CPU的更多時間片段, 不能保證一定優先執行

類似於擲骰子 1-4 張三 贏 56 是 李四贏, 真正玩遊戲, 不能保證是張三

int getPriority() 
返回此線程的優先級。 
setPriority(int newPriority) 
更改此線程的優先級。

設置線程名稱

1- 有參構造 Thread(String name) 
分配一個新的 Thread對象。 
2- void setName(String name) 
將此線程的名稱更改爲等於參數 name 。
String getName() 
返回此線程的名稱。  

案例

public class MyThread extends Thread{
    public MyThread() {
    }
    public MyThread(String name) {
     	// 子類如何調用執行 父類的構造方法
        super(name);
    }
}
new MyThread("線程1");

獲取當前正在執行的線程

static Thread currentThread() 
返回對當前正在執行的線程對象的引用。  

獲取線程狀態

Thread.State getState() 
返回此線程的狀態。  

判斷線程是否處於活躍狀態

boolean isAlive() 
測試這個線程是否活着。  

線程休眠

static void sleep(long millis) 
使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行),具體取決於系統定時器和調度程序的精度和準確性。  
public void run() {
    for(int i = 0 ; i < 100 ; i++){
        // 獲取當前線程名稱
        String sname = Thread.currentThread().getName();
        if(i == 61){
            // run 方法 是 子類重寫的父類的方法, 子類拋出的異常不能超過父類的異常
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(sname+"--"+i);
    }

線程加入

void join() 
等待這個線程死亡。  
ThreadTest tt1 = new ThreadTest();
ThreadTest tt2 = new ThreadTest();
ThreadTest tt3 = new ThreadTest();
tt1.setName("西施");
tt2.setName("貂蟬");
tt3.setName("王昭君");
tt1.start();
/**join的意思是使得放棄當前線程的執行,並返回對應的線程,例如下面代碼的意思就是:
 程序在main線程中調用tt1線程的join方法,則main線程放棄cpu控制權,並返回tt1線程繼續執行直到線程tt1執行完畢
 所以結果是tt1線程執行完後,纔到主線程執行,相當於在main線程中同步tt1線程,tt1執行完了,main線程纔有執行的機會
 */
tt1.join();
tt2.start();
// 我要插隊, 等我運行結束之後, 之後的代碼纔可以開始執行
tt3.start();

線程禮讓

static void yield() 
對調度程序的一個暗示,即當前線程放棄當前使用的處理器。 
public void run() {
    for(int i = 0 ; i < 100 ; i++){
        // 獲取當前線程名稱
        String sname = Thread.currentThread().getName();
        System.out.println(sname+"--"+i);
        // 當線程運行到該位置 , 放棄當前cpu的執行權
        // 但是CPU 依然有可能還選擇你 不能保證均勻
        Thread.yield();
    }
}

線程終止

void interrupt() 
中斷這個線程。 
// 詳解
interrupt() 它基於「一個線程不應該由其他線程來強制中斷或停止,而是應該由線程自己自行停止。」思想,是一個比較溫柔的做法,它更類似一個標誌位。其實作用不是中斷線程,而是「通知線程應該中斷了」,具體到底中斷還是繼續運行,應該由被通知的線程自己處理。

interrupt() 並不能真正的中斷線程,這點要謹記。需要被調用的線程自己進行配合才行。也就是說,一個線程如果有被中斷的需求,那麼就需要這樣做:

在正常運行任務時,經常檢查本線程的中斷標誌位,如果被設置了中斷標誌就自行停止線程。
在調用阻塞方法時正確處理InterruptedException異常。(例如:catch異常後就結束線程。)
1- interrupt 中斷操作時,非自身打斷需要先檢測是否有中斷權限,這由jvm的安全機制配置;
如果線程處於sleep, wait, join 等狀態,那麼線程將立即退出被阻塞狀態,並拋出一個InterruptedException異常;
2- 如果線程處於I/O阻塞狀態,將會拋出ClosedByInterruptException(IOException的子類)異常;
3- 如果線程在Selector上被阻塞,select方法將立即返回;
4- 如果非以上情況,將直接標記 interrupt 狀態;
5- 注意:interrupt 操作不會打斷所有阻塞,只有上述阻塞情況纔在jvm的打斷範圍內,如處於鎖阻塞的線程,不會受 interrupt 中斷;

守護線程

public final void setDaemon(boolean on)
將此線程標記爲daemon線程或用戶線程。
當運行的唯一線程都是守護進程線程時,Java虛擬機將退出。 
線程啓動前必須調用此方法。 
參數 如果 true ,將此線程標記爲守護線程 
public static void main(String[] args) {
    MyThread t1 = new MyThread("皇帝");
    MyThread t2 = new MyThread("總管1");
    MyThread t3 = new MyThread("總管2");
    // 線程啓動之前設置 守護線程
    t2.setDaemon(true);
    t3.setDaemon(true);
    // 只要主線程運行完畢, 守護線程 不是立即停止, 等待虛擬機退出
    t1.start();
    t2.start();
    t3.start();
}

Runnable 接口 創建線程

第二種創建線程的方法

查看API Thread 的構造方法
Thread(Runnable target) 
分配一個新的 Thread對象。  
Thread(Runnable target, String name) 
分配一個新的 Thread對象。  
// 如何啓動一個 Runnable 接口的線程
MyRunnable mr = new MyRunnable();
// 創建線程離不開 Thread
Thread t1 = new Thread(mr,"線程名稱1");
Thread t2 = new Thread(mr,"線程名稱2");
t1.start();
t2.start();

使用類創建線程 使用接口創建線程的區別

回憶 類和接口的區別
1- 類只能單繼承  接口可以多實現
2- 類是什麼是什麼的概念  接口 什麼有什麼功能

自己實現一個線程 如果採用繼承的方式 , 只能單繼承, 不能夠再繼承其他類

多線程也可以看做是一種功能, 可以實現 業務和邏輯的分離

推薦使用接口來創建線程

線程狀態

線程的生命週期

// 如何啓動一個 runnable 接口的線程
MyRunnable mr = new MyRunnable();
// 創建線程離不開 Thread
Thread t1 = new Thread(mr,"線程名稱1");
Thread t2 = new Thread(mr,"線程名稱2");
t1.start();
t2.start();

線程狀態圖例

在這裏插入圖片描述

在這裏插入圖片描述

線程的5種狀態

1- 新建  new 對象
2- 就緒 可運行狀態 (對象調用了 start() 方法之後)
3- 運行狀態 (CPU 選擇了 線程)
4- 死亡 run方法運行完畢
5- 阻塞 由運行狀態==> 阻塞狀態
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章