1. 程式人生 > >淺談Java線程安全

淺談Java線程安全

讀寫操作 安全 上下文 指令重排序 悲觀鎖 sync 競爭 put 自動

在多線程編程中我們需要同時關註可見性、順序性和原子性。

  • 可見性。對於共享數據,一個地方如果改變了該數據,其它地方要立馬知道。
  • 原子性。類似於數據庫事務的原子性,一次操作要全部執行,要麽全部不執行。
  • 順序性。程序在執行的時候,程序的代碼執行順序和語句的順序是一致的。

一、原子性

  • 使用鎖
  • 使用同步

鎖:保證同一時間只有一個線程拿到,也保證了同一時間只有一個線程執行申請鎖和鎖釋放的代碼。

同步:與鎖類似的是同步方法或者同步代碼塊。使用非靜態同步方法時,鎖住的是當前實例, 使用靜態同步方法時,鎖住的是該類的class對象。

volatile:對volatile單次的讀寫可保證原子性。

二、可見性

計算機存儲分為主內存和高級緩存。CPU寫速度要遠比從主內存讀數據再寫回主內存的速度快,直接對主內存操作的話,會引發各種問題。因此設計了交互更快的高級緩存。CPU與高級緩存進行交互,然後高級緩存再與主內存進行交互。由於CPU是多核的,一個核上A的線程共享變量進行寫操作的時候,另一個核B的變量可能正在讀,但是讀取的是與之對應的緩存中的數據,因此讀到的不是最新A寫入的變量,這就是可見性問題的大概描述。解決辦法是使用volatile變量。

  • 修改volatile變量時會強制將修改後的值刷新到內存中
  • 修改volatile變量後其它線程內存的變量立即失效,需從主內存讀取

三、有序性

程序執行的順序按照代碼的先後順序執行。如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。上述內容為指令重排序。

1.內存屏障

保證有序性的方法為內存屏障。

:針對跨處理器的讀寫操作,它被插入到兩個指令之間。作用是禁止對編譯器和處理器進行重排序。

  • MonitorEnter
  • Load Barrier
  • Acquire Barrier
  • Critical Area
  • Release Barrier
  • MonitorExit
  • Store Barrier

把屏障分為兩類,加載屏障和存儲屏障

  • 加載屏障:刷新處理器緩存
  • 存儲屏障:沖刷處理器緩存

保證有序性,還有個happens-before原則。

  • 如果一個操作happens-before另一個操作,那麽第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
  • 兩個操作之間存在happens-before關系,並不意著一定要按照happens-before原則制定的順序來執行。如果重排序之後的執行結果與按照happens-before原則執行的結果一致,那麽這種重排序合法。

四、題外話,Java中的各種鎖

1、樂觀鎖/悲觀鎖

  • 悲觀鎖:對數據的並發操作一定采取加鎖行為
  • 樂觀鎖:對數據的並發性操作不加鎖。幾種方法。一種是CAS(compare and swap)即比較替換,早期的做法是比較內容值是否和預期值一樣,如果一樣則寫入新值,不一樣就放棄。這存在ABA的問題,ABA問題就是當內容值和預期值一樣的時候,可能內容值已經被修改過了,可能修改了兩次又改回為了預期值。解決:CompareAndSet方法作用首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,若都相等則更新最新引用和最新值。

2、分段鎖

ConcurrentHashMap中分段鎖稱為Segment,它即類似於HashMap,當Put時並不是對整個HashMap進行加鎖,而是先通過HashCode來知道他要放在哪一個分段中,然後對這個分段進行加鎖,只要不是放在一個分段,則就可實現分段加鎖。分段鎖是ReentrantLock。

3、偏向鎖/輕量級鎖/重量級鎖

  • 偏向鎖,一段同步代碼一直被一個線程訪問,那麽該線程會自動獲取鎖,降低獲取鎖的代價。
  • 輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程訪問,偏向鎖會被升級為輕量級鎖。其他線程會通過自旋形式獲鎖,不會阻塞,提高性能。(鎖的自旋,與普通鎖不同,一個線程A在獲得普通鎖後,如果再有線程B試圖獲取鎖,那麽這個線程B將會掛起。如果兩個線程資源競爭不是特別激烈,而處理器阻塞一個線程引起的線程上下文切換的代價高於等待資源的代價,那麽可以不放棄CPU的時間片,而是原地等,直到鎖的持有者釋放該鎖)
  • 重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋的,單自旋不會一直持續下去,當自旋一定次數,還沒有獲取鎖,就會阻塞,該鎖變為重量級鎖,其他申請的線程進入阻塞。

4、公平鎖/非公平鎖

  • 公平鎖:過個線程按照申請鎖的順序來獲取鎖,隊列實現
  • 非公平鎖:多個線程獲取鎖的順序並不是按照申請鎖的順序

5、獨享鎖/共享鎖

  • 獨享鎖:該鎖一次只能被1個線程所持有:ReentrantLock,Synchronized
  • 共享鎖:該鎖可被多個線程所持有:ReentrantLock,Reed共享,Write獨享

淺談Java線程安全