1. 程式人生 > 實用技巧 >面試官:小夥子你連多執行緒輸出數列都不會,還敢說自己會多執行緒?

面試官:小夥子你連多執行緒輸出數列都不會,還敢說自己會多執行緒?

一、前言

計算機的作業系統大多采用任務和分時設計,多工是指在一個作業系統中可以同時執行多個程式,例如,可以在使用qq聊天的同時聽音樂,即有多個獨立執行的任務,每個任務對應一個程序,每個程序又可以產生多個執行緒。

1.程序

程序是程式的一次動態執行過程,它對應了從程式碼載入、執行至執行完畢的一個完整過程,這個過程也是程序本身從產生、發展至消亡的過程。作業系統同時管理一個計算機系統中的多個程序,讓計算機系統中的多個程序輪流使用CPU資源。
程序的特點:

  1. 程序是系統執行程式的基本單位
  2. 每一個程序都有自己獨立的一塊記憶體空間、一組系統資源
  3. 每一個程序的內部資料和狀態都是完全獨立的

2.執行緒

執行緒是程序中執行運算的最小單位,一個程序在其執行過程中可以產生多個執行緒而執行緒必須在某個程序內執行。
執行緒是程序內部的一個執行單元,是可完成一個獨立任務的順序控制流程,如果在一個程序中同時運行了多個執行緒,用來完成不同的工作,則稱之為多執行緒。
執行緒按處理級別可以分為核心級執行緒和使用者及執行緒

1.核心機執行緒
核心級執行緒是和系統任務相關的額執行緒,他負責處理不同程序之間的多個執行緒。允許不同程序中的執行緒按照同一相對優先排程方法對執行緒進行排程,使他們有條不紊的工作,可以發揮多處理器的併發優勢,以充分利用計算機的軟/硬體資源

2.使用者級執行緒
在開發程式時,由於程式的需要而編寫的執行緒即使用者級執行緒,這些執行緒的建立,執行和消亡都是在編寫應用時進行控制的。對於使用者級執行緒的切換,通常發生在一個應用程式的諸多執行緒之間,如迅雷中的多執行緒下載就屬於使用者執行緒

3.執行緒和程序的聯絡以及區別
1.一個程序中至少有一個執行緒
2.資源分配給程序,同一個程序的所有執行緒共享該程序的所有資源
3.處理機分配給執行緒,即真正在處理機上執行的是執行緒

4.多執行緒的優勢
1.多執行緒程式可以帶來更好的使用者體驗,避免因程式執行過慢而導致出現計算機宕機或者白屏的情況。
2.多執行緒可以最大限度地提高計算機系統的利用效率,如迅雷的多執行緒下載。

二、編寫執行緒類

每個程式至少自動擁有一個執行緒,稱為主執行緒。當程式載入到記憶體時啟動主執行緒。java程式中的main方法時主執行緒的入口,執行java程式時,會先執行這個方法。開發中,使用者編寫的執行緒一般都是指除了主執行緒之外的其他執行緒
使用一個執行緒的過程一般有以下4個步驟

  1. 定義一個執行緒,同時指明這個執行緒所要執行的程式碼,即期望完成的功能
  2. 建立執行緒物件
  3. 啟動執行緒
  4. 終止執行緒

定義一個執行緒通常由兩種方法,分別是繼承java.lang.Thread類和java.lang.Runnable介面

1.使用Thread類建立執行緒

Thread類的常用方法:

方法 說明
void run() 執行任務操作的方法
void start() 使該執行緒開始執行
void sleep(long mils) 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)
String getName() 返回該執行緒的名稱
int getPririt() 返回執行緒的優先順序
void setPriority(int newPoriority) 更改執行緒的優先順序
Thread.State getState() 返回該執行緒的狀態
boolean is Alive() 測試線成是否處於活動狀態
void join() 等待該執行緒終止
void interrupt() 中斷執行緒
void yield() 暫停當前正在執行的執行緒物件,並執行其他執行緒

