松花皮蛋的黑板報
  • 分享在京東工作的技術感悟,還有JAVA技術和業內最佳實踐,大部分都是務實的、能看懂的、可復現的

掃一掃
關注公眾號

JAVA并發編程中的安全性、活躍性和性能問題

博客首頁文章列表 松花皮蛋me 2019-03-17 11:40

保證線程安全本質上是保證線程同步,實際上就是線程間通信問題,線程通信常見方式有信號量、管道、共享內存、消息隊列、socket,java線程通信主要使用共享內存,那么首先需要關注的是可見性、有序性、原子性問題,然后就是活躍性問題和性能問題。

一、原子性、可見性和有序性

1、基本概念

  • 可見性:jvm對象變量的修改存在從Heap加載到Heap更新的過程,一個線程對共享變量的修改,另外一個線程能夠立即看到,也就是保證CPU緩存和內存一致
  • 原子性:線程切換時,保證一個或多個操作在cpu執行的過程中不被中斷的特性
  • 有序性: jvm指令重排和多線程代碼交替串行執行中,保證程序執行的順序按照代碼的先后順序執行,否則可能會導致線程獲取到一個未初始化的對象

2、方法:

  • 保證可見性:按需禁用緩存,包括volatile、synchronized、final
  • 保證原子性:互斥鎖,比如synchronized修飾的臨界區是互斥的
  • 保證有序性:按需禁用編譯優化,比如synchronized等,編譯器通過在操作前后各插入一些內存屏障禁止重排序優化
特性volatile關鍵字synchronized關鍵字Lock接口Atomic變量
原子性無法保障可以保障可以保障可以保障
可見性可以保障可以保障可以保障可以保障
有序性一定程度保障可以保障可以保障無法保障

3、Happens-Before規則

前面一個操作的結果對后續操作是可見的。

  • 程序的順序性規則
  • volatile變量規則:對一個volatile變量的寫操作先行于后面對這個變量的讀操作
  • 傳遞性
  • 管種中鎖的規則:一個unlock操作先行于后面對同一個鎖的lock操作
  • 線程start規則:主線程A啟動子線程B后,子線程B能夠看到主線程在啟動子線程B前的操作
  • 線程join規則:主線程A等待子線程B執行完成,當子線程B完成后,主線程A能看到子線程B的操作
  • 線程中斷規則:對線程的中斷操作先行發生于被中斷線程的代碼檢測到中斷事件的發生
  • 對象終結規則:一個對象的初始化完成先行于它的finalize方法的執行

二、活躍性問題,包括死鎖、活鎖和饑餓性

1、死鎖

爭奪資源而造成互相等待的情況。可以通過資源一次性分配、可剝奪資源、資源有序分配法進行預防,銀行家算法進行避免,在分配資源前先看清楚,分配后不會造成死鎖就分配,否則不分配

2、活鎖

線程雖然沒有阻塞但是仍然會存在執行不下去的情況,比如相互謙讓,處理辦法是相撞后各自等待一個隨機值。

3、饑餓性

線程無法訪問到所需資源而無法繼續執行下去,可以通過保證資源充足或者公平地分配資源(先來后到的公平鎖),防止持有鎖的線程長時間執行。

三、性能問題

鎖的過度使用可能導致串行化的范圍變大,造成吞吐量、延遲、并發量變差,可以使用下面的兩種方案解決

1、無鎖,比如寫入時復制、線程本地存儲等

Copy-on-write最常見的就是iterator的修改,使用了CopyOnWriteArrayList,在操作新元素時不直接操作原容器,而是先復制一個快照,對這個快照進行操作,在操作結束后再將原容器的引用指向新引用

2、減少鎖持有時間,增加并行度,比如concurrentHashMap的分段鎖,讀寫鎖ReentrantReadWriteLock

concurrentHashMap將內部分成多個segment數組,segment通過繼承ReentrantLock加鎖。segment里面則是hashEntry數組,hash值相同的條目以鏈表的形式存放,當數目達到一定時采用紅黑樹存放。另外hashEntry內部使用volatile的value字段來保證可見性

黑龙江6+1开奖结果查询