1. 程式人生 > >Java入門系列-21-多執行緒

Java入門系列-21-多執行緒

什麼是執行緒

在作業系統中,一個應用程式的執行例項就是程序,程序有獨立的記憶體空間和系統資源,在工作管理員中可以看到程序。

執行緒是CPU排程和分派的基本單位,也是程序中執行運算的最小單位,可完成一個獨立的順序控制流程,當然一個程序中可以有多個執行緒。

多執行緒:一個程序中同時運行了多個執行緒,每個執行緒用來完成不同的工作。多個執行緒交替佔用CPU資源,並非真正的並行執行。

使用多執行緒能充分利用CPU的資源,簡化程式設計模型,帶來良好的使用者體驗。

一個程序啟動後擁有一個主執行緒,主執行緒用於產生其他子執行緒,而且主執行緒必須最後完成執行,它執行各種關閉動作。

在Java中main()方法為主執行緒入口,下面使用 Thread 類檢視主執行緒名。

public class MainThread {
    public static void main(String[] args) {
        //獲取當前執行緒
        Thread t=Thread.currentThread();
        System.out.println("當前執行緒名字:"+t.getName());
        //自定義執行緒名字
        t.setName("MyThread");
        System.out.println("當前執行緒名字:"+t.getName());
    }
}

建立執行緒

在Java中建立執行緒有兩種方式 1.繼承 java.lang.Thread 類 2.實現 java.lang.Runnable 介面

1.繼承 Thread 類建立執行緒

(1)定義MyThread類繼承Thread類

(2)重寫run()方法,編寫執行緒執行體

public class MyThread extends Thread{

    //重寫run方法
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

(3)建立執行緒物件,呼叫start()方法啟動執行緒

public class TestMyThread {

    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        //啟動執行緒
        myThread.start();
    }
}

多個執行緒同時啟動後是交替執行的,執行緒每次執行時長由分配的CPU時間片長度決定

修改 TestMyThread.java 觀察多執行緒交替執行

public class TestMyThread {

    public static void main(String[] args) {
        MyThread myThread1=new MyThread();
        MyThread myThread2=new MyThread();
        myThread1.start();
        myThread2.start();
    }
}

多執行幾次觀察效果

啟動執行緒能否直接呼叫 run()方法? 不能,呼叫run()方法只會是主執行緒執行。呼叫start()方法後,子執行緒執行run()方法,主執行緒和子執行緒並行交替執行。

2.實現 Runnable 介面建立執行緒

(1)定義MyRunnable類實現Runnable介面

(2)實現run()方法,編寫執行緒執行體

public class MyRunnable implements Runnable{
    //實現 run方法
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

(3)建立執行緒物件,呼叫start()方法啟動執行緒

public class TestMyRunnable {

    public static void main(String[] args) {
        Runnable runnable=new MyRunnable();
        //建立執行緒,傳入runnable
        Thread t=new Thread(runnable);
        t.start();
    }
}

執行緒的生命週期

建立狀態:執行緒建立完成,比如 MyThread thread=new MyThread

就緒狀態:執行緒物件呼叫 start() 方法,執行緒會等待CPU分配執行時間,並沒有立馬執行

執行狀態:執行緒分配到了執行時間,進入執行狀態。執行緒在執行中發生禮讓 (yield) 會回到就緒狀態

阻塞狀態:執行過程中遇到IO操作或程式碼 Thread.sleep(),阻塞後的執行緒不能直接回到執行狀態,需要重新進入就緒狀態等待資源的分配。

死亡狀態:自然執行完畢或外部干涉終止執行緒

執行緒排程

執行緒排程指按照特定機制為多個執行緒分配CPU的使用權

執行緒排程常用方法

方法 說明
setPriority(int newPriority) 更改執行緒的優先順序
static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的執行緒休眠
void join() 等待該執行緒終止
static void yield() 暫停當前正在執行的執行緒物件,並執行其他執行緒
void interrupt() 中斷執行緒
boolean isAlive() 測試執行緒是否處於活動狀態

執行緒優先順序的設定

執行緒優先順序由1~10表示,1最低,預設有限級為5。優先順序高的執行緒獲得CPU資源的概率較大。

public class TestPriority {

