多線程實(shí)現(xiàn)方式

多線程有幾種實(shí)現(xiàn)方式?如果被問到這個(gè)問題一定很頭疼,因?yàn)榘俣纫幌码S便就能出現(xiàn)各種各樣的答案。兩種、三種、四種、五種、六種、七種。。。

但本質(zhì)上來講,個(gè)人認(rèn)為只有一種方式:實(shí)現(xiàn)Runnable接口。

先放個(gè)圖:


線程相關(guān)類圖.png

1、實(shí)現(xiàn)Runnable接口

public class DemoThreadTask implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
    
    public static void main(String[] args) {
        DemoThreadTask task = new DemoThreadTask();
        Thread t = new Thread(task);
        t.start();
        ...
    }
}

實(shí)現(xiàn)Runnable接口,利用Runnable實(shí)例構(gòu)造Thread,是較常用且最本質(zhì)實(shí)現(xiàn)。此構(gòu)造方法相當(dāng)于對(duì)Runnable實(shí)例進(jìn)行一層包裝,在線程t啟動(dòng)時(shí),調(diào)用Thread的run方法從而間接調(diào)用target.run():

public class Thread implements Runnable {
    /* What will be run. */
    private Runnable target;

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

2、繼承Thread類

public class DemoThread extends Thread{
    @Override 
    //重寫run方法
    public void run() {
        // TODO Auto-generated method stub
    }

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

這種實(shí)現(xiàn)方式是顯示的繼承了Thread,但從類圖中我們可以看到,Thread類本身就繼承自Runnable,所以繼承Thread的本質(zhì)依然是實(shí)現(xiàn)Runnable接口定義的run方法。

需要注意的是繼承Thread方式,target對(duì)象為null,重寫了run方法,導(dǎo)致方式1中的Thread原生的run方法失效,因此并不會(huì)調(diào)用到target.run()的邏輯,而是直接調(diào)用子類重寫的run方法。

因?yàn)閖ava是單根繼承,此方式一般不常用。

3、實(shí)現(xiàn)Callable接口并通過FutureTask包裝
先看demo:

public class DemoCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }
    
    public static void main(String[] args) throws Exception {
        DemoCallable c = new DemoCallable();
        FutureTask<String> future = new FutureTask<>(c); 
        Thread t = new Thread(future);
        t.start();
        ...
        String result = future.get(); //同步獲取返回結(jié)果
        System.out.println(result);
    }
}

實(shí)現(xiàn)Callable接口通過FutureTask包裝,可以獲取到線程的處理結(jié)果,future.get()方法獲取返回值,如果線程還沒執(zhí)行完,則會(huì)阻塞。

這個(gè)方法里,明明沒有看到run方法,沒有看到Runnable,為什么說本質(zhì)也是實(shí)現(xiàn)Runnable接口呢?

回看開篇的類圖,F(xiàn)utureTask實(shí)現(xiàn)了RunnableFuture,RunnableFuture則實(shí)現(xiàn)了Runnable和Future兩個(gè)接口。因此構(gòu)造Thread時(shí),F(xiàn)utureTask還是被轉(zhuǎn)型為Runnable使用。因此其本質(zhì)還是實(shí)現(xiàn)Runnable接口。

至于FutureTask的工作原理,后續(xù)篇章繼續(xù)分析。

4、匿名內(nèi)部類

匿名內(nèi)部類也有多種變體,上述三種方式都可以使用匿名內(nèi)部類來隱式實(shí)例化。

public class Demo{
    
    public static void main(String[] args) throws Exception {
        //方式一:Thread匿名內(nèi)部類
        new Thread(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
            }
        }.start();
        
        //方式二:Runnable匿名內(nèi)部類
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
            }
        }).start();
        
        ...
    }
}

匿名內(nèi)部類的優(yōu)點(diǎn)在于使用方便,不用額外定義類,缺點(diǎn)就是代碼可讀性差。

5、Lambda表達(dá)式

Lambda表達(dá)式是jdk8引入的,已不是什么新東西,現(xiàn)在都jdk10了。demo如下:

public class Demo{
    public static void main(String[] args) throws Exception {
        new Thread(() -> System.out.println("running") ).start() ;
        ...
    }
}

如此簡(jiǎn)潔的Lambda表達(dá)式,有沒有吸引到你呢?當(dāng)然本質(zhì)不多說,還是基于Runnable接口。

6、線程池

public class DemoThreadTask implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("running");
    }
    
    public static void main(String[] args) {
        DemoThreadTask task = new DemoThreadTask();
        ExecutorService ex = Executors.newCachedThreadPool();
        ex.execute(task);
        ...
    }
}

線程池與前面所述其他方式的區(qū)別在于執(zhí)行線程的時(shí)候由ExecutorService去執(zhí)行,最終還是利用Thread創(chuàng)建線程。線程池的優(yōu)勢(shì)在于線程的復(fù)用,從而提高效率。

關(guān)于線程池,后續(xù)篇章會(huì)繼續(xù)詳解。

7、定時(shí)器

public class DemoTimmerTask {

    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate((new TimerTask() {
            @Override
            public void run() {
                System.out.println("定時(shí)任務(wù)1執(zhí)行了....");
            }
        }), 2000, 1000);
    }
}

TimerTask的實(shí)現(xiàn)了Runnable接口,Timer內(nèi)部有個(gè)TimerThread繼承自Thread,因此繞回來還是Thread + Runnable。

總結(jié),多線程的實(shí)現(xiàn)方式,在代碼中寫法千變?nèi)f化,但其本質(zhì)萬變不離其宗。

多線程系列目錄(不斷更新中):
線程啟動(dòng)原理
線程中斷機(jī)制
多線程實(shí)現(xiàn)方式
FutureTask實(shí)現(xiàn)原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態(tài)控制
線程池之ThreadPoolExecutor執(zhí)行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優(yōu)雅關(guān)閉實(shí)踐

最后編輯于
?著作權(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)容