多執行緒相關整理

0冰鎮檸檬汁0發表於2020-09-30

執行緒基礎:

 
程式:資源分配的最小單位
執行緒:CPU排程的最小單位
 
並行:多個CPU同時執行多個任務
併發:一個CPU同時執行多個任務(時間片輪轉排程)
 
CPU排程演算法: 
 
                        先來先服務演算法(FCFS)、 最短作業優先(SJF)、 時間片輪轉排程演算法(RR )、 高響應比優先演算法(HRRN) 最高優先順序排程演算法、 多級反饋佇列排程演算法
 
守護執行緒:服務於使用者執行緒,使用者執行緒結束後才會結束(GC執行緒)
使用者執行緒:所有使用者執行緒都結束,JVM才能結束(main執行緒)
 
如何避免死鎖:
  • 確保執行緒按照同樣的順序獲取鎖
  • 加鎖時設定超時時間,超時則放棄獲取鎖並釋放已獲取的鎖
  • 降低鎖的粒度
  • 死鎖檢測
 
實現Callable介面與實現Runnable介面的區別:
  • call()方法有返回值,run()方法沒有
  • call()方法可以拋異常,外層可捕獲
  • Callable支援泛型
 

多執行緒:

 
  PS:需要平衡用多執行緒所帶來效能的提升度與多執行緒環境下執行緒間上下文切換帶來的開銷以及執行緒安全、程式碼複雜度等因素,考慮是否有必要用多執行緒。  
 
常見多執行緒使用場景:
  • 常見的瀏覽器、Web服務(現在寫的web是中介軟體幫你完成了執行緒的控制),web處理請求,各種專用伺服器(如遊戲伺服器)
  • tomcat,tomcat內部採用多執行緒,上百個客戶端訪問同一個WEB應用,tomcat接入後就是把後續的處理扔給一個新的執行緒來處理,這個新的執行緒最後呼叫我們的servlet程式,比如doGet或者dpPost方法
  • FTP下載,多執行緒操作檔案
  • 分散式計算
  • 後臺任務:如定時向大量(100W以上)的使用者傳送郵件;定期更新配置檔案、任務排程(如quartz),一些監控用於定期資訊採集
  • 自動作業處理:比如定期備份日誌、定期備份資料庫
  • 非同步處理:如發微博、記錄日誌
  • 頁面非同步處理:比如大批量資料的核對工作(有10萬個手機號碼,核對哪些是已有使用者)
  • 資料庫的資料分析(待分析的資料太多),資料遷移
  • 多步驟的任務處理,可根據步驟特徵選用不同個數和特徵的執行緒來協作處理,多工的分割,由一個主執行緒分割給多個執行緒完成
 
Synchronized:  底層 對映成位元組碼會增加兩個指令:monitorenter、monitorexit
 
  •     修飾非靜態方法和程式碼塊鎖(this)是競爭同一把鎖,都是物件鎖,鎖的都是呼叫方法的物件例項,當new兩個物件例項分別去呼叫方法時,競爭的不是同一把鎖
  •    修飾靜態方法和 程式碼塊 鎖(Class)是競爭同一把鎖,都是類鎖,鎖的是呼叫方法的類,無論是否用不同物件例項去呼叫方法競爭的都是同一把鎖。
  •    作用於 程式碼塊,對括號裡配置的物件(同步監視器)加鎖
     
 
鎖升級過程: ( jdk 1.6之前 synchronized 關鍵 字只表示重量級鎖,1.6之後區分為 偏向鎖、輕量級鎖、重量級鎖)
 
        預設是無鎖狀態,當執行緒第一次獲取鎖時(通過CAS競爭鎖),鎖為升級為偏向鎖,物件頭上會標記執行緒ID,表示這個物件偏向於當前執行緒,當偏向的執行緒銷燬後鎖會被重置為無鎖狀態,當產生鎖競爭時,偏向鎖會升級為輕量級鎖,輕量級鎖依賴CAS( compare and swap)來獲取鎖,執行緒會在一定次數內通過自旋操作重複嘗試獲取鎖,若獲取失敗則會升級會重量級鎖。
 
