Java 多線程知識點

Java多線程并發(fā)


知識結構

一、 java多線程創(chuàng)建方式

  1. 繼承Tread類

    將自己的類繼承Tread類,并重寫run()方法。

    Tread類的start()方法是一個native方法

  2. 實現Runable接口

    將自己的類實現Runable接口,重寫run方法。

    實例出一個task。再實例化一個Thread,并傳入task。

  3. 實現Callable接口(有返回值)

    執(zhí)行callable task 可以返回一個Future 對象,通過get方法能獲得任務的返回值。

二、 4種線程池


  1. newCachedTreadPool

    適用于短期異步任務。

    • 當任務申請線程,若有空閑線程,則重用這個空閑線程。
    • 若無空閑線程,則創(chuàng)建新線程。
    • 會終止并從緩存移出60秒未被使用的線程。
  2. newFixedTreadPool

    固定數量的線程池,用隊列排隊。
    適用于子啊任意點大多數線程都處于活動狀態(tài)的程序。
    有任務申請線程

    • 隊列滿,排隊等待,不滿,得到線程。
    • 若某個線程異常終止 , 創(chuàng)建新線程繼續(xù)任務。
    • 在線程被顯式關閉前,線程將一直存在于線程池。
  3. newScheduledThreadPool

    可設置內部線程延遲執(zhí)行或定期執(zhí)行。

  4. newSingelThreadExcutor

    線程池只有單個線程。

三、 線程生命周期


線程生命周期
它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5 種狀態(tài)。
  1. 新建狀態(tài)。JVM分配內存,并初始化成員變量。

  2. 就緒狀態(tài)。調用start()方法后,處于就緒狀態(tài)。JVM創(chuàng)建方法棧和PC,等待調度運行。

  3. 運行狀態(tài)。得到CPU控制權,開始執(zhí)行run方法。

  4. 阻塞狀態(tài)

    讓出CPU使用權

    • 等待阻塞 object.wait。JVM將線程放入該對象的等待鎖池。
    • 同步阻塞 lock 。同步鎖被別的線程占用,JVM將線程放入鎖池(look pool) ;
    • 其他阻塞 sleep/join 或IO ,不釋放鎖,當超時,或IO結束,線程重新變成Runable。

四、 終止線程的4種方式


  1. 正常結束

  2. 使用退出標志
    對于一些守護線程,當外部條件滿足時才能退出。這個時候可以用類似于While(!biaozhi) 循環(huán) 的方式控制線程是否終止。

  3. Interrupt()方法
    該方法實質是設置中斷標志位為true ,程序員通過檢測中斷標志位來結束線程。

    • 線程處于阻塞狀態(tài): 如調用了sleep、wait等方法后線程阻塞。此時調用線程的interrupt方法會拋出InterruptException異常。通過捕獲這個異常然后break跳出循環(huán),終止線程。

    • 未阻塞 :通過調用isInterrupted() 判斷線程中斷標志位來退出循環(huán)。

      while (!isInterrupted()) {to do}

    注意 interrupted()方法返回狀態(tài)后,會重置狀態(tài)。

    public class ThreadSafe extends Thread {
    public void run() {
    while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標志來退出
        try{
            Thread.sleep(5*1000);//阻塞過程捕獲中斷異常 來退出
        }catch(InterruptedException e){
            e.printStackTrace();
            break;//捕獲到異常之后,執(zhí)行break 跳出循環(huán)
            }
            }
        }
    }
    
  4. 使用stop方法.不安全

    線程會突然釋放所有的鎖,可能導致數據的破壞。

五、 sleep 和 wait 的區(qū)別


  1. sleep屬于 Thread 類 . wait 屬于Object類

  2. sleep后會自動恢復運行,而wait需要notify

  3. sleep不會釋放鎖,wait釋放鎖

六、 start和run的區(qū)別


  1. start() 是線程啟動方法。run是線程業(yè)務代碼方法

  2. start() 之后創(chuàng)建線程處于就緒狀態(tài),如果直接調用run()。會在主線程直接運行業(yè)務代碼。

七、 守護線程


為用戶線程提供服務的線程

設置方法setDaemon(true)

特性

  1. 守護線程可以不依賴于控制終端(應用)而依賴于系統。

  2. 在所有用戶線程結束之后再自動結束。

八、java鎖


  1. 樂觀鎖
    讀數據的時候不上鎖, 寫數據的時候上鎖。

    寫入的時候先讀出版本號V1,然后進行CAS原子操作。比較V1和加鎖后的V2是否相同,若相同,更新;若不同,更新失敗。

  2. 悲觀鎖
    讀寫數據的時候都加鎖。
    比如: Synchronized 同步 和 RetreenLock 鎖

  3. 自旋鎖

    讓未獲得鎖的線程不掛起,仍然保持內核態(tài)。等待鎖的釋放。此時會消耗cpu。

    適用于持有鎖的線程會在短時間釋放鎖的情況。

    優(yōu)點:

    • 減少線程阻塞,提高性能。自旋消耗小于線程阻塞掛起和幻想的消耗。

    • 若持有鎖的線程長時間占有鎖。則自旋所需的cpu性能白白消耗,同時是的其他需要cpu的線程沒有獲得cpu造成浪費。

    自旋鎖時間閾值
    自旋周期jad1.5的時候固定的,jdk1.6的時候是自適應的。

  4. Synchronized同步鎖

    悲觀鎖。獨占式、可重入。

    • 作用于方法, 鎖實例。當調用實例中被鎖方法,阻塞
    • 作用于靜態(tài)方法, 鎖Class實例。Class相關數據只存在于方法區(qū)(元空間) ,即鎖住所有調用該方法的線程。
    • 作用于對象時,當調用被鎖實例中任何方法,阻塞。

    核心組件

    Synchronized的組件

    1) Contention List:競爭隊列,所有請求鎖的線程首先被放在這個競爭隊列中;
    
    2) Wait Set:哪些調用wait 方法被阻塞的線程被 放置在這里;
    
     3) Entry List:Contention List 中那些有資格成為候選資源的線程被移動到Entry List 中;
    
    4) OnDeck:任意時刻,最多只有一個線程正在競爭鎖資源,該線程被成為OnDeck;
    
    5) Owner:當前已經獲取到所資源的線程被稱為Owner;
    
    6) !Owner:當前釋放鎖的線程。
    

    Synchronized 實現

    1. 當 Owner 線程 unlock時, JVM將EntryList中的一個線程移入OnDeck 。同時,將ContentionList中的一些線程移入EnryList。
    2. onDeck中的線程有競爭鎖的權利,而不是直接得到鎖。它與處于自旋狀態(tài)的線程競爭鎖。
    3. 持有鎖的線程調用wait方法, 該線程釋放鎖,并進入Wait Set--等待池。當調用notify時,將Wait Set 中線程加入EnryList--有資格競爭鎖的等待隊列。
    4. Synchronized是非公平鎖,線程在進入ContentList之前,會先嘗試獲得自旋鎖,并可能直接與OnDeck中的線程產生競爭,搶占鎖資源。
    5. 加鎖實際上是在競爭monitor對象
  1. ReentrantLock 可重入鎖

    ReentrantLock 和 synchronized都是可重入鎖.
    可重入鎖與不可重入鎖的區(qū)別

    不可重入鎖:只判斷這個鎖有沒有被鎖上,只要被鎖上申請鎖的線程都會被要求等待。實現簡單
    
     可重入鎖:不僅判斷鎖有沒有被鎖上,還會判斷鎖是誰鎖上的,當就是自己鎖上的時候,那么他依舊可以再次訪問臨界資源,并把加鎖次數加一。
    
     設計了加鎖次數,以在解鎖的時候,可以確保所有加鎖的過程都解鎖了,其他線程才能訪問。
    

    關鍵源碼 (上鎖): while(isLocked && lockedBy != thread){ wait(); } 判斷了是否上鎖,并且請求鎖的線程是否是當前線程。

    ReentrantLock 與 synchronized相比:

    1. 優(yōu)勢:可中斷、公平鎖、多個鎖。
    2. 使用lock 和 unlock 加鎖解鎖。解鎖必須在finally控制塊中完成。

    Condition 類和Object類鎖的區(qū)別

    1. Condition.await 和Object.wait等效
    2. signal 和 notify 等效
    3. ReentrantLock可以喚醒指定條件線程。

    Lock 和 tryLock

    1. tryLock 沒獲得鎖返回false。
    2. lock 沒獲得鎖阻塞 ,等待獲得鎖.

    Lock 和 lockIterrupibly

    1. lock被中斷不會拋出異常, 而 lockIterruptibly會拋出異常.

    熟悉的方法

    1. void lock(): 執(zhí)行此方法時, 如果鎖處于空閑狀態(tài), 當前線程將獲取到鎖. 相反, 如果鎖已經
    被其他線程持有, 將禁用當前線程, 直到當前線程獲取到鎖.
    2. boolean tryLock():如果鎖可用, 則獲取鎖, 并立即返回true, 否則返回false. 該方法和
    lock()的區(qū)別在于, tryLock()只是"試圖"獲取鎖, 如果鎖不可用, 不會導致當前線程被禁用,
    當前線程仍然繼續(xù)往下執(zhí)行代碼. 而lock()方法則是一定要獲取到鎖, 如果鎖不可用, 就一
    直等待, 在未獲得鎖之前,當前線程并不繼續(xù)向下執(zhí)行.
    3. void unlock():執(zhí)行此方法時, 當前線程將釋放持有的鎖. 鎖只能由持有者釋放, 如果線程
    并不持有鎖, 卻執(zhí)行該方法, 可能導致異常的發(fā)生.
    4. Condition newCondition():條件對象,獲取等待通知組件。該組件和當前的鎖綁定,
    當前線程只有獲取了鎖,才能調用該組件的await()方法,而調用后,當前線程將縮放鎖。
    

    不熟悉的方法

    1. getHoldCount() :查詢當前線程保持此鎖的次數,也就是執(zhí)行此線程執(zhí)行l(wèi)ock 方法的次
    數。
    2. getQueueLength():返回正等待獲取此鎖的線程估計數,比如啟動10 個線程,1 個
    線程獲得鎖,此時返回的是9
    3. getWaitQueueLength:(Condition condition)返回等待與此鎖相關的給定條件的線
    程估計數。比如10 個線程,用同一個condition 對象,并且此時這10 個線程都執(zhí)行了
    condition 對象的await 方法,那么此時執(zhí)行此方法返回10
    4. hasWaiters(Condition condition) : 查詢是否有線程等待與此鎖有關的給定條件
    (condition),對于指定contidion 對象,有多少線程執(zhí)行了condition.await 方法
    5. hasQueuedThread(Thread thread):查詢給定線程是否等待獲取此鎖
    6. hasQueuedThreads():是否有線程等待此鎖
    7. isFair():該鎖是否公平鎖
    8. isHeldByCurrentThread(): 當前線程是否保持鎖鎖定,線程的執(zhí)行l(wèi)ock 方法的前后分
    別是false 和true
    9. isLock():此鎖是否有任意線程占用
    10. lockInterruptibly():如果當前線程未被中斷,獲取鎖
    11. tryLock():嘗試獲得鎖,僅在調用時鎖未被線程占用,獲得鎖
    12. tryLock(long timeout TimeUnit unit):如果鎖在給定等待時間內沒有被另一個線程保持,
    則獲取該鎖。
    

    非公平鎖 : JVM隨機或就近分配鎖的機制。ReentrantLock(false) 構造函數設置公平/非公平

    公平鎖 : 先申請鎖的先得到鎖。ReentrantLock(true)

  1. Semaphore 信號量

    閾值設置,運行多少個線程同時操作資源。
    閾值設置為 1 即互斥

    信號量 減1 : Semaphore.acquire()

    信號量 加1 : Semaphore.release()

  1. AtomicInteger 原子操作Integer

    還可以通過AtomicReference<V>將對象的所有操作轉化為原子操作。

    保證操作的原子性 ,效率比lock高

  2. ReadWriteLock 讀寫鎖

    讀鎖: 讀取的時候保證多個人讀,不能同時寫

    寫鎖 : 寫入的時候保證一個人寫,且不能讀

  1. 鎖優(yōu)化

    • 減少鎖持有時間
    • 減少鎖粒度 , 降低鎖的競爭
    • 鎖分離 , 例如讀寫鎖
    • 鎖粗化 如果對一個鎖不停請求,同步釋放也會作大量
    • 鎖消除 編譯器干的事

