Java 多線程開發詳解

jopen 10年前發布 | 48K 次閱讀 Java Java開發

1、線程概述
    幾乎所有的操作系統都支持同時運行多個任務,一個任務通常就是一個程序,每個運行中的程序就是一個進程。
    當一個程序運行時,內部可能包含了多個順序執行流,每個順序執行流就是一個線程。
2、線程和進程
    幾乎所有的操作系統都有進程的概念,所有運行中的任務通常對應一條進程。當一個程序進入內存運行,就是一個進程了。
    進程是處于運行中的程序,具有一定的獨立能力,進程是系統進行資源分配和調度的一個獨立單位。
  進程特征:
    A、獨立性:進程是系統中獨立存在的實體,可以擁有自己獨立的資源,每個進程都擁有自己的私有地址地址。
            在沒有經過進程本身允許的情況下,一個用戶進程不可以訪問其他進程地址空間。
    B、動態性:進程和程序的區別在于,程序只是一個靜態的指令集合,而進程是一個正在系統中活動的指令集合。
            在程序中加入了時間概念,進程具有自己的生命周期和各種不同的狀態,這些概念是程序不具備的。
    C、并發性:多個進程可以在單個處理器上并發執行,多個進程之間不會互相影響。
    多線程則擴展了多進程的概念,使得同一個進程可以同時并發處理多個任務。線程也被稱為輕量級進程(Lightweight Process),線程是進程的執行單元。就像進程在操作系統中的地位一樣,線程在程序中是獨立、并發執行流。當進程被初始化后,主線程就被創建。對于絕大多數應用程序來說,通常僅要一個主線程,但我們也可以在該進程內創建多條順序執行流,這些順序執行流就是線程,每條線程也互相獨立的。
    線程是進程的組成部分,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程可以擁有自己的堆、棧、程序計數器、局部變量,但不能擁有系統資源,
它與父進程的其他線程共享該進程所有的全部資源。因為多個線程共享父進程的全部資源。
    線程可以完成一定的任務,可與其他線程共享父進程中的變量和部分環境,相互之間協作共同完成進程所要完成的任務。
    線程是獨立運行的,它并不知道進程中是否還有其他進程存在。線程的執行是搶占方式的,也就是說,當前運行的線程在任何時候都可以被掛起,以便其他線程運行。一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程可以并發執行。
綜述:一個程序運行后至少有一個進程,一個進程可以包含多個線程。至少包含一個線程。   
3、并發和并行
    并發性(concurrency)和并行性(parallel)是兩個概念;
    并行指在同一時刻,有多條指令(線程)在多個處理器上同時執行;
    并發指在同一時刻只能有一個指令(線程)執行,但多個進程指令被快速輪換執行,使得宏觀上具有多個進程同時執行的效果。
4、多線程的優勢
    線程劃分尺度小于進程,使得多線程劃分的并發性高。進程在執行時有自己獨立的單元,多個線程共享內存,從而提高了運行效率。
    線程比進程具有更高的性能,這是由于同一個進程中的線程都有共性:多個線程將共享同一個進程的虛擬空間。
    線程共性的環境包括:進程代碼段、進程共有數據等。線程很容易就利用共性的數據進行通信。
    當操作系統創建一個進程時,必須給該進程分別獨立的內存空間,并分配大量相關的資源;但創建一個線程則簡單得多,因此多線程來實現并發要
比多進程實現并發的性能高得多。
多線程優點:
    A、進程之間不能共享內存,但線程之間共享內存非常容易。
    B、系統創建進程需要為該進程重新分配系統資源,但創建線程則代價要小得多,因此使用線程來實現多任務并發比多進程的效率高。
    C、Java語言內置多線程功能支持,而不是單純的作為底層操作系統的調度方式,從而簡化Java的多線程編程。
5、線程的創建和啟動
    A、繼承Thread類或實現Runnable接口,重寫或實現run方法,run方法代表線程要完成的任務
    B、創建Thread子類或是Runnable的實現類,即創建的線程對象;不同的是接口實現線程,
        需要將接口的實現類作為參數傳遞給Thread類的構造參數
    C、用線程對象的start方法啟動線程
6、繼承Thread和實現Runnable接口創建線程的區別
    采用Runnable接口實現線程:
    優勢:
        A、線程類只是實現了Runnable接口,還可以繼承其他的類
        B、在這種方式下,可以多個線程共享同一個目標對象,所以很合適多個線程來處理同一份資源的情況,
            從而可以將CPU、代碼和數據分開,形成清晰的模型,較好的面相對象思想。
    劣勢:編程稍微復雜,如果需要訪問當前線程需要用Thread.currentThread方法來獲取
    采用繼承Thread類的方式實現線程:
    優勢:編寫簡單,如果要獲得當前線程直接this即可
    劣勢:線程類繼承了Thread,不能在繼承其他類
    相對而言,用Runnable的方式更好,具體可以根據當前需要而定;
