Java併發編程和高併發基礎三

七 多線程高級類

java.util.concurrent包下的類,callable也是在這個包下的。

解決原理中出現的問題:可見性 原子性 有序性

7.1 synchronized,ReentrantLock

7.1.1 synchronized

jvm級別,不會出現死鎖
是一種同步鎖,它修飾的對象有以下幾種:
1、修飾代碼塊,被修飾的代碼塊稱爲同步語句塊。其作用的範圍時大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象。(當沒有明確的對象作爲鎖,只是想讓一段代碼同步,可以使用this,在run()方法中)。
2、修飾一個方法,被修飾的方法稱爲同步方法,其作用的範圍時整個方法,作用的對象是調用這個方法的對象。
synchronized關鍵字不能繼承
3、修飾一個靜態方法,其作用的範圍時整個靜態方法,作用的對象是這個類的所有對象。
public synchronized static void method() {
// todo
}
4、修飾一個類,其作用的範圍是synchronized後面括起來的部分,作用的對象是這個類的所有對象。
在這裏插入圖片描述

7.1.2 ReentrantLock

  • 分爲公平鎖和非公平鎖(默認)

公平鎖表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,即先來先得FIFO先進先出順序。
非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得鎖的,和公平鎖不一樣的,這種方式可能造成某些線程一直拿不到鎖。
trylock(),unlock(),lock()
例子:

Lock lock = new ReentrantLock();
lock.lock();
try{
//可能會出現線程安全的操作
}finally{
//一定在finally中釋放鎖
lock.ublock();
}
  • condition類

7.1.3 Lock和synchronized對比

從性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭非常激烈時,此時Lock的性能要遠遠優於synchronized。

7.2 ThreadLocal

7.3 volatile

通過內存屏障和禁止重排序優化來實現。

  1. 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
  2. 禁止進行指令重排序。
  3. 但是不能保證原子性。
    內存屏障提供了以下三個功能:
    1)確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
    2)它會強制將對緩存的修改操作立即寫入主存;
    3)如果是寫操作,它會導致其他CPU中對應的緩存無效。
    在這裏插入圖片描述
    例子:
volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            
 
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

synchronized關鍵字是防止多個線程同時執行一段代碼,會影響執行效率,雖然volatile關鍵字無法替代synchronized關鍵字,因爲volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下兩個條件:
1)對變量的寫操作不依賴於當前值
2)該變量沒有包含在具有其他變量的不變式中。

7.4 countdownlatch,CyclicBarrier

7.5 semaphore

7.6 ReentrantReadWriteLock,StampedLock

7.7 原子類

八 線程池

爲什麼使用線程池?
每次new Thread性能差;
線程缺乏統一的管理,可能無限制創建線程,導致佔用系統過多資源而死機和OOM;
缺乏功能:如定時執行;

8.1 ThreadPoolExecutor

8.1.1 初始化參數講解:

  1. corePoolSize:核心線程數,默認核心線程會一直存活,即使處於閒置狀態也不會受keepAliveTime限制。
  2. maximumPoolSize:線程池所能容納的最大線程數,超過這個數的線程將被阻塞。當任務隊列爲沒有設置大小的LinkedBlocking時,這個值無效。
  3. keepAliveTime:非核心線程的閒置超時時間,超過這個線程就會被自動回收。
  4. unit:指定keepAliveTime的單位,TimeUnit.SECONDS。將allowCoreThreadTimeOut設置爲true時對corePoolSize生效。
  5. workQueue:線程中的任務隊列,阻塞隊列
  • SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等待另一個線程調用移除操作
  • LinkedBlockingQueue:無界緩存的等待隊列。基於鏈表結構的阻塞隊列,FIFO,吞吐量通常高於ArrayBlockingQueue。靜態工廠方法
  • ArrayBlockingQueue :使用較少。是一個基於數組結構的有界阻塞隊列,此隊列FIFO原則對元素進行排序。
  • Executors.newFixedThreadPool()使用這個隊列。
  1. threadFactory:線程工廠,主要用來創建線程
  2. RejectedExecutionHandler:拒接策略,表示拒絕處理任務的策略
    ThreadPoolExecutor.AbortPolicy:RejectedExecutionException異常。丟棄任務並拋棄,默認的。
    ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
    ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
    ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

總結:處理任務的優先級,核心線程數、任務隊列、最大線程數,如果三者都滿了,使用handler處理被拒絕的任務。
例子:這樣的過程說明,並不是先加入任務就一定會先執行。假設隊列大小爲 10,corePoolSize 爲 3,maximumPoolSize 爲 6,那麼當加入 20 個任務時,執行的順序就是這樣的:首先執行任務 1、2、3,然後任務 4 ~ 13 被放入隊列。這時候隊列滿了,任務 14、15、16 會被馬上執行,而任務 17~20 則會拋出異常。最終順序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13

監控系統裏可以用,統計一下每分鐘

  1. getTaskCount:線程池已執行和未執行的任務總數
  2. getCompletedTaskCount:已完成的任務數量
  3. getPoolSize:線程當前的線程數量
  4. getActiveCount:當前線程中正在執行任務的線程數量

8.1.2 線程池的狀態

    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

在這裏插入圖片描述
在這裏插入圖片描述
幾個重要的方法
execute(),submit(),shutdown(),shutdownNow()
execute()實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,提交到線程池去執行。
submit()是ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,ThreadPoolExecutor並沒有對其重寫。有返回值Future。execute+Furture
shoutdown():關閉線程池,等待任務都執行完畢
shoutdownNow():關閉線程池,不等待任務都執行完畢

8.1.3 Futrue

get方法:獲取計算結果,一直會等待
cancel方法:還沒計算完,可以取消計算過程
isDone方法:判斷是否計算完
isCancelled方法:判斷計算是否被取消

8.2 Executors

創建線程池的工具

  1. Executors.newCachedThreadPool
  2. Executors.newFixedThreadPool 指定併發數
  3. Executors.newScheduledThreadPool
  4. Executors.newSingleThreadPool 單線程執行

8.3 Timer+TimerTask

8.4 線程池合理配置

  1. cpu密集型任務,就需要儘量壓榨cpu,參考值設置爲NCPU+1
  2. IO密集型的任務,參考值設置爲2*NCPU
    任務計算的時間和任務的調度時間差不多時,就不要用線程池了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章