建立執行緒時繼承Thread類並重寫Thread類run()方法。其中run方法時執行緒要執行操作任務的方法,所以執行緒要執行的操作程式碼都要寫在run方法中,並通過呼叫start方法來啟動執行緒。
示例:使用繼承Thread類的方式來建立執行緒,線上程中輸入1-100的整數
實現步驟:
1.定義一個類來繼承Thread類,重寫run方法,在run方法中實現資料輸出
2.建立執行緒物件
3.呼叫start方法啟動執行緒

public class MyThread extends Thread{
    //示例:使用繼承Thread類的方式來建立執行緒,線上程中輸入1-100的整數
    private int count = 0;
    //重寫run方法
    public void run(){
        for (int i = 1; i <=100 ; i++) {
            System.out.println(i);
        }
    }
}

啟動執行緒

package xc.test1;

public class Test {
    public static void main(String[] args) {
        //例項化執行緒物件
        MyThread mt = new MyThread();
        //啟動執行緒
        mt.start();
    }
}

2.使用Runnable介面建立執行緒

雖然Thread類的方式建立執行緒簡單明瞭符合大家的習慣,但他也有一個缺點,如果定義的類已經繼承了其他類則無法再繼承Thread類。使用Runnable介面建立執行緒的方式可以解決上述問題。
Runnable介面中聲明瞭一個run方法(),即public void run()。一個類可以通過實現Runnable介面並實現其run()方法完成執行緒的所有活動,已實現的run方法稱為該物件的執行緒體。任何實現runable介面的物件都可以作為一個執行緒的目標物件。
示例:使用繼承Thread類的方式來建立執行緒,線上程中輸入1-100的整數
實現步驟:
1.定義了MyThread類實現Runable介面,並實現Runnable介面的run方法,在run方法中輸出資料
2.建立執行緒物件
3.呼叫start方法啟動執行緒

public class MyThread implements Runnable{
    //示例:使用繼承Thread類的方式來建立執行緒,線上程中輸入1-100的整數
    private int count = 0;
    //重寫run方法
    public void run(){
        for (int i = 1; i <=100 ; i++) {
            System.out.println(i);
        }
    }
}

啟動start

package xc.test1;

public class Test {
    public static void main(String[] args) {
        //例項化執行緒物件
       Thread thread = new Thread(new MyThread());
        //啟動執行緒
        thread.start();
    }
}

三、執行緒的狀態

執行緒生命週期4階段:新生狀態,可執行狀態,阻塞狀態,死亡狀態

1.新生狀態

執行緒在尚未呼叫start方法之前就有了生命,執行緒僅僅是一個空物件,系統沒有為其分配資源,此時只能啟動和終止執行緒,任何其他操作都會發生異常。

2.可執行狀態

當呼叫start方法後啟動執行緒後,系統為該執行緒分配出CPU外的所需資源,這時執行緒就處於可執行的狀態。
當然在這個狀態中,執行緒也可能未執行。對於只有一個CPU的機器而言,任何時刻只能有一個處於可執行狀態的執行緒佔用處理機

3.阻塞狀態

一個正在執行的執行緒因某種原因不能繼續執行時,進入阻塞狀態,阻塞狀態是不可執行的狀態。
導致阻塞狀態的原因

  1. 呼叫了Thread的靜態方法sleep()
  2. 一個執行緒執行到一個I/O操作時,I/O操作尚未完成
  3. 如果一個執行緒的執行需要得到一個物件鎖,而這個物件的鎖正在被別的執行緒用,那麼會導致阻塞
  4. 執行緒的suspend()方法被呼叫而使執行緒被掛起時,執行緒進入阻塞狀態

3.死亡狀態

當一個執行緒的run方法執行完畢,stop方法被呼叫或者在執行過程中出現未捕獲的異常時,執行緒進入死亡狀態。

四、執行緒排程

當同時有多個執行緒處於可執行狀態,他們需要排隊等待CPU資源,每個執行緒會自動 獲得一個執行緒的優先順序,優先順序的高低反映出執行緒的重要或緊急程度,可執行狀態的執行緒按優先順序排隊。執行緒排程依據建立在優先順序基礎上的“先到先服務”原則。
執行緒排程室搶佔式排程,即在當前執行緒執行過程中如果有一個更高優先順序的執行緒進入可執行狀態,則這個更高優先順序的執行緒立即被排程執行。

