Java中的多線程讓我們的程式可以同時運行多個任務,即使我們的CPU是單核的。當然我們都明白這種情況下的同時運行,並不是真正的同時運行,而是JVM中的線程調度器根據時間片輪轉的方式快速的在不同線程間的切換。線程調度器讓JVM一會運行這個線程,一會運行那個線程,切換的速度很快便我們產生了這些線程好像同時啟動並執行假象。
線程的建立方式有兩種,這兩種方式究竟有什麼不同?效能有什麼差異?為什麼要設計兩種方式呢?帶著這些疑問,我們首先回顧一下線程的兩種建立方式。
第一種方式,通過實現Runnable介面來建立一個線程:
public class StudyThread{public static void main(String[] args){MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}}class MyRunnable implements Runnable{public void run(){System.out.println("第一種建立方式");}}
第二種方式,通過繼承Thread類來建立一個線程:
public class StudyThreadTwo{public static void main(String[] args){MyThread myThread = new MyThread();myThread.start();}}class MyThread extends Thread{public void run(){System.out.println("第二種啟動方式");}}
通過這兩種方式,都能正常建立一個新的線程,效能方面我覺得應該沒有多大差異,我們來從設計的角度看一下它們究竟有什麼不同。
第一種建立方式是最經常被使用的建立方式。它建立一個線程,需要兩個相關類,一個Java提供的Thread類,另一個是要我們自己實現的:實現Runnable介面,並實現run方法。我們要做的就是建立一個Thread對象,在建立時需要一個Runnable類型的參數是需要我們自己實現的。在實際中我們通常使用匿名內部類來實現這種方式,具體如下:
public class StudyThread{public static void main(String[] args){Thread thread = new Thread(new Runnable(){public void run(){System.out.println("使用匿名內部類建立線程");}});thread.start();}}
第二種建立方式只需要一個類,就是我們繼承自Thread類,然後重寫run方法。這種建立方式相比第一種彷彿更簡單。實際上這種簡單是有代價的,如果我們從設計的角度(設計原則、設計模式)來考慮的話,就會明白我們為什麼在實際中很少用這種方式。
根據設計原則中的單一職責原則,我們的類應該僅實現一類功能,應該有且僅有一個原因引起類的變更,應該讓介面顯示的類只負責介面顯示,資料處理只負責資料處理,控制的只負責控制,最合適的例子就是MVC這種複合設計模式。我們再回到上面的分析,為了便於理解,我們可以將Thread類看做一個工人(a worker),Runnable(中的run方法)就是這個工人的工作(work),在第一種建立方式中,工人是工人,工作是工作(松耦合),工作(Runnable)的變更不會影響到工人(Thread);而在第二種建立方式中,工人與工作綁定死了(緊耦合),工作一變,工人也跟著變,這種牽一髮而動全身的編程方式,在簡單的工程中還好說,如果在一個及其龐大的項目中的話,如果你的一個地方的修改要引起整個系統的更改的話,那你與你的項目會一起崩潰的。
既然第二種相比第一種有如此弊端,為什麼還要設計第一種呢?我覺得我們可以從繼承的角度來理解。繼承可以讓子類自動共用父類的資料和方法,並允許子類添加新的資料或者方法。我們有時就是需要一個工作與工人綁定的,並如果被大量使用的,第二種方式就有優點了。不知道這種理解方式合適與否,歡迎大家積极參与討論。