多執行緒程式設計,處理多執行緒的併發問題(執行緒池)

一江夜雨發表於2018-03-01
        執行緒物件是可以產生執行緒的物件。比如在 Java 平臺中Thread物件,Runnable物件。執行緒,是指正在執行的一個指點令序列。在java平臺上是指從一個執行緒物件的start()開始,執行run方法體中的那一段相對獨立的過程。相比於多程式,多執行緒的優勢有:
    (1)程式之間不能共享資料,執行緒可以;
    (2)系統建立程式需要為該程式重新分配系統資源,故建立執行緒代價比較小;
    (3)Java語言內建了多執行緒功能支援,簡化了java多執行緒程式設計。
  1、執行緒實現方式:
             * 第一種方式:
             *         1.自定義類,繼承Thread;
             *         2.重寫run( )方法;
             *         3.啟動執行緒:
             *             1).例項化自定義執行緒物件;
             *             2).呼叫自定義執行緒物件的start()方法啟動執行緒;
             *
             * 第二種方式:
             *         1.自定義類,實現Runnable介面;
             *         2.重寫run( )方法:
             *         3.啟動執行緒:
             *             1).例項化自定義的Runnable子類物件;
             *             2).例項化一個Thread物件,將自定義物件作為引數傳給Thread的構造方法;
             *             3).呼叫Thread物件的start()啟動執行緒;

                區別:
                        一種是擴充套件java.lang.Thread類      
                        另一種是實現java.lang.Runnable介面
                好處:
                        在實際開發中通常以實現Runnable介面為主,因為實現Runnable介面相比繼承Thread類可以避免繼承的侷限,一個類可以繼承多個介面,適合於資源的共享。

            執行緒和程式的區別:
                  程式:
                       每個程式都有獨立的程式碼和資料空間(程式上下文),程式間的切換會有較大的開銷,
                        一個程式包含1--n個執行緒。
                      (程式是資源分配的最小單位)。
                執行緒:
                        同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小。
                      (執行緒是cpu排程的最小單位)。

        2、多執行緒
            好處:
                1.可以充分利用CPU資源;
                2.對於執行緒中程式碼,可以獨立於"主執行緒"單獨執行,它不用等待前面的程式碼執行完畢;
                   也就意味著,我們的程式可以同時做多件事情;

            問題:
                1.多執行緒有幾種實現方案,分別是哪幾種?
                     *  三種:
                     *     1.繼承Thread,重寫run()方法;
                     *     2.實現Runnable介面,重寫run()方法;
                     *     3.(JDK5)實現Callable介面,重寫call()方法;
                     *
                    
                3.啟動一個執行緒是run()還是start()?它們的區別?
                    1).start()啟動執行緒;
                    2).run是普通方法,我們把執行緒中需要做的事情寫在這裡;
                       start方法用於啟動執行緒,它會呼叫run()方法;
                       
                4.sleep( )和wait( )方法的區別:
                    1).sleep:
                        a.屬於Thread類;
                        b.讓當前執行緒休眠指定的時間,當時間到,會自動醒來;
                        c.在同步方法中,不會釋放鎖;
                    2).wait:
                        a.屬於Object類;
                        b.可以指定時間,也可以不指定;當時間到時,或者使用notify()或notifyAll()方法後,會醒來;
                        c.在同步方法中,會釋放鎖;
                        
                5.為什麼wait( ),notify( ),notifyAll( )等方法都定義在Object類中:
                    1).因為任何物件都有可能被多執行緒併發訪問,所以,任何物件都有讓當前訪問的執行緒"等待"的權利,也有需要"喚醒"的義務;
                    
                6.執行緒的生命週期
                    新建:
                          用new關鍵字和Thread類或其子類建立一個執行緒物件後,該執行緒物件就處於新生狀態。
                          處於新生狀態的執行緒有自己的記憶體空間,通過呼叫start方法進入就緒狀態(runnable)。
                    就緒:
                         處於就緒狀態的執行緒已經具備了執行條件,但還沒有分配到CPU,處於執行緒就緒佇列,
                         等待系統為其分配CPU。
                         一旦獲得CPU,執行緒就進入執行狀態並自動呼叫自己的run方法。
                    執行:
                         處於執行狀態的執行緒最為複雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
                    阻塞:
                         處於執行狀態的執行緒在某些情況下,如執行了sleep(睡眠)方法,或等待IO裝置等資源,
                         將讓出CPU並暫時停止自己的執行,進入阻塞狀態。在阻塞狀態的執行緒不能進入就緒佇列。
                    死亡:
                        當執行緒的run()方法執行完,或者被強制性地終止,就認為它死去。
                        
                7."並行"與"併發":
                      1)."並行":是指多個執行緒在"某個時間段內",在同時執行;
                      2)."併發":是指多個執行緒在"某個時間點上",同時訪問一個共享資源;

        3、併發
             定義:
                   是指多個執行緒在"某個時間點上",同時訪問一個共享資源;

             同步的方式:
                    1.synchronized:
                            1).同步程式碼塊;
                                       即有synchronized關鍵字修飾的語句塊。 
                                       被該關鍵字修飾的語句塊會自動被加上內建鎖,從而實現同步 
                                       注:
                                              同步是一種高開銷的操作,因此應該儘量減少同步的內容。 
                                              通常沒有必要同步整個方法,使用synchronized程式碼塊同步關鍵程式碼即可。
 
                            2).同步方法;
                                      即有synchronized關鍵字修飾的方法。 
                                      由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時, 
                                      內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。
                  2.(JDK5) ReentrantLock鎖:
                             在JavaSE5.0中新增了一個java.util.concurrent包來支援同步。
                             ReentrantLock類是可重入、互斥、實現了Lock介面的鎖, 
                             它與使用synchronized方法和快具有相同的基本行為和語義,並且擴充套件了其能力。
                       常用方法有:
                                 ReentrantLock() : 建立一個ReentrantLock例項 
                                 lock() : 獲得鎖 
                                 unlock() : 釋放鎖 
                       注意:
                            ReentrantLock()還有一個可以建立公平鎖的構造方法,但由於能大幅度降低程式執行效率,不推薦使用 
                       例子:              
                            // 存錢
                            public void addMoney(int money) {
                                    lock.lock();//上鎖
                            try{
                                count += money;
                                System.out.println(System.currentTimeMillis() + "存進:" + money);

                                }finally{
                                        lock.unlock();//解鎖 }
                                }
                   3.使用特殊域變數(volatile)實現執行緒同步      
                             a.volatile關鍵字為域變數的訪問提供了一種免鎖機制, 
                             b.使用volatile修飾域相當於告訴虛擬機器該域可能會被其他執行緒更新, 
                             c.因此每次使用該域就要重新計算,而不是使用暫存器中的值 
                             d.volatile不會提供任何原子操作,它也不能用來修飾final型別的變數 

                    好處:解決了併發性訪問的問題;
                    弊端:由於要處理執行緒的等待、阻塞等問題,所以效率會降低;    

        4、執行緒池
            1).獲取執行緒池物件:JDK5新增了一個Executors工廠類來產生執行緒池,有如下幾個方法:
                    public static ExecutorService newCachedThreadPool():建立一個可根據需要建立新執行緒的執行緒池
                    public static ExecutorService newFixedThreadPool(int nThreads):建立一個可重用固定執行緒數的執行緒池
                    public static ExecutorService newSingleThreadExecutor():建立一個使用單個 worker 執行緒的 Executor
            
            2).操作線城池:
                這些方法的返回值是ExecutorService物件,該物件表示一個執行緒池,可以執行Runnable物件或者Callable物件代表的執行緒。
                它提供瞭如下方法
                        Future<?> submit(Runnable task)
                        <T> Future<T> submit(Callable<T> task)

            3).使用執行緒池的好處
                      第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
                      第二:提高響應速度。當任務到達時,任務可以不需要的等到執行緒建立就能立即執行。
                      第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,
                               還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。
                               但是要做到合理的利用執行緒池,必須對其原理了如指掌。

             4).執行緒池的處理流程如下    
  1.                     首先執行緒池判斷基本執行緒池是否已滿?沒滿,建立一個工作執行緒來執行任務。滿了,則進入下個流程。
  2.                     其次執行緒池判斷工作佇列是否已滿?沒滿,則將新提交的任務儲存在工作佇列裡。滿了,則進入下個流程。
  3.                    最後執行緒池判斷整個執行緒池是否已滿?沒滿,則建立一個新的工作執行緒來執行任務,滿了,則交給飽和策略來處理這個任務。

                  5) .執行緒池的組成部分
                         一個比較簡單的執行緒池至少應包含執行緒池管理器、工作執行緒、任務列隊、任務介面等部分。
                        其中執行緒池管理器的作用是建立、銷燬並管理執行緒池,將工作執行緒放入執行緒池中。工作執行緒是一個可以迴圈執行任務的執行緒,在沒有任務是進行等待;任務列隊的作用是提供一種緩衝機制,將沒有處理的任務放在任務列隊中;任務介面是每個任務必須實現的介面,主要用來規定任務的入口、任務執行完後的收尾工作、任務的執行狀態等,工作執行緒通過該介面排程任務的執行。
                        執行緒池管理器至少有下列功能:建立執行緒池,銷燬執行緒池,新增新任務。
                        工作執行緒是一個可以迴圈執行任務的執行緒,在沒有任務時將等待。
                       任務介面是為所有任務提供統一的介面,以便工作執行緒處理。任務介面主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等。
                
               6). 執行緒的終止(shutdown、shutdownnow)
                     ExecutorService執行緒池就提供了shutdown和shutdownNow這樣的生命週期方法來關閉執行緒池自身以及它擁有的所有執行緒。  
                     shutdown:
                (1)執行緒池的狀態變成SHUTDOWN狀態,此時不能再往執行緒池中新增新的任務,否則會丟擲RejectedExecutionException異常。
                         (2)執行緒池不會立刻退出,直到新增到執行緒池中的任務都已經處理完成,才會退出
                            shutdown做了幾件事:
                                        1. 檢查是否能操作目標執行緒
                                        2. 將執行緒池狀態轉為SHUTDOWN
                                        3. 中斷所有空閒執行緒
                             
                      shutdownNow:
                         (1)執行緒池的狀態立刻變成STOP狀態,此時不能再往執行緒池中新增新的任務。
                         (2)終止等待執行的執行緒,並返回它們的列表;
                        (3)試圖停止所有正在執行的執行緒,試圖終止的方法是呼叫Thread.interrupt(),但是大家知道,
                                如果執行緒中沒有sleep 、wait、Condition、定時鎖等應用, interrupt()方法是無法中斷當前的執行緒的。
                                所以,ShutdownNow()並不代表執行緒池就一定立即就能退出,
                                它可能必須要等待所有正在執行的任務都執行完成了才能退出。

相關文章