多線程實現方式

多線程有幾種實現方式?如果被問到這個問題一定很頭疼,因為百度一下隨便就能出現各種各樣的答案。兩種、三種、四種、五種、六種、七種。。。

但本質上來講,個人認為只有一種方式:實現Runnable接口。

先放個圖:


線程相關類圖.png

1、實現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();
        ...
    }
}

實現Runnable接口,利用Runnable實例構造Thread,是較常用且最本質實現。此構造方法相當于對Runnable實例進行一層包裝,在線程t啟動時,調用Thread的run方法從而間接調用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();
        ...
    }
}

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

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

因為java是單根繼承,此方式一般不常用。

3、實現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(); //同步獲取返回結果
        System.out.println(result);
    }
}

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

這個方法里,明明沒有看到run方法,沒有看到Runnable,為什么說本質也是實現Runnable接口呢?

回看開篇的類圖,FutureTask實現了RunnableFuture,RunnableFuture則實現了Runnable和Future兩個接口。因此構造Thread時,FutureTask還是被轉型為Runnable使用。因此其本質還是實現Runnable接口。

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

4、匿名內部類

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

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

匿名內部類的優點在于使用方便,不用額外定義類,缺點就是代碼可讀性差。

5、Lambda表達式

Lambda表達式是jdk8引入的,已不是什么新東西,現在都jdk10了。demo如下:

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

如此簡潔的Lambda表達式,有沒有吸引到你呢?當然本質不多說,還是基于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);
        ...
    }
}

線程池與前面所述其他方式的區別在于執行線程的時候由ExecutorService去執行,最終還是利用Thread創建線程。線程池的優勢在于線程的復用,從而提高效率。

關于線程池,后續篇章會繼續詳解。

7、定時器

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("定時任務1執行了....");
            }
        }), 2000, 1000);
    }
}

TimerTask的實現了Runnable接口,Timer內部有個TimerThread繼承自Thread,因此繞回來還是Thread + Runnable。

總結,多線程的實現方式,在代碼中寫法千變萬化,但其本質萬變不離其宗。

多線程系列目錄(不斷更新中):
線程啟動原理
線程中斷機制
多線程實現方式
FutureTask實現原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態控制
線程池之ThreadPoolExecutor執行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優雅關閉實踐

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容