十、 線程的基本方法


  1. wait

    線程阻塞 , 釋放鎖,進入wait set 線程等待池

  2. sleep

    線程休眠,不釋放鎖 , 進入time-wating狀態(tài)

  3. yeld

    線程讓步 , 讓出cpu使用權,與其他線程競爭時間片

  4. interrupt()

    線程中斷 , 但不直接中斷線程。

    • 給出通知信號 , 使得time-waiting狀態(tài)的線程拋出interruptException異常。中斷位會清零。
    • 設置中斷位為 1
  5. join等待其他線程終止

    其他線程調用join方法,當前線程阻塞 。 不放棄鎖 , 等到相關線程結束,轉為就緒狀態(tài)。

  6. Object.notify

    線程喚醒 , 喚醒對象監(jiān)視器上等待的單個線程,競爭cpu使用權。

    為什么notify和wait都在object類上?

    答:因為notify和 wait需要作用在同一個鎖上。而鎖可以是任意對象,可以被任意對象調用的方法是定義在object類中。

  7. 其他方法:

    1. sleep():強迫一個線程睡眠N毫秒。 
    2. isAlive(): 判斷一個線程是否存活。  
    3. join(): 等待線程終止。  
    4. activeCount(): 程序中活躍的線程數。
    5. enumerate(): 枚舉程序中的線程。  
    6.  currentThread(): 得到當前線程。  
    7.  isDaemon(): 一個線程是否為守護線程。  
    8.   setDaemon(): 設置一個線程為守護線程。(用戶線程和守護線程的區(qū)別在于,是否等待主線 程依賴于主線程結束而結束)  
    9.    setName(): 為線程設置一個名稱。  
    10.    wait(): 強迫一個線程等待。  
    11. setPriority(): 設置一個線程的優(yōu)先級。
    12. getPriority()::獲得一個線程的優(yōu)先級。
    

