簡單闡釋進程和線程
對於進程最直觀的感受應該就是“windows任務管理器”中的進程管理:

(計算機原理課上的記憶已經快要模糊了,簡單理解一下):一個進程就是一個“執行中的程序”,是程序在計算機上的一次運行活動。程序要運行,系統就在內存中為該程序分配一塊獨立的內存空間,載入程序代碼和資源進行執行。程序運行期間該內存空間不能被其他進程直接訪問。系統以進程為基本單位進行系統資源的調度和分配。何為線程?線程是進程內一次具體的執行任務。程序的執行具體是通過線程來完成的,所以一個進程中至少有一個線程。回憶一下 HelloWrold 程序中main方法的執行,其實這時候,Java虛擬機會開啟一個名為“main”的線程來執行程序代碼。一個進程可以包含多個線程,這些線程共享數據空間和資源,但又分別擁有各自的執行堆棧和程序計數器。線程是CPU調度的基本單位。

多線程
一個進程包含了多個線程,自然就叫做多線程。擁有多個線程就可以讓程序看起來可以“同時”處理多個任務,為什么是看起來呢?因為CPU也分身乏術,只能讓你這個線程執行一會兒,好了你歇着,再讓另一個線程執行一會兒,下次輪到你的時候你再繼續執行。這里的“一會兒”實際上時間非常短,感覺上就是多個任務“同時”在執行。CPU就這樣不停的切來切去…既然CPU一次也只能執行一個線程,為什么要使用多線程呢?當然是為了充分利用CPU資源。一個線程執行過程中不可能每時每刻都在占用CPU,CPU歇着的時候我們就可以讓它切過來執行其他的線程。
比如QQ聊天的時候,跟一個人正聊着呢,另一個消息過來了。如果是單線程,不好意思,等我跟這一個聊完說拜拜之后再去理你吧。多線程呢,消息窗口全打開,這個窗口說完話了,總得等人家回吧,趁這個空閑時候,處理另一個窗口的消息。這樣看起來不就是同時進行了么,每一個窗口的另一邊都以為你只在跟他一個人聊天…
Java中的多線程
Java中啟用多線程有兩種方式:①繼承Thread類;②實現Runnable接口。
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of
Thread. This subclass should override therunmethod of classThread. An instance of the subclass can then be allocated and started.The other way to create a thread is to declare a class that implements theRunnableinterface. That class then implements therunmethod. An instance of the class can then be allocated, passed as an argument when creatingThread, and started.
繼承Thread類
創建一個類,繼承java.lang.Thread,並覆寫Thread類的run()方法,該類的實例就可以作為一個線程對象被開啟。
/** * Dog類,繼承了Thread類 * @author lt */ class Dog extends Thread { /* * 覆寫run()方法,定義該線程需要執行的代碼 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } }
線程創建好了,怎么讓它作為程序的一個獨立的線程被執行呢?創建一個該類的實例,並調用start()方法,將開啟一個線程,並執行線程類中覆寫的run()方法。
public class ThreadDemo { public static void main(String[] args) { Dog dog = new Dog(); dog.start(); } }

看不出什么端倪,如果我們直接調用實例的run()方法,執行效果是完全一樣的,見上圖。
public class ThreadDemo { public static void main(String[] args) { Dog dog = new Dog(); dog.run(); } }
如果一切正常,這時候程序中應該有兩個線程:一個主線程main,一個新開啟的線程。run()方法中的代碼究竟是哪個線程執行的呢?Java程序中,一個線程開啟會被分配一個線程名:Thread-x,x從0開始。我們可以打印當前線程的線程名,來看看究竟是誰在執行代碼。
class Dog extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + i); } } } public class ThreadDemo { public static void main(String[] args) { System.out.println("當前線程:" + Thread.currentThread().getName()); Dog dog = new Dog(); dog.start(); } }

可以看到,確實開啟了一個新的線程:Thread-0,main()方法的線程名就叫main。
同一個實例只能調用一次start()方法開啟一次,多次開啟,將報java.lang.IllegalThreadStateException異常:

我們再創建一個實例,開啟第三個線程:
public class ThreadDemo { public static void main(String[] args) { System.out.println("當前線程:" + Thread.currentThread().getName()); Dog dog = new Dog(); Dog dog2 = new Dog(); dog.start(); dog2.start(); } }

這時候我們已經能夠看到多線程的底層實現原理:CPU切換處理、交替執行的效果了。
run和start
上面我們直接調用run()方法和調用start()方法的結果一樣,現在我們在打印線程名的情況下再來看看:
public class ThreadDemo { public static void main(String[] args) { System.out.println("當前線程:" + Thread.currentThread().getName()); Dog dog = new Dog(); Dog dog2 = new Dog(); dog.run(); dog2.run(); } }

可以看到,這時候並沒有開啟新的線程,main線程直接調用執行了run()方法中的代碼。所以start()方法會開啟新的線程並在新的線程中執行run()方法中的代碼,而run()方法不會開啟線程。查看start()的源代碼,該方法調用了本地方法 private native void start0();即調用的是操作系統的底層函數:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); 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 */ } } } private native void start0();
實現Runnable接口
第二種方式,實現Runnable接口,並覆寫接口中的run()方法,這是推薦的也是最常用的方式。Runnable接口定義非常簡單,就只有一個抽象的run()方法。
//Runnable接口源碼 public interface Runnable { public abstract void run(); }
class Dog implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("當前線程:" + Thread.currentThread().getName() + "---" + i); } } }
這時候的Dog類看起來跟線程什么的毫無關系,也沒有了start()方法,怎么樣開啟一個新的線程呢?直接調用run()方法?想想也不行。這時候我們需要將一個Dog類的實例,作為Thread類的構造函數的參數傳入,來創建一個Thread類的實例,並通過該Thread類的實例來調用start()方法從而開啟線程。
public class ThreadDemo { public static void main(String[] args) { System.out.println("當前線程:" + Thread.currentThread().getName()); Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); } }

這時候如果要開啟第三個線程,需要創建一個新的Thread類的實例,同時傳入剛才的Dog類的實例(當然也可以創建一個新的Dog實例)。這時候我們就可以看到跟繼承Thread類的方式的區別:多個線程可以共享同一個Dog類的實例。
public class ThreadDemo { public static void main(String[] args) { System.out.println("當前線程:" + Thread.currentThread().getName()); Dog dog = new Dog(); Thread thread = new Thread(dog); Thread thread2 = new Thread(dog); thread.start(); thread2.start(); } }
兩種方式的比較
繼承Thread類的方式有它固有的弊端,因為Java中繼承的單一性,繼承了Thread類就不能繼承其他類了;同時也不符合繼承的語義,Dog跟Thread沒有直接的父子關系,繼承Thread只是為了能擁有一些功能特性。而實現Runnable接口,①避免了單一繼承的局限性,②同時更符合面向對象的編程方式,即將線程對象進行單獨的封裝,③而且實現接口的方式降低了線程對象(Dog)和線程任務(run方法中的代碼)的耦合性,④如上面所述,可以使用同一個Dog類的實例來創建並開啟多個線程,非常方便的實現資源的共享。實際上Thread類也是實現了Runnable接口。實際開發中多是使用實現Runnable接口的方式。
