java多線程解析之Callable

Callable和Runnable有什么區(qū)別?

首先,Runnable是出自jdk1.0,Callable出自jdk1.5,那么,后出的類肯定對(duì)于前者有增強(qiáng)。
再看Runnable的run方法與Callable的call方法進(jìn)行對(duì)比

// Runnable
public abstract void run();

// Callable
V call() throws Exception;

可以看出,run方法沒有返回值,call方法有返回值并能夠拋出異常。

在對(duì)Callable原理進(jìn)行解析之前,首先先看一下Callable的基本用法

方式一:通過FutureTask,交給線程池執(zhí)行,阻塞等待結(jié)果返回

    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
            public String call() throws InterruptedException {
                Thread.sleep(3000L);
                System.out.println("當(dāng)前線程:" + Thread.currentThread().getName());
                return "hello world";
            }
        });
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        System.out.println("開始時(shí)間戳為:" + System.currentTimeMillis());
        executorService.submit(futureTask);
        String result = futureTask.get();
        System.out.println("結(jié)束時(shí)間戳為:" + System.currentTimeMillis() + ",result = " + result);
    }
結(jié)果打印:
開始時(shí)間戳為:1639275517192
當(dāng)前線程:pool-1-thread-1
結(jié)束時(shí)間戳為:1639275520202,result = hello world

可以看出,callable的邏輯是在異步線程中處理的,并且,主線程通過阻塞式接受異步線程返回的內(nèi)容。
那么,如果直接調(diào)用futureTask的run方法會(huì)怎樣?

    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
            public String call() throws InterruptedException {
                Thread.sleep(3000L);
                System.out.println("當(dāng)前線程:" + Thread.currentThread().getName());
                return "hello world";
            }
        });
        System.out.println("開始時(shí)間戳為:" + System.currentTimeMillis());
        futureTask.run();
        String result = futureTask.get();
        System.out.println("結(jié)束時(shí)間戳為:" + System.currentTimeMillis() + ",result = " + result);
    }
結(jié)果打?。?開始時(shí)間戳為:1639275759794
當(dāng)前線程:main
結(jié)束時(shí)間戳為:1639275762796,result = hello world

結(jié)果和直接調(diào)用Runnable的方法是一樣的,最終是由主線程完成執(zhí)行

方式二:通過線程池執(zhí)行,返回Future對(duì)象

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        System.out.println("開始時(shí)間戳為:" + System.currentTimeMillis());
        Future<String> future = executorService.submit(new Callable<String>() {
            public String call() throws InterruptedException {
                Thread.sleep(3000L);
                System.out.println("當(dāng)前線程:" + Thread.currentThread().getName());
                return "hello world";
            }
        });
        String result = future.get();
        System.out.println("結(jié)束時(shí)間戳為:" + System.currentTimeMillis() + ",result = " + result);
    }
結(jié)果打印:
開始時(shí)間戳為:1639276055569
當(dāng)前線程:pool-1-thread-1
結(jié)束時(shí)間戳為:1639276058583,result = hello world

實(shí)現(xiàn)效果與方式一相同,這里返回的Future,實(shí)際上也是FutureTask


原理分析

首先分析下線程池的submit方法

    // FutureTask傳入方式調(diào)用的submit方法
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    // Callable傳入方式調(diào)用的submit方法
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

1.最終都會(huì)調(diào)用newTaskFor方法生成一個(gè)RunnableFuture方法
2.調(diào)用execute方法
3.返回RunnableFuture對(duì)象
線程池相關(guān)的原理不在本文章中具體展開,有興趣的小伙伴可以自行研究或者在之后的文章中推出詳解

再分析下future.get方法如何阻塞等待異步線程執(zhí)行結(jié)果

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            // 如果狀態(tài)還在進(jìn)行中,或者剛創(chuàng)建,就阻塞等待
            s = awaitDone(false, 0L);
        // 調(diào)用Report,返回結(jié)果或者拋出異常
        return report(s);
    }

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            // 狀態(tài)正常,返回結(jié)果
            return (V)x;
        if (s >= CANCELLED)
            // 狀態(tài)取消,拋出取消異常
            throw new CancellationException();
        // 拋出程序執(zhí)行異常
        throw new ExecutionException((Throwable)x);
    }

FutureTask內(nèi)部維護(hù)了任務(wù)進(jìn)行的狀態(tài),當(dāng)異步任務(wù)完成時(shí),會(huì)將狀態(tài)碼設(shè)置為已完成,如果發(fā)生異常,會(huì)將狀態(tài)碼設(shè)置成對(duì)應(yīng)的異常狀態(tài)碼。

如何自己實(shí)現(xiàn)一個(gè)類似的能夠有返回值的Runnable

    public static class MyRunnable implements Runnable {

        private String result;

        private AtomicBoolean finished = new AtomicBoolean();

        public void run() {
            try {
                Thread.sleep(3000L);
                System.out.println("當(dāng)前線程:" + Thread.currentThread().getName());
                this.result = "hello world";
                finished.compareAndSet(false, true);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String get() {
            while (true) {
                if (finished.get()) {
                    return result;
                }
            }
        }

    }

    public static void main(String[] args) throws Exception {
        System.out.println("開始時(shí)間戳為:" + System.currentTimeMillis());
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
        String result = myRunnable.get();
        System.out.println("結(jié)束時(shí)間戳為:" + System.currentTimeMillis() + ",result = " + result);
    }
結(jié)果打?。?開始時(shí)間戳為:1639280212975
當(dāng)前線程:異步線程
結(jié)束時(shí)間戳為:1639280215984,result = hello world

本質(zhì)上就是調(diào)用線程持有了異步線程的對(duì)象引用,jdk給我們封裝好了對(duì)應(yīng)的邏輯,無須額外處理。

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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