創建多線程的三種方式

在java中給我們提供了三種方式來創建多線程。前兩種是我們比較常見的,第三種是JDK1.5之后提供給我們的。接下來我們詳細的看一下這三種創建線程的寫法。

繼承Thread類

第一種方式是繼承Thread類的寫法,代碼如下:

        Thread thread = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("線程創建的第一種方式:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();

注意這里我們覆蓋的是run方法,而不是start方法,并且也千萬不要覆蓋start方法。為什么不能覆蓋start方法呢?我們看一下Thread源碼中start方法是怎么寫的:

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

注意這個方法是加鎖的方法。在這個方法中最重要的一段代碼是:start0();我們接著再來看一下start0()這個方法:

    private native void start0();

它是個native方法。就是這個native的start0()方法,它實現了啟動線程,申請棧內存、運行run方法、修改線程狀態等職責。線程管理和棧內存管理都是由jvm負責的。如果你覆蓋了start方法,也就是撤銷了線程管理和棧內存管理的能力,這樣還如何啟動一個線程呢?不過Thread的這個設計是很精妙的,因為你只需要關注你的業務邏輯就行了,而對于線程和棧內存的管理都有JVM來做就行了。如果在你的開發中不得不要覆蓋start方法的話,請千萬要記得調用super.start(),要不然你的線程無法啟動。

實現Runnable接口

第二種創建線程的方式是實現Runnable接口。具體代碼請看下面:

       Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("線程創建的第二種方式:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread2.start();

這種寫法是實現了Runnable接口的一種寫法。它的原理是什么呢?我們來看一下Thread源碼中run的寫法:

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

在run方法中我們可以看到如果target != null就調用target.run()方法。而這個target是從哪兒來的呢?我們繼續看Thread的源碼,發現在init的方法中有這樣一句話:

this.target = target;

接下來我們再看init()這個方法是在哪兒被調用的?通過翻讀源碼我們可以發現在Thread的每一個構造函數中都會調用init這個方法,并且有這樣一個構造函數:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

看到了吧。我們就是在創建Thread的時候,通過Thread的構造函數傳遞進去的Runnable的實現類。而線程啟動的時候,調用run方法,run方法又接著調用Runnable實現類的run方法!!!!

實現Callable接口

在JDK1.5之后又給我們提供了一種新的創建線程的方式:實現Callable方法。具體代碼如下:

        FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int i = 0;
                for(;i<10;i++){
                    System.out.println("線程創建的第三種方式:"+Thread.currentThread().getName());
                }
                return i;
            }
        });
        new Thread(ft).start();

在上面的代碼中,在創建FutureTask對象的時候,我們把Callable的一個匿名實現類當做參數傳到了FutureTask的構造函數中,而在啟動線程的時候,我們又把創建的FutureTask的對象當做參數傳到了Thread的構造函數中。在這里請注意我們此時不是覆蓋的run方法,而是一個叫call的方法。大家可能會感到疑惑這個FutureTask和Callable這兩個到底是個什么玩意?下面我們一個一個的分析:

FutureTask

通過翻讀FutureTask的源碼我們可以看出來實現了RunnableFuture接口,而RunnableFuture接口又繼承了Runnable和Future接口。注意:這里是繼承了兩個接口!你可能會有疑問JAVA中不是沒有多繼承嗎?不錯,java中類是沒有多繼承的,而對于接口是有多繼承的!!!到這里我們明白一件事,那就是FutureTask是Runnable接口的一個實現類。到這里你是不是明白了點什么呢?

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> 

如果你不明白的話,那就多看幾遍第二種創建線程的方式和它的原理吧。接下來我們來看一下FutureTask這個類中的run方法是怎么寫的:

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();

上面這個方法中的代碼我沒有貼全,只貼出來了主要的部分。在這個方法中我們會發現這樣的兩句話Callable<V> c = callable;result = c.call();這兩句話就是關鍵!!!通過翻讀源碼我們就會發現源碼這個callable就是我們剛才傳到FutureTask中的Callable的實現類啊!

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

c.call()那不就是調用的Callable實現類的call方法嗎?!!!到這里終于真相大白了!FutureTask中其他方法有興趣的同學可以繼續研究一下。

Callable

在Callable這個接口中只有一個call方法。
總結

在實際編碼中,我們看到創建線程更多的是使用第二種方式,因為它更符合java中面向接口編程的思想。
最后出個題考一下大家,請說出下面代碼的運行結果:

      new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println("Runnable實現類的調用:");
          }
      }){
          @Override
          public void run() {
              System.out.println("繼承Thread的調用:");
          }
      }.start();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容

  • 先看幾個概念:線程:進程中負責程序執行的執行單元。一個進程中至少有一個線程。多線程:解決多任務同時執行的需求,合理...
    yeying12321閱讀 563評論 0 0
  • 一.線程與進程相關 1.進程 ??定義:進程是具有獨立功能的程序關于某個數據集合上的一次運行活動,進程是操作系統分...
    Geeks_Liu閱讀 1,736評論 2 4
  • 下面是我自己收集整理的Java線程相關的面試題,可以用它來好好準備面試。 參考文檔:-《Java核心技術 卷一》-...
    阿呆變Geek閱讀 14,884評論 14 507
  • 線程概述 線程與進程 進程 ?每個運行中的任務(通常是程序)就是一個進程。當一個程序進入內存運行時,即變成了一個進...
    閩越布衣閱讀 1,019評論 1 7
  • 一個朋友,好像從我有記憶開始他就在了,而且我們形影不離,我們對彼此的了解有些方面甚至超過家人(畢竟有些事情不能和家...
    ChuJimmy閱讀 156評論 0 0