Java 創(chuàng)建線程的 3 種方式

Java 創(chuàng)建線程的 3 種方式

Java 創(chuàng)建線程有多種方式,我們經(jīng)常使用的一般為以下 3 種。

  1. 直接繼承 Thread 類
  2. 實(shí)現(xiàn) Runnable 接口
  3. 實(shí)現(xiàn) Callable 接口

繼承 Thread 類,覆蓋 run 方法

這是創(chuàng)建一個(gè)線程的最簡(jiǎn)單方式,也很清晰,一般在確定創(chuàng)建的線程子類不需要繼承其他的類的情況下使用。


public class CreateThreadDemo1 {

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

}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello,this is " + Thread.currentThread().getName());
    }
}

/**
 * 輸出
 *
hello,this is Thread-1
hello,this is Thread-0
hello,this is Thread-2
 *
 * /

實(shí)現(xiàn) Runable 接口

實(shí)現(xiàn) Runnable 接口,并將邏輯實(shí)現(xiàn)在 run 方法中,創(chuàng)建該 Runnable 接口實(shí)例后,作為構(gòu)造方法參數(shù)傳入到 Thread 中。


public class CreateThreadDemo2 {

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread th1 = new Thread(myRunnable);
        Thread th2 = new Thread(myRunnable);
        Thread th3 = new Thread(myRunnable);

        th1.start();
        th2.start();
        th3.start();

    }

}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("hello,this is " + Thread.currentThread().getName());
    }
}


當(dāng)然也可以使用匿名內(nèi)部類來實(shí)現(xiàn) Runnable 接口,在 JDK1.8 中,我們可以使用優(yōu)雅的 Lambda 表達(dá)式.


public class CreateThreadDemo3 {

    public static void main(String[] args) {
        new Thread(() -> System.out.println("hello,this is " + Thread.currentThread().getName())).start();
    }

}


實(shí)現(xiàn) Callable 接口

在 JDK1.5 中 java.util.concurrent 包中增加了 Callable<V>接口,Callable 接口是 Java 對(duì)多線程 API 的增強(qiáng),相較于 Runnable 接口,Callable 接口有 2 點(diǎn)增強(qiáng)

  1. call 方法有返回值,可以方便的返回線程的執(zhí)行結(jié)果
  2. call 方法允許聲明異常,如果線程在執(zhí)行過程中發(fā)生異常,會(huì)將異常包裝后在獲取結(jié)果時(shí)捕捉

下面我們使用 Callable 接口演示如何獲取線程的執(zhí)行結(jié)果。

使用 Callable 接口需要 FutureTask 類來進(jìn)行包裝,然后傳入到 Thread 實(shí)例中。步驟一般如下:

  1. 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call()方法
  2. 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,并將其做為構(gòu)造參數(shù)傳入到 FutureTask 類實(shí)例中,該 FutureTask 對(duì)象封裝了 Callback 對(duì)象的 call()方法的返回值
  3. 將 FutureTask 對(duì)象作為 Thread 實(shí)例的構(gòu)造參數(shù)(target)傳入到 Thread 實(shí)例中,創(chuàng)建并啟動(dòng)新線程
  4. 調(diào)用 FutureTask 對(duì)象的 get()方法來獲得子線程執(zhí)行結(jié)束后的返回值

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CreateThreadDemo4 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //將Callable接口的實(shí)現(xiàn)實(shí)例作為構(gòu)造參數(shù)傳遞給FutureTask實(shí)例
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable1());
        //將futureTask作為構(gòu)造參數(shù)傳遞給Thread實(shí)例,并啟動(dòng)這個(gè)線程
        new Thread(futureTask).start();
        //在主線程中獲取futureTask實(shí)例的執(zhí)行結(jié)果
        String result = futureTask.get();
        System.out.println(result);//打印線程執(zhí)行結(jié)果--線程的名稱Thread0
    }

}

//實(shí)現(xiàn)Callable接口,返回值為String類型
class MyCallable1 implements Callable<String> {

    //將線程的名字做為返回值
    @Override
    public String call() throws Exception {
        String name = Thread.currentThread().getName();
        Thread.sleep(1000);
        return name;
    }
}

/**
 * 輸出
 * Thread-0
**/


正如我們所設(shè)計(jì)的,主線程開啟子線程后,順利的拿到了子線程的執(zhí)行結(jié)果Thread-0,但是上一個(gè)例子寫法實(shí)在是有些羅嗦,作為一個(gè)邏輯簡(jiǎn)單的 Demo,使用匿名內(nèi)部類和 Lambda 語法來改寫一下,得到一個(gè)更為簡(jiǎn)單的 Demo。


import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CreateThreadDemo5 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(() -> Thread.currentThread().getName());
        new Thread(task).start();
        System.out.println(task.get());
    }

}

我們已經(jīng)能夠得到線程的執(zhí)行結(jié)果了,但是如果線程執(zhí)行中發(fā)生異常呢?我們?cè)撊绾尾蹲疆惓#緾allable 接口允許拋出異常,并且在 futureTask 實(shí)例進(jìn)行 get()獲取結(jié)果時(shí),可以將異常捕獲(FutureTask 將 Callable 中發(fā)生的異常進(jìn)行了包裝)。

示例如下:

package xxx....;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CreateThreadDemo6 {

    public static void main(String[] args) {

        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();
        try {
            String result = futureTask.get();//在get()獲取執(zhí)行結(jié)果時(shí),可以捕捉異常
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {//call方法拋出的異常已經(jīng)被ExecutionException包裝
            System.out.println("catch it !!!");
            e.printStackTrace();
        }
    }

}

class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        String name = Thread.currentThread().getName();
        Thread.sleep(1000);
        Object o = null;
        System.out.println(o.toString());//NPE is here
        return name;
    }
}

/**
 * 輸出
 * catch it !!!
 * java.util.concurrent.ExecutionException: java.lang.NullPointerException
 * at java.util.concurrent.FutureTask.report(FutureTask.java:122)
 * at java.util.concurrent.FutureTask.get(FutureTask.java:192)
 * at xxx.....CreateThreadDemo6.main(CreateThreadDemo6.java:14)
 * Caused by: java.lang.NullPointerException
 * at xxx.....MyCallable.call(CreateThreadDemo6.java:33)
 * at xxx.....MyCallable.call(CreateThreadDemo6.java:26)
 * at java.util.concurrent.FutureTask.run(FutureTask.java:266)
 * at java.lang.Thread.run(Thread.java:748)
 */



這樣我們就可以捕獲線程執(zhí)行中的異常了。那么 Callable 接口是如何將異常傳遞出去的呢?主線程為什么可以捕獲到 Callable 接口的異常呢?

FutureTask 類實(shí)現(xiàn)了 RunnableFuture 接口,而 RunnableFuture 接口繼承了RunnableFuture<V>,其中Future<V>接口代表可獲取線程執(zhí)行結(jié)果。

Future 接口中的 get()方法,聲明可以拋出ExecutionException異常,get()方法聲明為:V get() throws InterruptedException, ExecutionException;

在 Callable 接口的 run()方法執(zhí)行時(shí),如果發(fā)生異常,則 FutureTask 類會(huì)捕捉到這個(gè)異常,并且使用ExecutionException進(jìn)行一次包裝。在外部調(diào)用 get()方法時(shí),則會(huì)拋出這個(gè)對(duì)原始異常進(jìn)行包裝之后的ExecutionException

我們可以截取一段 JDK 的源碼來看得更清晰。


public class FutureTask<V> implements RunnableFuture<V> {

    .....
    public void run() {
     try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {//如果callable實(shí)例在運(yùn)行中發(fā)生異常
                    result = null;
                    ran = false;
                    setException(ex);//在這里進(jìn)行異常的包裝
                }
                if (ran)
                    set(result);
            }
        } finally {

    ....

    //callable實(shí)例發(fā)生異常時(shí),調(diào)用setException()方法將異常進(jìn)行記錄
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;//將異常進(jìn)行記錄
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }



    //report方法為get()方法所調(diào)用,返回線程的執(zhí)行結(jié)果或者異常信息,就是在這個(gè)方法中,對(duì)Callable執(zhí)行的原始異常進(jìn)行了包裝
    /**
     * Returns result or throws exception for completed task.
     *
     * @param s completed state value
     */
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {//注意ExecutionException,對(duì)原始異常進(jìn)行了包裝
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);//在這里對(duì)Callable發(fā)送的異常進(jìn)行了包裝
    }


    //獲取線程執(zhí)行結(jié)果的get()方法
    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);//獲取執(zhí)行結(jié)果時(shí),調(diào)用report()方法獲取結(jié)果或者異常信息
    }

    ....

}

小結(jié)

用一張圖來表示 Java 常用的創(chuàng)建線程的三種方式。

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容