7、線程生命周期
    線程被創建啟動后,不并不是啟動后就進入了執行狀態,也不是一直處于的執行狀態。
    線程的生命周期分為創建(new)、就緒(Runnable)、運行(running)、阻塞(Blocked)、死亡(Dead)五種狀態。
    線程啟動后不會一直霸占CPU資源,所以CPU需要在多條線程中切換執行,線程就會在多次的運行和阻塞中切換。
8、新建(new)和就緒(Runnable)狀態
    當new一個線程后,該線程處于新建狀態,此時它和Java對象一樣,僅僅由Java虛擬機為其分配內存空間,并初始化成員變量。
    此時線程對象沒有表現出任何的動態特征,程序也不會執行線程的執行體。
    注意:run方法是線程的執行體,不能由我們手動調用。我們可以用start方法啟動線程,系統會把run方法當成線程的執行體來運行,
    如果直接調用線程對象run方法,則run方法立即會被運行。而且在run方法返回之前其他線程無法并行執行,
    也就是說系統會把當前線程類當成一個普通的Java對象,而run方法也是一個普通的方法,而不是線程的執行體。
9、運行(running)和阻塞(Blocked)狀態
    如果處于就緒狀態的線程就獲得了CPU,開始執行run方法的線程執行體,則該線程處于運行狀態。
    單CPU的機器,任何時刻只有一條線程處于運行狀態。當然,在多CPU機器上將會有多線程并行(parallel)執行,
    當線程大于CPU數量時,依然會在同一個CPU上切換執行。
    線程運行機制:一個線程運行后,它不可能一直處于運行狀態(除非它執行的時間很短,瞬間執行完成),線程在運行過程中需要中斷,
    目的是讓其他的線程有運行機會,線程的調度取決于底層的策略。對應搶占式的系統而言,系統會給每個可執行的線程一個小時間段來處理任務,
    當時間段到達系統就會剝奪該線程的資源,讓其他的線程有運行的機會。在選擇下一個線程時,系統會考慮線程優先級。
    以下情況會出現線程阻塞狀態:
        A、線程調用sleep方法,主動放棄占用的處理器資源
        B、線程調用了阻塞式IO方法,在該方法返回前,該線程被阻塞
        C、線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。
        D、線程等待某個通知(notify)
        E、程序調用了suspend方法將該線程掛起。不過這個方法容易導致死鎖,盡量不免使用該方法
    當線程被阻塞后,其他線程將有機會執行。被阻塞的線程會在合適的時候重新進入就緒狀態,注意是就緒狀態不是運行狀態。
    也就是被阻塞線程在阻塞解除后,必須重新等待線程調度器再次調用它。
    針對上面線程阻塞的情況,發生以下特定的情況可以解除阻塞,讓進程進入就緒狀態:
        A、調用sleep方法的經過了指定的休眠時間
        B、線程調用的阻塞IO已經返回,阻塞方法執行完畢
        C、線程成功獲得了試圖同步的監視器
        D、線程正在等待某個通知,其他線程發出了通知
        E、處于掛起狀態的線程調用了resume恢復方法
    線程從阻塞狀態只能進入就緒狀態,無法進入運行狀態。而就緒和運行狀態之間的轉換通常不受程序控制,而是由系統調度所致的。
    當就緒狀態的線程獲得資源時,該線程進入運行狀態;當運行狀態的線程事情處理器資源時就進入了就緒狀態。
    但對調用了yield的方法就例外,此方法可以讓運行狀態轉入就緒狀態。
10、線程死亡(Dead)狀態
    線程會在以下方式進入死亡狀態:
    A、run方法執行完成,線程正常結束
    B、線程拋出未捕獲的異常或Error
    C、直接調用該線程的stop方法來結束線程—該方法易導致死鎖,注意使用
    注意:當主線程結束的時候,其他線程不受任何影響。一旦子線程啟動后,會擁有和主線程相同的地位,不受主線程影響。
    isAlive方法可以測試當前線程是否死亡,當線程處于就緒、運行、阻塞狀態,該方法返回true,如果線程處于新建或死亡狀態就會返回false。
    不要試圖對死亡的線程調用start方法,來啟動它。死亡線程不可能再次運行。