1.執行緒優先順序

執行緒的優先順序用1~10表示,10表示優先順序最高,預設值是5,每個優先順序對應一個Thread類的公用靜態常量
例如:public static final int NORM_PRIORITY=5;
每個執行緒的優先順序都介於Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間
執行緒的優先順序可以通過setPrioity(int grade)方法更改

2.實現執行緒排程的方法

實現執行緒排程的方法
1.join()方法
join方法使當前執行緒暫停執行,等待呼叫該方法的執行緒結束後再繼續執行本執行緒。
它有3中過載形式
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)

示例:使用join()阻塞執行緒
實現步驟:
1.定義執行緒類,輸出5次當前執行緒的名稱
2.定義測試類,使用join方法阻塞主執行緒

package xc.test2;

public class MyThread extends Thread {
    public MyThread(String name){
        super(name);
    }
    public void run(){
        for (int i = 0; i <5 ; i++) {
            //輸出當前執行緒的名稱
            System.out.println(Thread.currentThread().getName()+""+i);
        }
    }
}

package xc.test2;

public class Test {
    public static void main(String[] args) {
        //主執行緒執行五次後,開始執行MyThread執行緒
        for (int i = 0; i <10 ; i++) {
            if (i==5){
                MyThread t = new MyThread("MyThread");
                try {
                t.start();
                t.join();//把該執行緒通過join方法插入到主執行緒面前
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+""+i);
        }
    }
}

示例:使用sleep()方法阻塞執行緒
實現步驟:
定義執行緒
在run()方法中使用sleep()方法阻塞執行緒
定義測試類

package xc.test3;

public class Wait {
    //==示例:使用sleep()方法阻塞執行緒==
    public static void bySec(long s){
        for (int i = 0; i < s; i++) {
            System.out.println((i+1)+"秒");
        }
        try {
            Thread.sleep(1000);//括號中的是毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package xc.test3;

public class Test {
    public static void main(String[] args) {
        System.out.println("wait");//提示等待
        Wait.bySec(5);//讓主執行緒等待五秒再執行
        System.out.println("start");//提示恢復執行

    }
}

2.yield()方法
語法格式:

  • public static void yield()

yield方法可讓當前執行緒暫停執行,允許其他執行緒執行,但該執行緒仍處於可執行狀態,並不變為阻塞狀態。此時,系統選擇其他相同或更高優先順序執行緒執行,若無其他相同或更高優先順序執行緒,則該執行緒繼續執行。

示例:使用yield方法暫停執行緒
實現步驟:
1.定義兩個執行緒
2.在run方法中使用yield方法暫停執行緒
3.定義測試類

package xc.test4;

public class FirstThread  extends Thread{
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println("第一個執行緒的第"+(i+1)+"次執行");
            Thread.yield();//暫停執行緒
        }
    }
}

package xc.test4;

public class SecThread extends Thread{
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println("第二個執行緒的第"+(i+1)+"次執行");
            Thread.yield();
        }
    }
}

package xc.test4;

public class Test {
    public static void main(String[] args) {
        FirstThread f = new FirstThread();
        SecThread s = new SecThread();
        f.start();
        s.start();
    }
}

sleep方法與yield方法的區別

sleep()方法 yield()方法
使當前執行緒進入被阻塞的狀態 使當前執行緒進入暫停執行的狀態
即使沒有其他等待執行的執行緒,當前執行緒也會等待指定的時間 如果沒有其他等待執行的執行緒,當前執行緒會馬上恢復執行
其他等待執行的執行緒的機會是均等的 會執行優先順序相同或更高的執行緒

最後

感謝你看到這裡,看完有什麼的不懂的可以在評論區問我,覺得文章對你有幫助的話記得給我點個贊,每天都會分享java相關技術文章或行業資訊,歡迎大家關注和轉發文章!