wait()/notify()/notifyAll()
        執行緒間通訊的方法,只能用於同步程式碼塊或同步方法中,且呼叫者只能是同步程式碼塊或同步方法中的同步監視器
 
wait()/sleep():
  • 都讓執行緒進入阻塞狀態
  • sleep()是Thread類的方法,wait()是Object類的方法
  • sleep()可在任何地方呼叫,wait()只能在同步程式碼塊或同步方法中呼叫
  • sleep()不會釋放鎖,wait()會釋放鎖
 
Concurrent(JDK1.5之後提供)包下常用類:
 
       volatile :  輕量級鎖, 保證修飾共享變數在多執行緒環境下的可 見性,會禁止JVM對指令進行重排序,但不保證原子性
                     當修飾共享變數被寫時,變數值會立即從本地記憶體重新整理到主記憶體中,當讀共享變數時,會直接從主記憶體中讀取,本地該變數會被置為無效
 
     AtomicInteger/AtomicBoolean/AtomicLong等基本型別原子類:
                既保證可見性也保證原子性:用volatile來修飾變數value保證可見性,對變數的操作通過CAS(compare and swap)保證原子性
       
       CAS演算法(樂觀鎖)保證原子性:CAS是一種無鎖的非阻塞演算法實現, CAS 操作中包含三個運算元 —— 需要讀寫的記憶體值(V)、進行比較的預期值(A)和擬寫入的新值(B)。如果記憶體值V與預期值A相等,那麼會將值更新為新值B。否則不做任何操作。
  • 用於讀多的場景,即很少發生衝突,避免多次retry(減少自旋)
  • 但會產生ABA問題:通過引入版本號來解決
        
     ConcurrentHashMap: 
                    介於HashMap和HashTable之間,內部採用“鎖分段”( DEFAULT_CONCURRENCY_LEVEL = 16, 預設分16段即支援併發數為16 ) 機制代替HashTable的獨佔鎖,效能更好。
 
         PS: JDK1.8的實現已經拋棄了Segment分段鎖機制,利用CAS+Synchronized來保證併發更新的安全。資料結構採用:陣列+連結串列+紅黑樹,JDK1.7是Segment+HashEntry
 
                與HashTable的區別:
  •        HashTable只能序列訪問,效率低。ConcurrentHashMap可並行訪問,不同執行緒可訪問不同的分段(segment)
    • HashTable是用synchronized來鎖,ConcurrentHashMap分段鎖是用 ReentrantLock
 
     CopyOnWriteArrayList:
      CopyOnWriteArraySet:通過 CopyOnWriteArrayList實現
                  共同特性:
    • 寫時複製策略, 通過 ReentrantLock控制併發
    • 適用於讀多寫少的場景(讀操作並沒有加鎖)
    • 只能保證資料最終一致性,並不能保證資料實時一致性,當一個執行緒在寫資料時,另一執行緒讀只能讀到當時已寫的那部分資料,還未寫完的資料無法讀到。
 
     Lock(介面):  唯一實現類 ReentrantLock,基於volatile和CAS實現
                與synchronized區別: 
  •        synchronized是JAVA關鍵字,會自動釋放鎖,Lock是一個介面,需手動釋放鎖
    • 都是可重入的獨佔鎖
    • synchronized適用於少量程式碼同步的情況,lock適用於大量程式碼同步的情況
    • synchronized是非公平鎖, ReentrantLock可設定非公平鎖或公平鎖,預設非公平鎖
 
     Condition: 用來控制執行緒間通訊的類
      • 必須配合Lock使用,可用來實現等待/通知模式
      • await()/signal()/signalAll()可替代Object的wait()/notify()/notifyAll(),只能在lock.lock()和lock.unlock()之間使用
      • signal() 可喚醒指定執行緒,而notify()只能喚醒隨機執行緒
 
       ReadWriteLock(介面): 實現類 ReentrantReadWriteLock 讀寫鎖,適用於寫寫/讀寫需要互斥而讀讀不需要互斥的場景,避免在任何情況下都使用獨佔鎖,提高效能。ReadLock/WriteLock實現了Lock,用法同 ReentrantLock。
 
 

執行緒池

 
                     執行緒複用、減少建立和銷燬執行緒帶來的開銷、可用來控制併發數量,也能保證建立的最大執行緒數不超過系統承受能力
 
執行緒數量的設定:
  •     CPU密集型:執行緒數=CPU數( Runtime.getRuntime().availableProcessors()
  •     IO密集型:   執行緒數=任務數
        
 
執行緒池建立:
                    
                     ExecutorService service = new ThreadPoolExecutor int corePoolSize, int maximumPoolSize, long keepAliveTime,
                                                          TimeUnitunit, BlockingQueue <Runnable> workQueue,
                                                          ThreadFactory threadFactory, RejectedExecutionHandler handler);
       corePoolSize : 執行緒池核心池的大小。
  maximumPoolSize : 執行緒池的最大執行緒數。
  keepAliveTime : 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。
  unit : keepAliveTime 的時間單位。
  workQueue : 用來儲存等待執行任務的佇列。
  threadFactory : 執行緒工廠。
  handler  拒絕策略。
 
阻塞佇列:
  ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
  LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列(不指定佇列大小時為無界佇列)。
       SynchronousQueue: 一個不儲存元素的阻塞佇列。
  PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
  DelayQueue: 一個使用優先順序佇列實現的無界阻塞佇列。
  LinkedTransferQueue: 一個由連結串列結構組成的無界阻塞佇列。
  LinkedBlockingDeque: 一個由連結串列結構組成的雙向阻塞佇列。
  拒絕策略:
  ThreadPoolExecutor.AbortPolicy: 丟棄任務並丟擲RejectedExecutionException異常。 (預設)
  ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
  ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務。(重複此過程)
  ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務。
 
PS: 執行緒池用完需要手動關閉 shutdown()
 
ForkJoinPool:(JDK1.7之後提供)  https://blog.csdn.net/f641385712/article/details/83749798
 

ThreadLocal: 

 
        設計:每個執行緒Thread都維護一個ThreadLocalMap(ThreadLocal有一個靜態內部類ThreadLocalMap,這個map維護一個Entry陣列,Entry的key為ThreadLocal物件,value為變數值)。所以變數的設定和獲取都是依賴當前執行緒的ThreadLocalMap來操作的。
 
         記憶體洩漏分析:在用 執行緒池管理執行緒的情況下,由於ThreadLocalMap是依賴於執行緒的,當執行緒存活時,即使key(ThraedLocal)的外部引用已經沒了 (強引用鏈①會斷開),若Entry沒有被手動刪除則value依舊是被Entry所引用的,所以不會被GC,同時它也不可被訪問(ThreadLocal用完被GC導致Entry的key為null),因此造成記憶體洩漏
        記憶體洩漏 根本原因:ThreadLocal的生命週期與當前執行緒Thread的生命週期一樣長,若沒有手動刪除Entry就會發生記憶體洩漏
        造成記憶體洩露的兩個前提:1、執行緒使用完未銷燬(不好控制,尤其是在用執行緒池的情況下) 2、未手動刪除Entry(相對容易控制,因此在使用ThreadLocal時記得手動remove())
        ThreadLocal如何解決的:在ThreadLocal的set()和get()方法中對髒Entry自動進行清理
        為什麼Entry的key採用弱引用:將ThreadLocal物件的生命週期與執行緒生命週期解綁,即ThreadLocal不在被使用時是可以被回收的,那麼在下一次再對ThreadLocal操作時Entry會因為key為null被清理,從而避免記憶體洩漏。也算是未手動清理使用完的Entry的一道保障
 
 
        
        常用場景:用來解決資料庫連線、Session管理
        特點:
    • 在特定場景下可替代 synchronized通過無鎖方式解決共享變數執行緒安全問題,提高效能,提高併發量
    • 可解決引數多層傳遞的高耦合問題
 
        於 synchronized區別:
        • synchronized 只能序列處理資料,時間換空間,保證多執行緒下共享變數的同步
        • ThreadLocal   可並行處理資料,空間換時間,為每個執行緒提供一個共享變數的副本,保證多執行緒下的資料隔離
 
        PS:ThreadLocalMap是如何解決雜湊衝突的?與HashMap解決雜湊衝突方式有何區別?
        
 

相關文章