11、控制線程
    Java線程提供了很多工具方法,這些方法都很好的控制線程
    A、join線程
        讓一個線程等待另一個線程完成的方法。當某個程序執行流中調用其他線程的join方法時,調用線程將會被阻塞,直到被join方法的join線程執行完成為止。
        join方法通常有使用線程的程序調用,將大問題劃分成許多小問題。每個小問題分配一個線程。當所有的小問題得到處理后,再調用主線程進一步操作。
        join有三種重載模式:
            一、join等待被join的線程執行完成
            二、join(long millis)等待被join的線程時間最長為millis毫秒,如果在millis毫秒外,被join的線程還沒有執行完則不再等待
            三、join(long millis, int nanos)被join的線程等待時間長為millis毫秒加上nanos微秒
        通常我們很少用第三種join,原因有二:程序對時間的精度無需精確到千分之一毫秒,計算機硬件、操作系統也無法做到精確到千分之一毫秒
    
    B、后臺線程
        有一種線程,在后臺運行,它的任務是為其他線程提供服務,這種線程被稱為“后臺線程(Daemon Thread)”,有被稱為“守護線程”或“精靈線程”。
        JVM的垃圾回收器線程就是后臺進程。
        后臺進程有個特征是:如果前臺的進程都死亡,那么后臺進程也死亡。(它為前臺進程服務)
        用Thread的setDaemon (true)方法可以指定當前線程為后臺線程。
        注意:前臺線程執行完成死亡后,JVM會通知后臺線程,后臺線程就會死亡。但它得到通知到后臺線程作成響應,需要一段時間,
        而且要將某個線程設置為后臺線程,必需要在該線程啟動前設置,也就是說設置setDaemon必需在start方法前面調用。
        否則會出現java.lang.IllegalThreadStateException異常   
    C、線程休眠sleep
        如果需要當前線程暫停一段時間,并進入阻塞狀態就需要用sleep,sleep有2中重載方式:
        sleep(long millis)讓當前線程暫停millis毫秒后,并進入阻塞狀態,該方法受系統計時器和線程調度器的影響
        sleep(long millis, int nanos)讓當前正在執行的線程暫停millis毫秒+nanos微秒,并進入阻塞
        當調用sleep方法進入阻塞狀態后,在sleep時間段內,該線程不會獲得執行機會,即使沒有其他可運行的線程,處于sleep的線程不會執行。
    D、線程讓步yield
        yield和sleep有點類似,它也可以讓當前執行的線程暫停,但它不會阻塞線程,只是將該線程轉入到就緒狀態。
        yield只是讓當前線程暫停下,讓系統線程調度器重新調度下。
        當yield的線程后,當前線程暫停。系統線程調度器會讓優先級相同或是更高的線程運行。       
        sleep和yield的區別
            (1)、sleep方法暫停當前線程后,會給其他線程執行集合,不會理會線程的優先級。但yield則會給優先級相同或高優先級的線程執行機會
            (2)、sleep方法會將線程轉入阻塞狀態,直到經過阻塞時間才會轉入到就緒狀態;而yield則不會將線程轉入到阻塞狀態,它只是強制當前線程進入就緒狀態。
                    因此完全有可能調用yield方法暫停之后,立即再次獲得處理器資源繼續運行。
            (3)、sleep聲明拋出了InterruptedException異常,所以調用sleep方法時,要么捕獲異常,要么拋出異常。而yield沒有申明拋出任何異常        
    E、改變線程優先級
        每個線程都有優先級,優先級決定線程的運行機會的多少。
        每個線程默認和它創建的父類的優先級相同,main方法的優先級是普通優先級,那在main方法中創建的子線程都是普通優先級。
        getPriority(int newPriority)/setPriority(int)
        設置優先級有以下級別:
            MAX_PRIORITY 值是10
            MIN_PRIORITY 值是1
            NORM_PRIORITY 值是5
            范圍是1-10;
12、線程同步
    當多個線程訪問同一個數據時,非常容易出現線程安全問題。這時候就需要用線程同步
    Case:銀行取錢問題,有以下步驟:
    A、用戶輸入賬戶、密碼,系統判斷是否登錄成功
    B、用戶輸入取款金額
    C、系統判斷取款金額是否大于現有金額
    D、如果金額大于取款金額,就成功,否則提示小于余額
    現在模擬2個人同時對一個賬戶取款,多線程操作就會出現問題。這時候需要同步才行;
    同步代碼塊:
    synchronized (object) {
        //同步代碼
    }
    Java多線程支持方法同步,方法同步只需用用synchronized來修飾方法即可,那么這個方法就是同步方法了。
    對于同步方法而言,無需顯示指定同步監視器,同步方法監視器就是本身this
    同步方法:
    public synchronized void editByThread() {
        //doSomething
    }
    需要用同步方法的類具有以下特征:
    A、該類的對象可以被多個線程訪問
    B、每個線程調用對象的任意都可以正常的結束,返回正常結果
    C、每個線程調用對象的任意方法后,該對象狀態保持合理狀態
    不可變類總是線程安全的,因為它的對象狀態是不可改變的,但可變類對象需要額外的方法來保證線程安全。
    例如Account就是一個可變類,它的money就是可變的,當2個線程同時修改money時,程序就會出現異常或錯誤。
    所以要對Account設置為線程安全的,那么就需要用到同步synchronized關鍵字。
  
    下面的方法用synchronized同步關鍵字修飾,那么這個方法就是一個同步的方法。這樣就只能有一個線程可以訪問這個方法,
    在當前線程調用這個方法時,此方法是被鎖狀態,同步監視器是this。只有當此方法修改完畢后其他線程才能調用此方法。
    這樣就可以保證線程的安全,處理多線程并發取錢的的安全問題。
    public synchronized void drawMoney(double money) {
        //取錢操作
    }
    注意:synchronized可以修飾方法、代碼塊,但不能修飾屬性、構造方法
    
    可變類的線程安全是以降低程序的運行效率為代價,為了減少線程安全所帶來的負面影響,可以采用以下策略:
    A、不要對線程安全類的所有方法都采用同步模式,只對那些會改變競爭資源(共享資源)的方法進行同步。
    B、如果可變類有2中運行環境:單線程環境和多線程環境,則應該為該可變提供2種版本;線程安全的和非線程安全的版本。
    在單線程下采用非線程安全的提高運行效率保證性能,在多線程環境下采用線程安全的控制安全性問題。
    
    釋放同步監視器的鎖定
    任何線程進入同步代碼塊、同步方法之前,必須先獲得對同步監視器的鎖定,那么何時會釋放對同步監視器鎖定?
    程序無法顯示的釋放對同步監視器的鎖定,線程可以通過以下方式釋放鎖定:
    A、當線程的同步方法、同步代碼庫執行結束,就可以釋放同步監視器
    B、當線程在同步代碼庫、方法中遇到break、return終止代碼的運行,也可釋放
    C、當線程在同步代碼庫、同步方法中遇到未處理的Error、Exception,導致該代碼結束也可釋放同步監視器
    D、當線程在同步代碼庫、同步方法中,程序執行了同步監視器對象的wait方法,導致方法暫停,釋放同步監視器
 
    下面情況不會釋放同步監視器:
    A、當線程在執行同步代碼庫、同步方法時,程序調用了Thread.sleep()/Thread.yield()方法來暫停當前程序,當前程序不會釋放同步監視器
    B、當線程在執行同步代碼庫、同步方法時,其他線程調用了該線程的suspend方法將該線程掛起,該線程不會釋放同步監視器。注意盡量避免使用suspend、resume
   
    同步鎖(Lock)
    通常認為:Lock提供了比synchronized方法和synchronized代碼塊更廣泛的鎖定操作,Lock更靈活的結構,有很大的差別,并且可以支持多個Condition對象
    Lock是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,
    線程開始訪問共享資源之前應先獲得Lock對象。不過某些鎖支持共享資源的并發訪問,如:ReadWriteLock(讀寫鎖),在線程安全控制中,
    通常使用ReentrantLock(可重入鎖)。使用該Lock對象可以顯示加鎖、釋放鎖。
     
    class C {
        //鎖對象
        private final ReentrantLock lock = new ReentrantLock();
        //保證線程安全方法
        public void method() {
            //上鎖
            lock.lock();
            try {
                //保證線程安全操作代碼
            } catch() {
            
            } finally {
                lock.unlock();//釋放鎖
            }
        }
    }
    使用Lock對象進行同步時,鎖定和釋放鎖時注意把釋放鎖放在finally中保證一定能夠執行。    
    使用鎖和使用同步很類似,只是使用Lock時顯示的調用lock方法來同步。而使用同步方法synchronized時系統會隱式使用當前對象作為同步監視器,
    同樣都是“加鎖->訪問->釋放鎖”的操作模式,都可以保證只能有一個線程操作資源。
    同步方法和同步代碼塊使用與競爭資源相關的、隱式的同步監視器,并且強制要求加鎖和釋放鎖要出現在一個塊結構中,而且獲得多個鎖時,
    它們必須以相反的順序釋放,且必須在與所有鎖被獲取時相同的范圍內釋放所有資源。
    Lock提供了同步方法和同步代碼庫沒有的其他功能,包括用于非塊結構的tryLock方法,已經試圖獲取可中斷鎖lockInterruptibly()方法,
    還有獲取超時失效鎖的tryLock(long, timeUnit)方法。
    ReentrantLock具有重入性,也就是說線程可以對它已經加鎖的ReentrantLock再次加鎖,ReentrantLock對象會維持一個計數器來追蹤lock方法的嵌套調用,
    線程在每次調用lock()加鎖后,必須顯示的調用unlock()來釋放鎖,所以一段被保護的代碼可以調用另一個被相同鎖保護的方法。
    
    死鎖
    當2個線程相互等待對方是否同步監視器時就會發生死鎖,JVM沒有采取處理死鎖的措施,這需要我們自己處理或避免死鎖。
    一旦死鎖,整個程序既不會出現異常,也不會出現錯誤和提示,只是線程將處于阻塞狀態,無法繼續。
    主線程保持對Foo的鎖定,等待對Bar對象加鎖,而副線程卻對Bar對象保持鎖定,等待對Foo加鎖2條線程相互等待對方先釋放鎖,進入死鎖狀態。
    由于Thread類的suspend也很容易導致死鎖,所以Java不推薦使用此方法暫停線程。
13、線程通信
    (1)、線程的協調運行
        場景:用2個線程,這2個線程分別代表存款和取款。——現在系統要求存款者和取款者不斷重復的存款和取款的動作,
        而且每當存款者將錢存入賬戶后,取款者立即取出這筆錢。不允許2次連續存款、2次連續取款。
        實現上述場景需要用到Object類,提供的wait、notify和notifyAll三個方法,這3個方法并不屬于Thread類。但這3個方法必須由同步監視器調用,可分為2種情況:
        A、對于使用synchronized修飾的同步方法,因為該類的默認實例this就是同步監視器,所以可以在同步中直接調用這3個方法。
        B、對于使用synchronized修改的同步代碼塊,同步監視器是synchronized后可括號中的對象,所以必須使用括號中的對象調用這3個方法
        方法概述:
        一、wait方法:導致當前線程進入等待,直到其他線程調用該同步監視器的notify方法或notifyAll方法來喚醒該線程。
                wait方法有3中形式:無參數的wait方法,會一直等待,直到其他線程通知;帶毫秒參數的wait和微妙參數的wait,
                這2種形式都是等待時間到達后蘇醒。調用wait方法的當前線程會釋放對該對象同步監視器的鎖定。
        二、notify:喚醒在此同步監視器上等待的單個線程。如果所有線程都在此同步監視器上等待,則會隨機選擇喚醒其中一個線程。
            只有當前線程放棄對該同步監視器的鎖定后(用wait方法),才可以執行被喚醒的線程。
        三、notifyAll:喚醒在此同步監視器上等待的所有線程。只有當前線程放棄對該同步監視器的鎖定后,才能執行喚醒的線程。
    (2)、條件變量控制協調
        如果程序不使用synchronized關鍵字來保證同步,而是直接使用Lock對象來保證同步,則系統中不存在隱式的同步監視器對象,
        也不能使用wait、notify、notifyAll方法來協調進程的運行。
        當使用Lock對象同步,Java提供一個Condition類來保持協調,使用Condition可以讓那些已經得到Lock對象卻無法組合使用,
        為每個對象提供了多個等待集(wait-set),這種情況下,Lock替代了同步方法和同步代碼塊,Condition替代同步監視器的功能。
        Condition實例實質上被綁定在一個Lock對象上,要獲得特定的Lock實例的Condition實例,調用Lock對象的newCondition即可。
        Condition類方法介紹:
        一、await:類似于隱式同步監視器上的wait方法,導致當前程序等待,直到其他線程調用Condition的signal方法和signalAll方法來喚醒該線程。
            該await方法有跟多獲取變體:long awaitNanos(long nanosTimeout),void awaitUninterruptibly()、awaitUntil(Date daadline)
        二、signal:喚醒在此Lock對象上等待的單個線程,如果所有的線程都在該Lock對象上等待,則會選擇隨機喚醒其中一個線程。
            只有當前線程放棄對該Lock對象的鎖定后,使用await方法,才可以喚醒在執行的線程。
        三、signalAll:喚醒在此Lock對象上等待的所有線程。只有當前線程放棄對該Lock對象的鎖定后,才可以執行被喚醒的線程。
    (3)、使用管道流
        線程通信使用管道流,管道流有3種形式:
        PipedInputStream、PipedOutputStream、PipedReader和PipedWriter以及Pipe.SinkChannel和Pipe.SourceChannel,
        它們分別是管道流的字節流、管道字符流和新IO的管道Channel。
        管道流通信基本步驟:
        A、使用new操作法來創建管道輸入、輸出流
        B、使用管道輸入流、輸出流的connect方法把2個輸入、輸出流連接起來
        C、將管道輸入、輸出流分別傳入2個線程
        D、2個線程可以分別依賴各自的管道輸入流、管道輸出流進行通信
