java - 為什麼要將Runnable介面的子類別物件傳遞給Thread的建構子?
巴扎黑
巴扎黑 2017-06-12 09:19:22
0
3
1594

此外,runnable相比thread除了繼承方面,程式碼和資料獨立體現在哪裡?像是有些部落格寫的thread不能共享資源,runnable能分享資源,將thread中的變數改成static不就行了吧?就像下面這篇說的http://blog.csdn.net/uudou/ar...

巴扎黑
巴扎黑

全部回覆(3)
漂亮男人

跟數據似乎關係不大,我覺得Runnable有兩個好處:

  1. 實作Runnable以後,也就是可以開個執行緒跑(一般是用executorService.exec(command),挫一點也可以用new Thread(command).start()),也可以不開線程阻塞式的跑(直接呼叫command.run());

  2. Java 1.8以後可以用Lambda來跑,例如:

new Thread(() -> {
    // Do something
}).start();


代言

Runnable的好處是各種場景都可以用,例如你可以讓任何一個Class implements Runnable,但是extends Thread就有一些限制,因為Java單繼承的原因,在有些場景下沒用。

Peter_Zhu

回答:

這個問題算是設計問題。

之所以將 Thread 和 Runnable 分開,是希望把線程的 "創建過程" 與線程的 "執行邏輯" 徹底分開。

也就是說:
執行緒的創建過程是「程式碼」;
執行緒的執行邏輯是「資料」;

這聽起來有點叫人暈呼,不都是 JAVA 代碼麼?怎麼程式碼又變成資料了呢?

我們不在這些概念上糾纏,我覺得可以倒轉過來思考這個問題,舉個例子來說明問題。

討論過程:

例如我要設計一個單執行緒程序,這個單執行緒需要完成兩個任務:

1、印一句 hello world;
2、計算一下 int a 與 int b 兩個數的和並輸出;

注意:到底是執行 1? 還是 2?是由參數 n 決定的,n 是隨機數…

為了讓這兩個任務在同一個執行緒執行,我們可以寫這樣的程式碼:

float n = (float)Math.random();

int a = 1;
int b = 1;

Thread t = new Thread() {
    @Override
    public void start() {
        if (n >= 0.5f) {
            System.out.println("hello world");
        } else {
            System.out.println(a + b);
        }
    }
};

t.start();

上面的程式碼確實是可以完成任務的,但問題是我們把線程的 "創建過程" 和 "業務邏輯" 混淆在一起了…

這樣不太妙。順便說一句,從作業系統層面來看,執行緒的創建過程其實是非常複雜的!

Java 語言把這種複雜性都封裝得看不見了,雖然程式碼上就是一個 Thread 類,呼叫起來似乎也沒什麼門檻,但 Thread 的創建過程還是很複雜、很消耗資源的。

言歸正傳,現在我再次加入一個小小的需求,除了前面的 1、2,我再加入一個 3,顯示一下系統當前時間戳。

於是任務變成了:
1、打印一句 hello world;
2、計算一下 int a 與 int b 兩個數的和並輸出;
3、顯示一下系統當前時間戳;

注意,這時候我們需要修改 Thread 的創建過程,也就是修改 start 函數:

float n = (float)Math.random();

int a = 1;
int b = 1;

Thread t = new Thread() {
    @Override
    public void start() {
        if (n >= 0.33f) {
            System.out.println("hello world");
        } else if (n >= 0.66f) {
            System.out.println(a + b);
        } else {
            System.out.println(System.currentTimeMillis()); // 这里是新增的语句
        }
    }
};

t.start();

討論至此,讓我們仔細觀察觀察…其實:

Thread t = new Thread() {
    @Override
    public void start() {
        // ...
    }
}

這部分程式碼是不變的,只有 start 函數裡面的程式碼是隨著需求變化而修改的。

那我們可不可以把這部分變化的內容包裝成一個介面? ?

這應該是個不錯的主意!

Thread t = new Thread() {
    private Runnable runnable; // 这里定义一个 Runnable 类型的成员

    @Override
    public void start() {
        if (null != this.runnable) {
            runnable.run(); // 在这里用接口来把频繁变化的业务逻辑从线程代码里给拿出去,只调用 run 函数
        }
    }
}

到這裡不知道你是否已經完全明白了? :D

哈哈,Java 的 Thread 類別不是剛好提供了一個帶有 Runnable 參數的建構器麼?

我們將業務程式碼被放到 Runnable 介面的實作類別裡:

class BizLogicRun implements Runnable {
    @Override
    public void run() {
        float n = (float)Math.rand();

        int a = 1;
        int b = 1;

        if (n >= 0.33f) {
            System.out.println("hello world");
        } else if (n >= 0.66f) {
            System.out.println(a + b);
        } else {
            System.out.println(System.currentTimeMillis()); // 这里是新增的语句
        }
    }
}

那麼最後,我們可以這麼呼叫:

Thread t = new Thread(new BizLogicRun());
t.start();

這樣就完成了線程的 "創建過程" 和 "業務邏輯" 徹底拆分!這種 "分割" 也為 Java 執行緒池(Thread Pool)技術做好了鋪墊。

說實話,範例程式碼中的 Thread t = new Thread() { ... } 這個還是夠簡單的,但在執行緒池中建立 Thread 可就沒這麼簡單了。

所以 "拆分" 是非常必要的!

另外,我們是否可以想像:

class PoolRun implements Runnable {
    List<Runnable> runnableList;
}

如果 Runable 實作類別裡面,夾帶的還是一個 Runnable 清單會怎麼樣呢?

總結:

1、使用Runnable 介面的目的是把線程的"創建過程" 與線程的"執行邏輯" 徹底分開;
2、Thread 不能共享資源,Runnable 能共享資源,這個說法是不正確的;
3、在討論過程中我們是從具體到抽象;
4、我在例子中給出的程式碼確實比較簡單,但希望能說明白問題;

好了,以上就是我對這個問題的回答,希望對你有幫助。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板