    public static void main(String[] args) {
        Thread t1=new Thread(new MyRunnable(),"執行緒A");
        Thread t2=new Thread(new MyRunnable(),"執行緒B");
        //最大優先順序
        t1.setPriority(Thread.MAX_PRIORITY);
        //最小優先順序
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

執行緒休眠

讓執行緒暫時睡眠指定時長,執行緒進入阻塞狀態,睡眠時間過後執行緒會再進入可執行狀態。

休眠時長以毫秒為單位,呼叫sleep()方法需要處理 InterruptedException異常。

public class TestSleep {
    
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            System.out.println("第 "+i+" 秒");
            try {
                //讓當前執行緒休眠1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

強制執行

使用 join() 方法實現,可以認為是執行緒的插隊,會先強制執行插隊的執行緒。

public class JoinThread implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println("執行緒名:"+Thread.currentThread().getName()+" i:"+i);
        }
        System.out.println("插隊執行緒執行完畢!");
    }   
}
public class TestJoin {

    public static void main(String[] args) {
        Thread joinThread=new Thread(new JoinThread(),"插隊的執行緒");
        //啟動後與主執行緒交替執行
        joinThread.start();
        for (int i = 1; i <= 10; i++) {
            if (i==5) {
                try {
                    System.out.println("====開始插隊強制執行====");
                    joinThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("執行緒名:"+Thread.currentThread().getName()+" i:"+i);
        }
        System.out.println("主執行緒執行完畢!");
    }
}

最一開始執行,主執行緒 main 和 "插隊的執行緒"是交替執行。當主執行緒的迴圈到第5次的時候,呼叫 "插隊的執行緒"的join方法,開始強制執行"插隊的執行緒",待"插隊的執行緒"執行完後,才繼續恢復 main 執行緒的迴圈。

執行緒禮讓

使用 yield() 方法實現,禮讓後會暫停當前執行緒,轉為就緒狀態,其他具有相同優先順序的執行緒獲得執行機會。

下面我們實現Runnable介面,在run方法中實現禮讓,建立兩個執行緒,達到某種條件時禮讓。

public class YieldThread implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("執行緒名:"+Thread.currentThread().getName()+" i:"+i);
            //當前執行緒執行到5後發生禮讓
            if (i==5) {
                System.out.println(Thread.currentThread().getName()+" 禮讓:");
                Thread.yield();
            }
        }
    }
}
public class TestYield {

    public static void main(String[] args) {
        Thread t1=new Thread(new YieldThread(),"A");
        Thread t2=new Thread(new YieldThread(),"B");
        t1.start();
        t2.start();
    }
}

只是提供一種可能,不能保證一定會實現禮讓

執行緒同步

首先看一個多線共享同一個資源引發的問題

倉庫有10個蘋果,小明、小紅、小亮每次可以從倉庫中拿1個蘋果,拿完蘋果後倉庫中的蘋果數量-1。

先編寫倉庫資源類,實現介面

//這個實現類將被多個執行緒物件共享
public class ResourceThread implements Runnable{
    private int num=10;
    @Override
    public void run() {
        while(true) {
            if (num<=0) {
                break;
            }
            num--;
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
        }
    }
}

編寫測試類,建立兩個執行緒物件,共享同一個資源

public class TestResource {

    public static void main(String[] args) {
        ResourceThread resource=new ResourceThread();
        //使用同一個Runnable實現類物件
        Thread t1=new Thread(resource,"小明");
        Thread t2=new Thread(resource,"小紅");
        Thread t3=new Thread(resource,"小亮");
        t1.start();
        t2.start();
        t3.start();
    }
}

執行後我們發現,每次拿完蘋果後的剩餘數量出現了問題,使用同步方法可以解決這個問題。

語法:

訪問修飾符 synchronized 返回型別 方法名(引數列表){
    ……
}

synchronized 就是為當前的執行緒宣告一個鎖

修改 ResourceThread.java 實現同步

//這個實現類將被多個執行緒物件共享
public class ResourceThread implements Runnable{
    private int num=10;
    private boolean isHave=true;
    @Override
    public void run() {
        while(isHave) {
            take();
        }
    }
    //同步方法
    public synchronized void take() {
        if (num<=0) {
            isHave=false;
            return;
        }
        num--;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
    }
}

實現同步的第二種方式同步程式碼塊

語法:

synchronized(syncObject){
    //需要同步的程式碼
}

syncObject為需同步的物件,通常為this

修改 ResourceThread.java 改為同步程式碼塊

//這個實現類將被多個執行緒物件共享
public class ResourceThread implements Runnable{
    private int num=10;
    private boolean isHave=true;
    @Override
    public void run() {
        while(isHave) {
            synchronized(this) {
                if (num<=0) {
                    isHave=false;
                    return;
                }
                num--;
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);               
            }
        }
    }
}