十一線程上下文切換


  1. 線程上下文切換,同一顆cpu上 ,任務狀態(tài)的保存和再加載

  2. 上下文

    某一時間點,cpu寄存器和計數器內容

  3. PCB 進程控制塊-切換幀

    表示進程狀態(tài)的信息塊。

  4. 上下文切換活動

    1. 掛起進程(線程) , 將進程信息存儲在內存
    2. 內存中檢索下一個進程上下文,并在CPU寄存器中恢復
    3. 跳轉到PC ,運行程序
  5. 引起上下文切換原因

    • 時間片用完
    • IO阻塞
    • 多任務搶占鎖,未獲得鎖
    • 硬件中斷

十二線程池原理


  1. 主要特點 : 線程復用 , 控制最大并發(fā)數 , 線程管理

  2. 線程復用: ?

    在Thread strat() 方法中不斷循環(huán)調用傳遞過來的Runnable對象 , 并使用Queue組織這些Runnable對象。

  3. ThreadPoolExecutor

    構造方法如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize, 
                              long keepAliveTime, 
                              TimeUnit unit, 
                              BlockingQueue<Runnable> workQueue) { 
    
      this(corePoolSize, maximumPoolSize, keepAliveTime, unit , workQueue, 
      Executors.defaultThreadFactory(), defaultHandler); 
    }
    
    1. corePoolSize:指定了線程池中的線程數量。 
    2. maximumPoolSize:指定了線程池中的大線程數量。 
    3. keepAliveTime:當前線程池數量超過corePoolSize時,多余的空閑線程的存活時間,即多少時間內會被銷毀。 
    4. unit:keepAliveTime的單位。 
    5. workQueue:任務隊列,被提交但尚未被執(zhí)行的任務。 
    6. threadFactory:線程工廠,用于創(chuàng)建線程,一般用默認的即可。 
    7. handler:拒絕策略,當任務太多來不及處理,如何拒絕任務。
    
  4. 線程池工作過程

    1. 線程池剛創(chuàng)建時,里面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列里面 有任務,線程池也不會馬上執(zhí)行它們。 
    
    2. 當調用 execute() 方法添加一個任務時,線程池會做如下判斷: 
       a) 如果正在運行的線程數量小于 corePoolSize,那么馬上創(chuàng)建線程運行這個任務; 
       b) 如果正在運行的線程數量大于或等于 corePoolSize,那么將這個任務放入隊列;
       c) 如果這時候隊列滿了,而且正在運行的線程數量小于 maximumPoolSize,那么還是要 創(chuàng)建非核心線程立刻運行這個任務; 
       d) 如果隊列滿了,而且正在運行的線程數量大于或等于 maximumPoolSize,那么線程池 會拋出異常RejectExecutionException。 
    
    3. 當一個線程完成任務時,它會從隊列中取下一個任務來執(zhí)行。
    
    4. 當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運 行的線程數大于 corePoolSize,那么這個線程就被停掉。
       所以線程池的所有任務完成后,它 終會收縮到 corePoolSize 的大小。 
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容