多線程編程學習三(線程間通信).


一、概要

    線程是操作系統中獨立的個體,但這些個體如果不經過特殊的處理就不能成為一個整體,線程間的通信就是成為整體的必用方案之一。可以說,使線程進行通信后,系統之間的交互性會更強大,在大大提高cpu利用率的同時還會使程序員對各線程任務在處理過程中進行有效的把控和監督。

二、等待/通知機制

1、"wait/notify"機制:等待/通知機制,wait使線程暫停運行,而notify 使暫停的線程繼續運行。用一個廚師和服務員的交互來說明:

(1) 服務員取到菜的時間取決於廚師,所以服務員就有“等待”(wait)的狀態。

(2) 廚師將菜放在“菜品傳遞台”上,其實就相當於一種通知(notify),這時服務員才可以拿到菜並交給就餐者。

2、wait()

(1) 使當前執行代碼的線程進行等待。wait()方法是Object類的方法,該方法用來將當前線程置入“預執行隊列”中,並且在wait()所在的代碼行處停止執行,直到接收通知或被中斷為止。

(2) 在調用wait()方法之前,線程必須獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調用wait()方法,否則拋出IllegalMonitorStateException異常。(屬於Runtime的一個子類,不需要try-catch 語句進行捕捉異常)

tips:為什么調用wait()方法一定要加鎖呢?首先:wait()和notify() 都是屬於Object的方法,並不是屬於Thread的方法,只會辨識到Object類,並不會辨識到Thread類 。其二:wait()方法必須有noyify()方法匹配才能實現線程間通信。那么!同屬於Object類的notify()方法怎么知道要喚醒哪個wait()呢?就是通過鎖機制來判斷的啦!否則,一個notifyAll()豈不是把所有等待的wait()線程,即使是不同鎖的喚醒了?

(3) 在調用wait()方法之后,當前線程釋放鎖,而此對象會進入線程等待池中,等待被喚醒。在從wait()返回前,線程與其他呈wait線程競爭重新獲得鎖。

(4) wait()方法可以被interrupt 打斷並拋出InterruptedException。

(5) wait(long):帶一個參數的wait(long)方法的功能是等待某一時間內是否有線程對鎖進行喚醒,如果超過這個時間則自動喚醒。

3、notify()

(1) 用來通知那些可能等待該對象的對象鎖的其他線程。如果有多個線程等待,則由線程規划器隨機挑選出其中一個呈wait狀態的線程,對其發出通知notify,並使它等待獲取該對象的對象鎖。(注意!這里說的是等待,即在執行完notify()方法后,當前線程不會馬上釋放該對象鎖,即wait()狀態的線程也不會馬上獲得對象鎖,需要將synchronized 代碼塊中的代碼執行完后才釋放鎖!)

(2)也要在同步方法或同步塊中調用,即在調用前,線程也必須獲得該對象的對象級別鎖,否則也會拋出IllegalMonitorStateException.

(3)當notify()發出通知,卻沒有wait()線程在等待時,則不作作用。

4、notifyAll()

(1) 可以使所有正在等待隊列中的 等待同一共享資源(即同一個鎖) 的"全部"線程從等待狀態退出,進入可運行狀態。

5、

6、假死:“假死”現象其實就是線程進入WAITING等待狀態。如果全部線程都進入WAITING狀態,則程序就不再執行任何功能了,整個項目呈停止狀態。 出現這樣的原因是因為:比如多個生產者和多個消費者的問題,“生產者”可能喚醒“生產者”,“消費者”可能喚醒“消費者”,喚醒了同類,導致線程不斷在等待。怎么解決這個問題呢?將notify() 改成 notifyAll()方法即可,也就是將異類一同喚醒就可以了。

7、 Jave中 管道流(pipeStream)是一種特殊的流,可用於在不同的線程中直接傳送數據。一個線程發送數據到輸出管道,另一個線程從輸入管道中讀數據。通過使用管道,實現不同線程間的通信,而無須借助於類似臨時文件之類的東西。JDK中提供了四個類來使線程間可以通信,其中包括字節流(PipedOutputStream、PipedInputStream)和字符流(PipedWriter、PipedReader)。

public class Piped {

    public static void main(String[] args) throws Exception {
        // 定義成字節流也可以
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        // 將輸出流和輸入流進行連接,否則在使用時會拋出IOException
        out.connect(in);
        Thread printThread = new Thread(new Print(in), "PrintThread");
        printThread.start();
        int receive;
        try {
            while ((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }
    }

    static class Print implements Runnable {
        private PipedReader in;

        public Print(PipedReader in) {
            this.in = in;
        }

        @Override
        public void run() {
            int receive;
            try {
                while ((receive = in.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException ex) {
            }
        }
    }
}

三、方法join的使用

1、在很多情況下,主線程創建並啟動子線程,如果子線程中要進行大量的耗時計算,主線程往往將早於子線程結束之前結束。這時,如果主線程想等待子線程執行完成之后再結束,比如子線程處理一個數據,主線程要取得這個數據中的值,就要用到 join() 方法了。

2、join() 的作用是等待線程銷毀,而使當前線程進行無限期的阻塞,等待 join() 的線程銷毀后再繼續執行當前線程的代碼。

3、同樣的,join() 方法可以被 interrupt() 方法打斷並拋出 InterruptedException 異常。

4、join() 方法是 wait/notify 范式的簡潔應用。當子線程調用 join() 時,主線程執行 wait() 方法進入等待,釋放鎖。子線程得到鎖,執行結束后調用 notifyAll() 方法,釋放鎖。主線程接着執行。

5、join 與 synchronized 的區別?

(1) join()在內部使用wait() 方法進行等待。

(2) synchronized 關鍵字使用的是“對象監視器”原理作為同步。

6、方法 join(long) 和 sleep(long)的區別?

(1) join(long)內部采用wait(long)方法實現,當執行wait(long)方法后,當前線程的鎖被釋放,那么其他線程也可以調用此線程中的同步方法了。即 join(long)之后,該線程釋放鎖,又需要和其他線程去爭搶鎖的資源。

(2) Thread.sleep(long)方法不釋放鎖。

四、類 ThreadLocal 的使用

1、變量值的共享可以使用public static 變量的形式,所有的線程都使用同一個public static 變量。如果想實現每一個線程都有自己的共享變量該如何解決呢?類ThreadLocal解決的就是每個線程綁定自己的值,可以將ThreadLocal類比喻成全局存放數據的盒子,盒子中可以存放每個線程的私有數據。

2、類ThreadLocal 具有隔離性,即每個線程都可以存入自己線程的數據而互不影響,而取到的也是自己線程存入的數據。

 

五、類InheritableThreadLocal的使用

1、InheritableThreadLocal類繼承於ThreadLocal類,所以它具有ThreadLocal類的特性,但又是一種特殊的ThreadLocal,其特殊性在於InheritableThreadLocal變量值會自動傳遞給所有子線程,而普通ThreadLocal變量不行;而且,通過重寫這個類中的 childValue 方法,子線程的值可以作為父線程值的一個任意函數。

備注:

(1)  什么是子線程? 

包含在 Thread thread = new Thread(new ThreadStart(delegate{ 
}));里面均視為子線程。(個人理解)

(2) 什么是主線程? 

UI界面和Main函數均為主線程,除了“不包含在Thread里面的程序”均可視為主線程。(個人理解)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM