1. 程式人生 > >Java 多執行緒

Java 多執行緒

程序和執行緒的概念

程序

幾乎所有的作業系統都支援程序的概念。一個任務通常對應一個程序。程序具有如下特徵:

  • 程序通常是獨立存在的,擁有自己獨立的資源。
  • 程序擁有自己的生命週期和各種不同的狀態。
  • 多個程序可以在單個處理器上併發執行,多個程序之間不會互相響應。

執行緒

執行緒是cpu執行的最小單元,一個程序可以有多個執行緒。一個執行緒必須有一個父程序。執行緒可以擁有自己的堆疊,但不擁有系統資源。對 cpu 而言,在同一時間,只能執行一個執行緒。之所以看起來像是在同時執行,是因為cpu輪循的時間特別快。執行緒由程序來負責管理和排程。

執行緒的生命週期

在網上扒了一張圖,足夠清晰明瞭。

Java執行緒具有五種基本狀態

新建狀態(New):當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行;

執行狀態(Running):當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就 緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:執行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;

2.同步阻塞 -- 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態;

3.其他阻塞 -- 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。

死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

需要注意的是: wait() 、notify()、notifyAll() 方法是 Object 類的方法。

java 中執行緒的建立和啟動

1.繼承自 Thread 類建立執行緒:

  • 定義類繼承自 Thread 類,重寫該類的 run() 方法,run() 方法方法體代執行緒要執行的任務。因此,run() 方法也稱執行緒執行體。
  • 建立定義的執行緒類的例項,即建立執行緒物件。
  • 呼叫執行緒物件的 start() 方法啟動該執行緒。
    public class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println(getName() + "-----" + i);
            }
        }
    
        public static void main(String[] args){
            new MyThread().start();
            new MyThread().start();
        }
    }

擷取部分執行結果如下:

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

  • 定義 Runnable 介面的實現類,並重寫該介面的 run()方法,該 run() 同樣是執行緒執行體。
  • 建立 Runnable 實現類的例項,將 Runnable 實現類的例項傳入作為引數傳入,建立 Thread 類的例項,作為執行緒物件。
  • 呼叫執行緒物件的 start() 方法啟動執行緒。
    public class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName() + "-----" + i);
            }
        }

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

擷取部分執行結果如下:

3.使用 Callable 和 Future 建立執行緒:

  • 建立 Callable 的實現類,並實現 call() 方法,call() 方法為執行緒執行體。泛型引數為 call() 返回值。
  • 建立 Callable 實現類的例項,並將該物件傳入,建立 FutureTask 類的例項。該 FutureTask 的物件封裝了 該 Callable 例項物件的返回值。
  • 建立 FutureTask 的例項,作為 target 傳入,建立 Thread 類的物件,作為執行緒物件。
  • 呼叫執行緒物件的 start() 方法啟動執行緒。
  • 如有必要,可以通過 FutureTask 物件的 get() 方法來獲得執行緒執行結束後的返回值。
    public class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int i = 0;
            for (; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName() + "-----" + i);
            }
            return i;
        }
        public static void main(String[] args) {
            MyCallable mc = new MyCallable();
            FutureTask<Integer> task = new FutureTask<Integer>(mc);
            new Thread(task).start();
            try {
                Thread.sleep(5000);// 可能做一些事情
                System.out.println("----------" + task.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

值得一提的是:雖然直接繼承自 Thread 類的方式實現起來最為簡單,但是由於java的類只能單繼承,但可以實現多個介面。所以一般我們不採用該方式。另外,啟動執行緒的方式,都是通過呼叫執行緒物件的 start() 方法,切莫不要直接呼叫 run() 方法,這樣實際是把該執行緒類當作了一個普通類而已。