14、線程組和未處理異常
    ThreadGroup表示線程組,它可以表示一批線程進行分類管理,Java允許程序對
    Java允許直接對線程組控制,對線程組控制相對于同時控制這批線程。用戶創建的所有線程都屬于指定的線程組。
    如果程序沒有值得線程屬于哪個組,那這個線程就屬于默認線程組。在默認情況下,子線程和創建它父線程屬于同一組。
    一旦某個線程加入了指定線程組之后,該線程將屬于該線程組,直到該線程死亡,線程運行中途不能改變它所屬的線程組。
    Thread類提供一些構造設置線程所屬的哪個組,具有以下方法:
    A、Thread(ThreadGroup group, Runnable target):target的run方法作為線程執行體創建新線程,屬于group線程組
    B、Thread(ThreadGroup group, Runnalbe target, String name):target的run方法作為線程執行體創建的新線程,該線程屬于group線程組,且線程名為name
    C、Thread(ThreadGroup group, String name):創建新線程,新線程名為name,屬于group組
 
    因為中途不能改變線程所屬的組,所以Thread提供ThreadGroup的setter方法,但提供了getThreadGroup方法來返回該線程所屬的線程組,
    getThreadGroup方法的返回值是ThreadGroup對象的表示,表示一個線程組。
    ThreadGroup有2個構造形式:
    A、ThreadGroup(String name):name線程組的名稱
    B、ThreadGroup(ThreadGroup parent, String name):指定名稱、指定父線程組創建的一個新線程組
 
    上面的構造都指定線程名稱,也就是線程組都必須有自己的一個名稱,可以通過調用ThreadGroup的getName方法得到,
    但不允許中途改變名稱。ThreadGroup有以下常用的方法:
    A、activeCount:返回線程組活動線程數目
    B、interrupt:中斷此線程組中的所有線程
    C、isDeamon:判斷該線程是否在后臺運行
    D、setDeamon:把該線程組設置為后臺線程組,后臺線程具有一個特征,當后臺線程的最后一個線程執行結束或最后一個線程被銷毀,后臺線程組自動銷毀。
    E、setMaxPriority:設置線程組最高優先級
    uncaughtException(Thread t, Throwable e)該方法可以處理該線程組內的線程所拋出的未處理的異常,
    Thread.UncaughtExceptionHandler是Thread類的一個內部公共靜態接口,
    該接口內只有一個方法:void uncaughtException(Thread t, Throwable e) 該方法中的t代表出現異常的線程,而e代表該線程拋出的異常
     
    Thread類中提供2個方法來設置異常處理器:
    A、staticsetDefaultUnaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):為該線程類的所有線程實例設置默認的異常處理器
    B、setUncaughtExceptionHandler(Thread.UncaughtExceptionHander eh):為指導線程實例設置異常處理器
 
    ThreadGroup實現了Thread.UncaughtExceptionHandler接口,所以每個線程所屬的線程組將會作為默認的異常處理器。當一個線程拋出未處理異常時,
    JVM會首先查找該異常對應的異常處理器,(setUncaughtExceptionHandler設置異常處理器),如果找到該異常處理器,將調用該異常處理器處理異常。
    否則,JVM將會調用該線程的所屬線程組的uncaughtException處理異常,線程組處理異常流程如下:
    A、如果該線程有父線程組,則調用父線程組的uncaughtException方法來處理異常
    B、如果該線程實例所屬的線程類有默認的異常處理器(setDefaultUnaughtExceptionHandler方法設置異常處理器),那就調用該異常處理器來處理異常信息
    C、將異常調用棧的信息打印到System.err錯誤輸出流,并結束該線程
15、Callable和Future
    Callable接口定義了一個call方法可以作為線程的執行體,但call方法比run方法更強大:
    A、call方法可以有返回值
    B、call方法可以申明拋出異常
    Callable接口是JDK5后新增的接口,而且不是Runnable的子接口,所以Callable對象不能直接作為Thread的target。而且call方法還有一個返回值,
    call方法不能直接調用,它作為線程的執行體被調用。那么如何接收call方法的返回值?
    JDK1.5提供了Future接口來代表Callable接口里的call方法的返回值,并為Future接口提供了一個FutureTask實現類,該實現類實現Future接口,
    并實現了Runnable接口—可以作為Thread的target。
 
    Future接口里定義了如下幾個公共方法控制他關聯的Callable任務:
    A、boolean cancel(Boolean mayInterruptlfRunning):試圖取消該Future里關聯的Callable任務
    B、V get():返回Callable任務里的call方法的返回值,調用該方法將導致線程阻塞,必須等到子線程結束才得到返回值
    C、V get(long timeout, TimeUnit unit):返回Callable任務里的call方法的返回值,該方法讓程序最多阻塞timeout和unit指定的時間。
        如果經過指定時間后Callable任務依然沒有返回值,將會拋出TimeoutException。
    D、boolean isCancelled:如果在Callable任務正常完成前被取消,則返回true。
    E、boolean isDone:如果Callable任務已經完成,則返回true
    創建、并啟動有返回值的線程的步驟如下:
    一、創建Callable接口的實現類,并實現call方法,該call方法的返回值,并作為線程的執行體。
    二、創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call方法的返回值
    三、使用FutureTask對象作為Thread對象的target創建、并啟動新線程
    四、調用FutureTask對象的方法來獲得子線程執行結束后的返回值
