簡單闡釋進程和線程java
對於進程最直觀的感覺應該就是「windows任務管理器」中的進程管理:編程
(計算機原理課上的記憶已經快要模糊了,簡單理解一下):一個進程就是一個「執行中的程序」,是程序在計算機上的一次運行活動。程序要運行,系統就在內存中爲該程序分配一塊獨立的內存空間,載入程序代碼和資源進行執行。程序運行期間該內存空間不能被其餘進程直接訪問。系統以進程爲基本單位進行系統資源的調度和分配。何爲線程?線程是進程內一次具體的執行任務。程序的執行具體是經過線程來完成的,因此一個進程中至少有一個線程。回憶一下 HelloWrold 程序中main方法的執行,其實這時候,Java虛擬機會開啓一個名爲「main」的線程來執行程序代碼。一個進程能夠包含多個線程,這些線程共享數據空間和資源,但又分別擁有各自的執行堆棧和程序計數器。線程是CPU調度的基本單位。windows
多線程多線程
一個進程包含了多個線程,天然就叫作多線程。擁有多個線程就可讓程序看起來能夠「同時」處理多個任務,爲何是看起來呢?由於CPU也分身乏術,只能讓你這個線程執行一下子,好了你歇着,再讓另外一個線程執行一下子,下次輪到你的時候你再繼續執行。這裏的「一下子」實際上時間很是短,感受上就是多個任務「同時」在執行。CPU就這樣不停的切來切去…既然CPU一次也只能執行一個線程,爲何要使用多線程呢?固然是爲了充分利用CPU資源。一個線程執行過程當中不可能每時每刻都在佔用CPU,CPU歇着的時候咱們就可讓它切過來執行其餘的線程。ide
好比QQ聊天的時候,跟一我的正聊着呢,另外一個消息過來了。若是是單線程,很差意思,等我跟這一個聊完說拜拜以後再去理你吧。多線程呢,消息窗口全打開,這個窗口說完話了,總得等人家回吧,趁這個空閒時候,處理另外一個窗口的消息。這樣看起來不就是同時進行了麼,每個窗口的另外一邊都覺得你只在跟他一我的聊天…函數
Java中的多線程post
Java中啓用多線程有兩種方式:①繼承Thread類;②實現Runnable接口。this
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 therun
method 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 theRunnable
interface. That class then implements therun
method. An instance of the class can then be allocated, passed as an argument when creatingThread
, and started. spa
繼承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接口的方式。