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();
於是任務變成了: 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();
跟數據似乎關係不大,我覺得Runnable有兩個好處:
實作Runnable以後,也就是可以開個執行緒跑(一般是用
executorService.exec(command)
,挫一點也可以用new Thread(command).start()
),也可以不開線程阻塞式的跑(直接呼叫command.run()
);Java 1.8以後可以用Lambda來跑,例如:
Runnable
的好處是各種場景都可以用,例如你可以讓任何一個Class implements Runnable
,但是extends Thread
就有一些限制,因為Java單繼承的原因,在有些場景下沒用。回答:
這個問題算是設計問題。
之所以將 Thread 和 Runnable 分開,是希望把線程的 "創建過程" 與線程的 "執行邏輯" 徹底分開。
也就是說:
執行緒的創建過程是「程式碼」;
執行緒的執行邏輯是「資料」;
這聽起來有點叫人暈呼,不都是 JAVA 代碼麼?怎麼程式碼又變成資料了呢?
我們不在這些概念上糾纏,我覺得可以倒轉過來思考這個問題,舉個例子來說明問題。
討論過程:
例如我要設計一個單執行緒程序,這個單執行緒需要完成兩個任務:
1、印一句 hello world;
2、計算一下 int a 與 int b 兩個數的和並輸出;
注意:到底是執行 1? 還是 2?是由參數 n 決定的,n 是隨機數…
為了讓這兩個任務在同一個執行緒執行,我們可以寫這樣的程式碼:
上面的程式碼確實是可以完成任務的,但問題是我們把線程的 "創建過程" 和 "業務邏輯" 混淆在一起了…
這樣不太妙。順便說一句,從作業系統層面來看,執行緒的創建過程其實是非常複雜的!
Java 語言把這種複雜性都封裝得看不見了,雖然程式碼上就是一個 Thread 類,呼叫起來似乎也沒什麼門檻,但 Thread 的創建過程還是很複雜、很消耗資源的。
言歸正傳,現在我再次加入一個小小的需求,除了前面的 1、2,我再加入一個 3,顯示一下系統當前時間戳。
於是任務變成了:
1、打印一句 hello world;
2、計算一下 int a 與 int b 兩個數的和並輸出;
3、顯示一下系統當前時間戳;
注意,這時候我們需要修改 Thread 的創建過程,也就是修改 start 函數:
討論至此,讓我們仔細觀察觀察…其實:
這部分程式碼是不變的,只有 start 函數裡面的程式碼是隨著需求變化而修改的。
那我們可不可以把這部分變化的內容包裝成一個介面? ?
這應該是個不錯的主意!
到這裡不知道你是否已經完全明白了? :D
哈哈,Java 的 Thread 類別不是剛好提供了一個帶有 Runnable 參數的建構器麼?
我們將業務程式碼被放到 Runnable 介面的實作類別裡:
那麼最後,我們可以這麼呼叫:
這樣就完成了線程的 "創建過程" 和 "業務邏輯" 徹底拆分!這種 "分割" 也為 Java 執行緒池(Thread Pool)技術做好了鋪墊。
說實話,範例程式碼中的 Thread t = new Thread() { ... } 這個還是夠簡單的,但在執行緒池中建立 Thread 可就沒這麼簡單了。
所以 "拆分" 是非常必要的!
另外,我們是否可以想像:
如果 Runable 實作類別裡面,夾帶的還是一個 Runnable 清單會怎麼樣呢?
總結:
1、使用Runnable 介面的目的是把線程的"創建過程" 與線程的"執行邏輯" 徹底分開;
2、Thread 不能共享資源,Runnable 能共享資源,這個說法是不正確的;
3、在討論過程中我們是從具體到抽象;
4、我在例子中給出的程式碼確實比較簡單,但希望能說明白問題;
好了,以上就是我對這個問題的回答,希望對你有幫助。