JAVA系列-併發

1. 鎖

1) Lock與synchronized的區別

  • Lock是接口,而synchronized是java中的關鍵字;
  • synchronized不會導致死鎖現象發生,而Lock可能造成死鎖現象;
  • Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行;
  • 通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到;
  • Lock可以提高多個線程進行讀操作的效率;
  • 在性能上來說,如果競爭資源不激烈,兩者的性能差不多,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。
總結:這兩者的使用在具體使用時要根據適當情況選擇

2)公平鎖

  • ReentrantLock 設置公平鎖true

3) 非公平鎖

  • ReentrantLock 默認非公平鎖,吞吐量比公平鎖大

4) 可重入鎖(遞歸鎖)

  • ReentrantLock
  • synchronized

5) 獨佔鎖(寫鎖)/獨享鎖(讀鎖)/互斥鎖

獨佔鎖:指該鎖只能被一個線程所有;

  • ReentrantLock
  • Synchronized

共享鎖:指該鎖可被多個線程所持有;

  • ReentrantReadWriteLock其讀鎖是共享鎖,寫鎖是獨佔鎖

讀鎖的共享鎖可保證併發讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。

6)自旋鎖

嘗試獲取鎖的線程不會立即阻塞

JAVA系列-併發

6)分佈式鎖

(1)具備特性

  • 互斥性
  • 可重入性
  • 鎖超時高效
  • 高可用
  • 支持阻塞和非阻塞
  • 支持公平鎖和非公平鎖

(2)實現分佈式鎖的幾種方案

  • 數據庫實現(悲觀與樂觀鎖)
  • 基於Zookeeper實現
  • 基於Redis實現
  • 自研分佈式鎖:如谷歌的Chubby
分佈式鎖方案比較:
  • 理解難易程序:數據庫 < 緩存 < zookeeper
  • 實現複雜度:zookeeper <= 緩存 < 數據庫
  • 性能:緩存 > zookeeper >= 數據庫
  • 可靠性:zookeeper > 緩存 > 數據庫

(3)基於Redis分佈鎖

實現Redisson

7. 死鎖

死鎖的概念:

指兩個或兩上以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力干涉那它們都無法推進下去,如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。

JAVA系列-併發

產生死鎖的原因:

  • 系統資源不足
  • 進程運行推進的順序不合適
  • 資源分配不當

查詢java死鎖現象的常用命令

  • jps命令定位進程號: jps -l
  • jstack找到死鎖查看: jstack 進程號

2. 多線程

1)線程池

線程池的幾大參數(7大參數)

  • corePoolSize: 線程池中常駐核心線程數
  • maximumPoolSize:線程池能夠容納同時執行的最大線程數,此值必須大於等於1
  • keepAliveTime:
    多餘的空閒線程的存活時間(當前線程池數量超過corePoolSize時,當空閒時間達到keepAliveTime值時,多餘空閒線程會被銷燬直到只剩下corePoolSize個線程為止)
  • unit: keepAliveTime的單位
  • workQueue: 任務隊列,被提交但尚未被執行的任務
  • threadFactory: 表示生成線程池中工作線程的線程工廠,用於創建線程一般用默認的即可
  • handler: 拒絕策略,表示當隊列滿了並且工作線程大於等於線程池的最大線程數(maximumPoolSize)
    • AbortPolicy
    • DiscardPolicy
    • CallerRunsPolicy
    • DiscardOldestPolicy

線程池的底層工作原理

JAVA系列-併發


JAVA系列-併發

JAVA系列-併發

線程池的拒絕策略

JDK內置的拒絕策略(均實現了RejectedExecutionHandler接口)
  • AbortPolicy(默認): 直接拋出RejectedExecutionException異常阻止系統正常運行;
  • CallerRunsPolicy:“調用者運行”一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者,從而降低新任務的流量;
  • DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交當前任務;
  • DiscardPolicy:直接丟棄任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種方案;

選擇哪一個線程池

阿里java手冊說明

【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。

說明:線程池的好處是減少在創建和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問 題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

說明:Executors 返回的線程池對象的弊端如下:

  • FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
  • CachedThreadPool: 允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

合理的配置線程池(maximumPoolSize

CPU密集型:
  • 說明:CPU密集一般該任務需要大量的運算,而沒有阻塞,CPU一直全速運行,CPU密集任務只有在真正的多核CPU上才可能得到加速(通過多線程)
  • 建議:CPU密集型任務配置儘可能少的線程數量
  • 參考公式:CPU核數+1個線程的線程池
IO密集型:
  • 說明:IO密集型任務線程並不是一直在執行任務,則應配置儘可能多的線程IO密集型時,大部分線程都阻塞,故需要多配置線程數
  • 參考公式:CPU核*2CPU核數 / (1 - 阻塞係數) 阻塞係數在0.8~0.9之間

2) 線程安全

(1) 集合

a. 線程不安全:
  • ArryList
  • HashSet
  • HashMap

多線程寫情況下會拋ConcurrentModificationException異常

b. 線程安全
  • Vector
  • collections.synchronizedxxx
  • CopyOnWriteArrayxxx
  • ConcurrentMap
c. 解決方案:
  • 同步向量:new Vector()
  • 加鎖數組:collections.synchronizedList(new ArrayList())
  • 寫時複製:new CopyOnWriteArrayList()

3. 併發相關概念

1)volatile

Java虛擬機提供的輕量級的同步機制

  • 可見性
  • 不保證原子性
  • 禁止指令重排

2) JMM(Java內存模型)

要求的特性

  • 可見性
  • 原子性
  • 有序性

3)CAS (Compare and set )

AtomXX-相關類

缺點:

  • 1. 循環時間長,開銷很大(主要是CPU開銷)
  • 2. 只能保證一個共享變量的原子操作
  • 3. 引發ABA問題

4)ABA問題

  • 原子引用(AtomicReference):時間戳的原子引用(AtomicStampedReference)
  • 規避ABA問題

5)ThreadLocal

  • ThreadLocal 面試六連問,你能 Hold 住嗎?
  • 深入剖析ThreadLocal
  • 深入分析 ThreadLocal 內存洩漏問題
  • 使用 ThreadLocal 變量的時機和方法


分享到:


相關文章: