多線程(multiple thread)是計算機實現多任務並行處理的一種方式。html
在單線程狀況下,計算機中存在一個控制權,並按照順序依次執行指令。單線程好像是一個只有一個隊長指揮的小隊,整個小隊同一個時間只能執行一個任務。java
單線程編程
在多線程情境下,計算機中有多個控制權。多個控制權能夠同時進行,每一個控制權依次執行一系列的指令。多線程好像是一個小隊中的成員同時執行不一樣的任務。api
可參考Linux多線程與同步,並對比Python多線程與同步多線程
多線程併發
傳統意義上,多線程是由操做系統提供的功能。對於單核的CPU,硬件中只存在一個線程。在操做系統的控制下,CPU會在不一樣的任務間(線程間)切換,從而形成多任務齊頭並進的效果。這是單CPU分時複用機制下的多線程。如今,隨着新的硬件技術的發展,硬件自己開始提供多線程支持,好比多核和超線程技術。然而,硬件的多線程仍是要接受操做系統的統一管理。在操做系統之上的多線程程序依然通用。oracle
多個線程能夠並存於同一個進程空間。在JVM的一個進程空間中,一個棧(stack)表明了方法調用的次序。對於多線程來講,進程空間中須要有多個棧,以記錄不一樣線程的調用次序。多個棧互不影響,但全部的線程將共享堆(heap)中的對象。dom
Java中「一切皆對象」,線程也被封裝成一個對象。咱們能夠經過繼承Thread類來建立線程。線程類中的的run()方法包含了該線程應該執行的指令。咱們在衍生類中覆蓋該方法,以便向線程說明要作的任務:post
public class Test { public static void main(String[] args) { NewThread thread1 = new NewThread(); NewThread thread2 = new NewThread(); thread1.start(); // start thread1 thread2.start(); // start thread2 } } /** * create new thread by inheriting Thread */ class NewThread extends Thread { private static int threadID = 0; // shared by all /** * constructor */ public NewThread() { super("ID:" + (++threadID)); } /** * convert object to string */ public String toString() { return super.getName(); } /** * what does the thread do? */ public void run() { System.out.println(this); } }
(++是Java中的累加運算符,即讓變量加1。這裏++出如今threadID以前,說明先將threadID加1,再對周邊的表達式求值this
toString是Object根類的方法,咱們經過覆蓋該方法,來將對象轉換成字符串。當咱們打印該對象時,Java將自動調用該方法。)
能夠看到,Thread基類的構建方法(super())能夠接收一個字符串做爲參數。該字符串是該線程的名字,並使用getName()返回。
定義類以後,咱們在main()方法中建立線程對象。每一個線程對象爲一個線程。建立線程對象後,線程尚未開始執行。
咱們調用線程對象的start()方法來啓動線程。start()方法能夠在構造方法中調用。這樣,咱們一旦使用new建立線程對象,就當即執行。
Thread類還提供了下面經常使用方法:
join(Thread tr) 等待線程tr完成
setDaemon() 設置當前線程爲後臺daemon (進程結束不受daemon線程的影響)
Thread類官方文檔: http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html
實現多線程的另外一個方式是實施Runnable接口,並提供run()方法。實施接口的好處是容易實現多重繼承(multiple inheritance)。然而,因爲內部類語法,繼承Thread建立線程能夠實現相似的功能。咱們在下面給出一個簡單的例子,而不深刻:
public class Test { public static void main(String[] args) { Thread thread1 = new Thread(new NewThread(), "first"); Thread thread2 = new Thread(new NewThread(), "second"); thread1.start(); // start thread1 thread2.start(); // start thread2 } } /** * create new thread by implementing Runnable */ class NewThread implements Runnable { /** * convert object to string */ public String toString() { return Thread.currentThread().getName(); } /** * what does the thread do? */ public void run() { System.out.println(this); } }
多任務編程的難點在於多任務共享資源。對於同一個進程空間中的多個線程來講,它們都共享堆中的對象。某個線程對對象的操做,將影響到其它的線程。
在多線程編程中,要盡力避免競爭條件(racing condition),即運行結果依賴於不一樣線程執行的前後。線程是併發執行的,沒法肯定線程的前後,因此咱們的程序中不該該出現競爭條件。
然而,當多任務共享資源時,就很容易形成競爭條件。咱們須要將共享資源,並形成競爭條件的多個線程線性化執行,即同一時間只容許一個線程執行。
(可更多參考Linux多線程與同步)
下面是一個售票程序。3個售票亭 (Booth)共同售賣100張票(Reservoir)。每一個售票亭要先判斷是否有餘票,而後再賣出一張票。若是隻剩下一張票,在一個售票亭的判斷和售 出兩個動做之間,另外一個售票亭賣出該票,那麼第一個售票亭(因爲已經執行過判斷)依然會齒形賣出,形成票的超賣。爲了解決該問題,判斷和售出兩個動做之間 不能有「空隙」。也就是說,在一個線程完成了這兩個動做以後,纔能有另外一個線程執行。
在Java中,咱們將共享的資源置於一個對象中,好比下面r(Reservoir)對象。它包含了總共的票數;將可能形成競爭條件的,針對共享資源的操做,放在synchronized(同步)方法中,好比下面的sellTicket()。synchronized是方法的修飾符。在Java中,同一對象的synchronized方法只能同時被一個線程調用。其餘線程必須等待該線程調用結束,(餘下的線程之一)才能運行。這樣,咱們就排除了競爭條件的可能。
在main()方法中,咱們將共享的資源(r對象)傳遞給多個線程:
public class Test { public static void main(String[] args) { Reservoir r = new Reservoir(100); Booth b1 = new Booth(r); Booth b2 = new Booth(r); Booth b3 = new Booth(r); } } /**
* contain shared resource
*/ class Reservoir { private int total; public Reservoir(int t) { this.total = t; } /** * Thread safe method * serialized access to Booth.total */ public synchronized boolean sellTicket() { if(this.total > 0) { this.total = this.total - 1; return true; // successfully sell one } else { return false; // no more tickets } } } /** * create new thread by inheriting Thread */ class Booth extends Thread { private static int threadID = 0; // owned by Class object private Reservoir release; // sell this reservoir private int count = 0; // owned by this thread object /** * constructor */ public Booth(Reservoir r) { super("ID:" + (++threadID)); this.release = r; // all threads share the same reservoir this.start(); } /** * convert object to string */ public String toString() { return super.getName(); } /** * what does the thread do? */ public void run() { while(true) { if(this.release.sellTicket()) { this.count = this.count + 1; System.out.println(this.getName() + ": sell 1"); try { sleep((int) Math.random()*100); // random intervals } catch (InterruptedException e) { throw new RuntimeException(e); } } else { break; } } System.out.println(this.getName() + " I sold:" + count); } }
(Math.random()用於產生隨機數)
Java的每一個對象都自動包含有一個用於支持同步的計數器,記錄synchronized方法的調用次數。線程得到該計數器,計數器加1,並執行synchronized方法。若是方法內部進一步調用了該對象的其餘synchronized方法,計數器加1。當synchronized方法調用結束並退出時,計數器減1。其餘線程若是也調用了同一對象的synchronized方法,必須等待該計數器變爲0,才能鎖定該計數器,開始執行。Java中的類一樣也是對象(Class類對象)。Class類對象也包含有計數器,用於同步。
上面,咱們利用synchronized修飾符同步了整個方法。咱們能夠同步部分代碼,而不是整個方法。這樣的代碼被稱爲關鍵代碼(critical section)。咱們使用下面的語法:
synchronized (syncObj) { ...; }
花括號中包含的是想要同步的代碼,syncObj是任意對象。咱們將使用syncObj對象中的計數器,來同步花括號中的代碼。