併發編程(一)之線程的創建和啓動

併發編程之線程的創建和啓動

一、線程創建

1.1. 實現Runnable接口

實現Runnable接口,重寫run方法,實現Runnable接口的實現類的實例對象作爲Thread構造函數的target:

public class CreateThread implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ": 通過Runnable創建線程......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread createThread = new Thread(new CreateThread(), "子線程");
        createThread.start();
        System.out.println(Thread.currentThread().getName() + "主線程......");
    }

}

我們看一下Runnable接口:


@FunctionalInterface
public interface Runnable {

    public abstract void run();

}

我們可以看到RunnableFunctionInterface接口,說明使用lamdba的寫法去實現線程。很簡潔。

public class CreateThread {

    public static void main(String[] args) {
        
        System.out.println(Thread.currentThread().getName() + "主線程......");

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + ": 通過Runnable創建線程......");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子線程1").start();
    }

}
1.2. 繼承Thread

繼承Thread類,重寫run方法。其實Thread也是實現了Runnable接口,裏面有很多native方法。後面會分析。

public class CreateThread1 extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ": 通過Runnable創建線程......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread createThread = new CreateThread1();
        createThread.setName("子線程");
        createThread.start();
        System.out.println(Thread.currentThread().getName() + "主線程......");
    }

}

我們簡單看見一下Thread裏面的run方法:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

這個裏面的target其實就是我們傳入的Runnable,這也是爲啥我們可以實現Runnable接口的run方法,這也是就是繼承Thread(把run重寫)和實現Runnable(調用target.run()方法)的區別。

更值得我們注意的是run方法是異常的處理和拋出的,這意味的子線程發生異常,主線程是無法捕獲到的(但是具體還是有處理的方法的,日後介紹,挖個坑,日後填)。

1.3. 總結
  1. 實現Runnable接口更好
  2. Runnable方便配合線程池使用
  3. Thread線程執行和線程創建無法解耦
  4. Thread繼承之後無法繼承其他線程,限制擴展性

最後再說一下 :創建線程我們可以有兩種方法實現Runnable和繼承Thread,但是本質來說創建線程只有一種(構造Thread類),run方法有兩種傳入Runnable通過target.run()調用和重寫run()方法。

二、線程的啓動

我們從上面的看到,線程的啓動涉及到start()run()兩種方法。

我們先看看start()方法:

/**
 * 1. start方法將導致當前線程開始執行。由JVM調用當前線程的run方法
 * 2. 結果是兩個線程同時運行:當前線程(從對start方法的調用返回)和另一個線程(執行其run方法)
 * 3. 不允許多次啓動線程, 線程一旦完成執行就不會重新啓動 
 */
public synchronized void start() {
        /**
         * 對於VM創建/設置的主方法線程或“系統”組線程,不調用此方法。
         * 將來添加到此方法的任何新功能可能也必須添加到VM中.
         *
         * threadStatus = 0 意味着線程處於初始狀態
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* 通知組此線程即將啓動,以便可以將其添加到組的線程列表中,並且可以減少組的未啓動計數。*/
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* 不做處理。如果start0拋出了一個Throwable,那麼它將被傳遞到調用堆棧上 */
            }
        }
    }

    private native void start0();

從上面代碼的註釋我們可以看到:

  1. start()方法被synchronized進行修飾,爲同步方法,這樣避免了多次調用問題;
  2. 使用threadStatus(此變量被volatile修飾)記錄線程狀態;多次調用會拋出異常;
  3. 這方法會重寫的run方法被虛擬機調用,是子線程執行的run方法。

上面已經介紹了startrun方法,接着我們寫一個例子看看兩個的區別:

public static void main(String[] args) {

    Thread one = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "啓動");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "線程1");

    Thread two = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "啓動");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "線程2");
    one.start();
    two.run();
    System.out.println("主線程啓動");

}

執行結果也很明顯,調用run會阻塞主線程,start是異步的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章