16、線程池
    Why? 系統啟動一個新線程的成本比較高,因為涉及到與操作系統交互。這個時候用線程池可以很好的提高性能,
    尤其是當程序中需要創建大量生存期很短暫的線程時,更應該考慮使用線程池。
    原理:(流程)線程池和數據庫連接池有點類似的是,線程池在系統啟動時創建大量空閑線程,程序將一個Runnable對象傳給線程池,
    線程池就會啟動一條線程來執行該線程對象的run方法,當run方法執行結束后,該線程并不會死亡,而是再次返回線程池中成為空閑線程,
    等待執行下一個Runnable對象的run方法。
    優點:使用線程池可以有效的控制系統中并發線程的數量,當系統中包含大量的并發線程時,會導致系統性能劇烈下降,
    甚至導致JVM的崩潰,而線程池的最大線程參數可以控制系統中并發線程數目不超過此數目。
 
    在JDK1.5開始,提供Java內建的線程池,JDK提供一個Executors工廠類來產生線程池,該工程類包含如下幾個靜態工廠方法來創建連接池:
    A、newCachedThreadPool():創建一個具有緩存功能的線程池,系統根據需要創建線程,這些線程將會被緩存在線程池中。
    B、newFixedThreadPool(int nThreads):創建一個可重用的、具有固定線程數的線程池
    C、newSingleThreadExecutor():創建一個只有單線程的線程池,它相當于newFixedThreadPool方法傳遞參數1
    D、newScheduledThreadPool(int corePoolSize):創建具有指定線程數的線程池,它可以在指定延遲后執行線程任務,
        corePoolSize 指池中所保持的線程數,即使線程是空閑的也被保存在線程池內。
    E、newSingleThreadScheduiedExecutor():創建只有一條線程的線程池,它可以在指定延時后執行線程任務。
    以上5個方法的前三個方法返回的是一個ExecutorService對象,該對象代表一個線程池,它可以執行Runnable對象或Callable對象所代表的線程。
    而后兩個方法返回一個ScheduledExecutorService線程池,它是ExecutorService的子類,它可以在指定延時后執行線程任務。
 
    ExecutorService代表盡快執行線程的線程池(只要線程中有空閑的線程就立即執行線程任務)。
    程序只要將一個Runnable對象或Callable對象(代表線程任務)提及給該線程池即可,該線程池就會盡快的執行任務。
    ExecutorService里提供如下3個方法:
    A、Future<?> submit(Runnable task):將一個Runnable對象提交給指定的線程池。線程池將在有空閑線程時執行Runnable對象的代表的任務。
        其中Future對象代表Runnable任務的返回值—run方法蠻腰返回值,所以Future對象將在run方法執行結束后返回null,
        但可以調用Future的isDone,isCancelled方法來獲得Runnable對象的執行狀態
    B、<T> Future<T> submit(Runnable task, T reslut):將一個Runnable對象提及給指定的線程池,線程池將在有空閑線程時執行Runnable對象代表的任務,
        result顯示執行線程執行結束后的返回值,所以Future對象將在run方法執行結束后返回result
    C、<T> Future<T> submit(Callable<T> task):將一個Callable對象提交給指定線程池。線程池將在有空閑線程時執行Callable對象代表的任務,
        Future代表Callable對象里的call方法的返回值
    ScheduledExecutorService代表可在指定延遲或周期性執行線程任務的線程池,它提供了如下方法:    
    A、ScheduledFuture<V> schedule(Callable<V> callable, long delay, Timeout unit):指定Callable任務將在delay延遲后執行
    B、ScheduledFuture<V> schedule(Runnable command, long delay, Timeout unit):指定command任務將在delay延遲后執行
    C、ScheduleFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
        指定command任務將在delay延長后執行,而且以設定頻率重復執行,也就是說,在initialDelay后開始執行,
        異常在initialDelay+2* period 處重復運行,依次類推
    D、ScheduleFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, Timeout unit)
        創建并執行一個在給定初始延遲后首次啟用的定期操作,隨后,在每一次執行終止和下一次執行開始之間都存在給定的延遲。
        如果任務的任一次運行遇到異常,就會取消后續運行。否則,只能通過程序來顯示取消或終止任務
    當用完一個線程池后,應該調用shutdown方法,該方法將啟動線程池的關閉序列,調用了shutdown方法后線程池不再接受新的任務,
    但會將以前所有一提交的任務執行完成。當線程池所有線程任務執行完畢后,池中所有線程都會死亡。另外也可以執行線程池的shutdownNow方法來關閉線程池,
    該方法試圖停止所有正在執行的活動任務,暫停處理正在等待的任務,并返回等待執行任務列表。
    使用線程池來執行線程任務步驟:
    A、調用Executors類的靜態工廠方法創建一個ExecutorService對象,該對象代表一個線程池
    B、創建Runnable實現類或是Callable實現類的實例,作為線程的執行任務
    C、調用ExecutorService對象的submit方法來提交Runnable和Callable實例
    D、當不想提交任務時調用ExecutorService對象的shutdown方法來關閉線程池
