java - 為什么要將Runnable接口的子類對象傳遞給Thread的構(gòu)造函數(shù)?
問題描述
此外,runnable相比thread除了繼承方面,代碼和數(shù)據(jù)獨立體現(xiàn)在哪?像有些博客上寫的thread不能共享資源,runnable能共享資源,將thread中的變量改成static不就行了吧?就像下面這篇說的http://blog.csdn.net/uudou/ar...
問題解答
回答1:跟數(shù)據(jù)似乎關(guān)系不大,我覺得Runnable有兩個好處:
實現(xiàn)Runnable以后,即可以開個線程跑(一般是用executorService.exec(command),挫一點也可以用new Thread(command).start()),也可以不開線程阻塞式的跑(直接調(diào)用command.run());
Java 1.8以后可以用Lambda來跑,例如:
new Thread(() -> { // Do something}).start();回答2:
Runnable的好處是各種場景都可以用,比如你可以讓任何一個Class implements Runnable,但是extends Thread就有一些限制,因為Java單繼承的原因,在有些場景下沒法用。
回答3:回答:
這個問題算是一個設(shè)計問題。
之所以將 Thread 和 Runnable 分開,是希望把線程的 '創(chuàng)建過程' 與線程的 '執(zhí)行邏輯' 徹底分開。
也就是說:線程的創(chuàng)建過程是“代碼”;線程的執(zhí)行邏輯是“數(shù)據(jù)”;
這聽起來有點叫人暈呼,不都是 JAVA 代碼么?怎么代碼又變成數(shù)據(jù)了呢?
我們不在這些概念上糾纏,我覺得可以倒轉(zhuǎn)過來思考這個問題,舉個例子來說明問題。
討論過程:
例如我要設(shè)計一個單線程程序,這個單線程需要完成兩個任務(wù):
1、打印一句 hello world;2、計算一下 int a 與 int b 兩個數(shù)的和并輸出;
注意:到底是執(zhí)行 1? 還是 2?是由參數(shù) n 來決定的,n 是一個隨機數(shù)……
為了讓這兩個任務(wù)在同一個線程里執(zhí)行,我們可以寫這樣的代碼:
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();
上面的代碼確實是可以完成任務(wù)的,但問題是我們把線程的 '創(chuàng)建過程' 和 '業(yè)務(wù)邏輯' 混淆在一起了……
這樣不太妙。順便說一句,從操作系統(tǒng)層面來看,線程的創(chuàng)建過程其實是非常復(fù)雜的!
Java 語言把這種復(fù)雜性都封裝得看不見了,雖然代碼上就是一個 Thread 類,調(diào)用起來似乎也沒什么門檻,但 Thread 的創(chuàng)建過程還是很復(fù)雜、很消耗資源的。
言歸正傳,現(xiàn)在我再次加入一個小小的需求,除了前面的 1、2,我再加入一個 3,顯示一下系統(tǒng)當(dāng)前時間戳。
于是任務(wù)變成了:1、打印一句 hello world;2、計算一下 int a 與 int b 兩個數(shù)的和并輸出;3、顯示一下系統(tǒng)當(dāng)前時間戳;
注意,這時候我們需要修改 Thread 的創(chuàng)建過程,也就是修改 start 函數(shù):
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();
討論至此,讓我們仔細(xì)觀察觀察……其實:
Thread t = new Thread() { @Override public void start() {// ... }}
這部分代碼是不變的,只有 start 函數(shù)里面的代碼是隨著需求變化而修改的。
那么我們可不可以把這部分變化的內(nèi)容包裝成一個接口??
這應(yīng)該是一個不錯的主意!
Thread t = new Thread() { private Runnable runnable; // 這里定義一個 Runnable 類型的成員 @Override public void start() {if (null != this.runnable) { runnable.run(); // 在這里用接口來把頻繁變化的業(yè)務(wù)邏輯從線程代碼里給拿出去,只調(diào)用 run 函數(shù)} }}
到這里不知道你是否已經(jīng)完全明白了? :D
哈哈,Java 的 Thread 類不是剛好提供了一個帶有 Runnable 參數(shù)的構(gòu)造器么?
我們將業(yè)務(wù)代碼被放到 Runnable 接口的實現(xiàn)類里:
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()); // 這里是新增的語句} }}
那么最后,我們可以這么調(diào)用:
Thread t = new Thread(new BizLogicRun());t.start();
這樣就完成了線程的 '創(chuàng)建過程' 和 '業(yè)務(wù)邏輯' 徹底拆分!這種 '拆分' 也為 Java 線程池(Thread Pool)技術(shù)做好了鋪墊。
說實話,示例代碼中的 Thread t = new Thread() { ... } 這個還是夠簡單的,但在線程池中創(chuàng)建 Thread 可就沒這么簡單了。
所以 '拆分' 是非常有必要的!
另外,我們是否可以想象:
class PoolRun implements Runnable { List<Runnable> runnableList;}
如果 Runable 實現(xiàn)類里面,夾帶的還是一個 Runnable 列表會怎么樣呢?
總結(jié):
1、使用 Runnable 接口的目的是把線程的 '創(chuàng)建過程' 與線程的 '執(zhí)行邏輯' 徹底分開;2、Thread 不能共享資源,Runnable 能共享資源,這個說法是不正確的;3、在討論過程中我們是從具體到抽象;4、我在例子中給出的代碼確實比較簡單,但希望能說明白問題;
好了,以上就是我對這個問題的回答,希望對你有所幫助。
相關(guān)文章:
1. 為什么python中實例檢查推薦使用isinstance而不是type?2. python - (初學(xué)者)代碼運行不起來,求指導(dǎo),謝謝!3. 老師您的微信號是多少?4. nginx - pip install python庫報錯5. mysql如何添加索引的時候指定索引方式6. python - django orm 過濾日期為當(dāng)天日期的數(shù)據(jù)7. mysql - 5千萬文章,怎么做相關(guān)文章?8. python - 如何判斷字符串為企業(yè)注冊名稱9. mysql里的大表用mycat做水平拆分,是不是要先手動分好,再配置mycat10. window下mysql中文亂碼怎么解決??
