Java進階05 多線程Python多線程與同步Linux多線程與同步

來源:互聯網
上載者:User

作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝! 

 

多線程

多線程(multiple thread)是電腦實現多任務平行處理的一種方式。

在單線程情況下,電腦中存在一個控制權,並按照順序依次執行指令。單線程好像是一個只有一個隊長指揮的小隊,整個小隊同一個時間只能執行一個任務。

單線程

 

在多線程情境下,電腦中有多個控制權。多個控制權可以同時進行,每個控制權依次執行一系列的指令。多線程好像是一個小隊中的成員同時執行不同的任務。

可參考Linux多線程與同步,並對比Python多線程與同步

多線程

傳統意義上,多線程是由作業系統提供的功能。對於單核的CPU,硬體中只存在一個線程。在作業系統的控制下,CPU會在不同的任務間(線程間)切換,從而造成多任務齊頭並進的效果。這是單CPU分時複用機制下的多線程。現在,隨著新的硬體技術的發展,硬體本身開始提供多線程支援,比如多核和超執行緒技術。然而,硬體的多線程還是要接受作業系統的統一管理。在作業系統之上的多線程程式依然通用。

多個線程可以並存於同一個進程空間。在JVM的一個進程空間中,一個棧(stack)代表了方法調用的次序。對於多線程來說,進程空間中需要有多個棧,以記錄不同線程的調用次序。多個棧互不影響,但所有的線程將共用堆(heap)中的對象。

 

建立線程

Java中“一切皆對象”,線程也被封裝成一個對象。我們可以通過繼承Thread類來建立線程。線程類中的的run()方法包含了該線程應該執行的指令。我們在衍生類中覆蓋該方法,以便向線程說明要做的任務:

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,再對周邊的運算式求值

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

實現多線程的另一個方式是實施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);    }}

 

synchronized

多任務編程的痛點在於多任務共用資源。對於同一個進程空間中的多個線程來說,它們都共用堆中的對象。某個線程對對象的操作,將影響到其它的線程。

在多線程編程中,要儘力避免競爭條件(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對象中的計數器,來同步花括弧中的代碼。

 

歡迎繼續閱讀“Java快速教程”系列文章

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.