17、線程相關類
    一、ThreadLocal類
        在JDK5后ThreadLocal引入了泛型的支持,通過使用ThreadLocal可以簡化多多線程的編程時是并發訪問,使用這個工具類可以幫我們更好的實現多線程。
        ThreadLocal,是Thread Local Variable(線程的局部變量)的意思。線程局部變量功能非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,
        是每一個線程都可以獨立的改變自己的副本,而不會和其他線程的副本沖突。
        ThreadLocal提供常用方法:
            A、T get():返回此線程局部變量中當前線程副本中的值
            B、void remove():刪除此線程局部變量中當前線程副本中的值
            C、void set(T value):設置此線程局部變量中當前線程副本中的值
        ThreadLocal和其他所有同步機制都是為了解決多線程中對同一變量的訪問沖突,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一變量的安全訪問的。
        在這種情形下,該變量是多個線程共享的,所以要使用這種同步機制需要很細致的分析在什么時候對變量進行讀寫,
        上面時候需要鎖定某個對象,什么時候釋放對象鎖等。
        在這種情況下系統并沒有將這份資源復制多份,只是采用了案情機制來控制隊這份資源的訪問而已。
        ThreadLocal就從另一個角度來解決多線程的并發訪問,ThreadLocal將需要并發訪問的資源復制出多份,每個線程擁有一份資源,每個線程都擁有自己的資源副本,
        從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的對象,在編寫多線程代碼時,可以把不安全的整個變量封裝進ThreadLocal,
        或者把該對象與現場相關的狀態使用ThreadLocal保存。
        ThreadLocal并不能代替同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的并發訪問,是多個線程之間進行通信的有效方式;
        而ThreadLocal是隔離多個線程的數據共享,從根本上避免了多個線程之間共享資源(變量),也就不需要對多個線程進行同步了。
        通常認為:如果需要進行多個線程之間的共享資源,已到達線程之間的通信功能,就使用步機制,如果僅僅需要隔離多個線程之間的共享沖突,就用ThreadLocal。
    二、包裝線程不安全集合
        當用多線程操作集合時,對線程不安全的集合進行操作容易破壞集合數據的完整性。
        A、<T> Collection<T> synchronizedCollection(Collection<T> c):返回指定collection對應的線程安全的collection
        B、static <T> List<T> synchronizedList(List<T> list):返回指定List對應的現場安全的List對象
        C、static <K, V> Map<K, V> synchronizedMap(Map<K, V> m):返回指定Map對象對應的現場安全的Map對象
        D、static <T> Set<T> synchronizedSet(Set<T> s):返回指定Set對應的線程安全的Set
        E、static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> m):返回指定SortedMap對象所對應線程安全的SortedMap對象
        F、static <K, V> SortedSet<K, V> synchronizedSortedSet(SortedSet<K, V> m):返回指定SortedSet對象所對應線程安全的SortedSet 對象
        使用線程安全的HashMap對象:
        HashMap map = Collections.synchronizedMap(new HashMap());
        注意:如果需要把某個集合包裝成線程安全的集合,則應該在創建之后立即包裝,包裝后就是線程安全的HashMap對象了。
    三、線程安全的集合
        在JDK5后提供了java.util.concurrent包的ConcurrentHashMap、ConcurrentLinkedQueue兩個支持并發訪問的集合,
        它們分別代表了支持并發訪問的HashMap和支持并發訪問的Queue。默認都支持多線程并發寫入,寫入操作是線程安全的,讀取不必鎖定。這兩個集合采用了復雜的算法,他們是永遠都鎖不住的集合。
        當多線程共享訪問一個集合時,ConcurrentLinkedQueue最合適不過,Queue不允許為null元素。Quee實現了多線程的高效訪問,多條線程訪問ConcurrentLinkedQueue集合是無需等待。 ConcurrentHashMap默認支持16條線程并發訪問,當有超過16條線程并發訪問就需要等待。
        但可以設置concurrentLevel構造方法參數(默認16)來支持更多的線程并發數量。 與HashMap和普通集合不同的是,當我們用迭代器變量ConcurrentHashMap、ConcurrentLinkedQueue時, 如果在迭代器創建后對集合元素的修改是不會在迭代器中做出修改,也不會出現異常。
  如果用Collection作為集合對象時,如果對象在創建迭代器后發生變化修改,就會引發ConcurrentModificationException

來自:http://blog.csdn.net/lixiaopeng23/article/details/17262445

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!