更新時間:2019-09-07 09:00:00 來源:動力節點 瀏覽2781次
我們在過去傳統的編程過程中,我們定義一個內存中的數據類型,用于將數據放入內存中時,只考慮被單個線程訪問,所以不需要考慮太多,因為它是安全的。
但當數據被多個線程同時操作時,就會出現問題。我們必須同步多個線程對數據的訪問,以確保可見性和正確性。
因為JMM模型中對于多線程并發操作時,父線程中共享的資源變量是存儲在父線程的內存空間里的,而作為子線程,各自有各自的線程空間,我們也稱之為工作線程的內存空間,每次操作都會拷貝共享資源的副本到自己的工作線程并操作,在操作完成后,需要將各個工作線程的副本更新會主線程的共享變量中,這個過程造成了各個線程可能讀取的共享變量數值可能不一致問題。同時一個子線程對共享數據的修改,在被更新會父線程空間的變量值時,不能被其它子線程立刻看到,這就出現了操作可見性問題。
比如我們定義一個普通的計數器數據類型Counter類:
這個類在單線程環境中工作是沒有任何問題的,但是當多個線程訪問同一個計數器實例時就完全不能工作了。因為并發操作的每個線程都會拷貝它一個副本到自己的線程空間里進行操作,完成后更新會共享的變量中,這就有可能出現數據不一致問題。
同步鎖操作實現
此時我們可以使用對操作方法使用synchronized關鍵字或者使用volatile關鍵字進行同步的方式來解決問題。
這個進行了同步的類可以很好的解決了多個線程并發訪問它的問題。但是我們知道Synchronized關鍵字背后的實現原理是對修飾的方法進行鎖定處理,而且這個加鎖和解鎖過程并不是一個輕量級的機制,所有存在很多缺點。
當多個線程在試著獲取同一個鎖時,大部分線程都會被掛起,在鎖被釋放時才會被重置回來。
當我們的操作臨界區比較小,這個開銷就會變得非常大,尤其是當鎖經常被獲取并且存在很多爭用時。
另一個缺點在于其它線程在等待鎖被釋放過程中什么也做不了,如果擁有當前鎖的線程執行出現延遲(頁面錯誤或者量子鐘結束等原因),那么其它線程就會出現長時間得不到執行的情況。
原子操作實現原理
為了避免這些問題的出現,Java推出了非鎖定的算法。該算法不使用鎖機制,而且更具可伸縮性和更好的性能。
它使用了底層的機器指令的原子性來確保高層操作的原子性。簡單說來就是將高一層級的操作分解成跟底層原子操作層級對應的操作,從而讓每一次操作不能被中斷或者竊取。
我們知道在處理資源爭用時我們一般有兩種思路:
一種是悲觀模式,就是相信爭用的情況一定會發生,所以預先建立防范措施,其中加鎖就是屬于這種悲觀模式的實現。
另外一種是樂觀模式,我們沿著每次操作都可能會成功也可能會失敗的思路處理,就是如果成功就執行完畢,如果出現失敗就再執行一次。
悲觀模式是不管怎樣我先鎖定資源使用的是一種阻塞思維,而原子操作則是不采用鎖定機制,大家都可以訪問的非阻塞思維。
目前,實際的處理器大都提供了一些指令,它們大大簡化了這種非阻塞算法的實現,其中目前使用最多的操作是比較和交換操作(CAS)。
這種算法的操作原理是它接收三個參數分別為要操作的內存地址,我們預期的當前要操作的數值,我們需要它變成的新值。
它會首先檢查指定內存地址的值是否跟當前我們預期的值相同,如果相同就將它更新為新值,如果跟我們預期的值不同就放棄本次操作,當本次操作完成時,它會返回指定內存地址的值。因此,當多個線程試圖執行CAS操作時,一個線程獲勝,其他線程什么也不做。調用者可以選擇重試或執行其他操作。
其實我們平時經常使用這種比較和設置的操作原理來實現一些其它功能操作,方法原理與CAS完全相同,只是會返回一個布爾值,指示操作是否成功。
可用的原子操作類型
在Java5.0之前,開發人員不能直接使用這個操作,但是在Java5.0中添加了幾個原子變量(int、long、boolean和引用變量)。
int和long版本也支持數字操作。JVM使用了有硬件機器,CAS或者使用鎖的Java實現來編譯這些類從而使之成為更好的操作方式。
AtomicInteger
AtomicLong
AtomicBoolean
AtomicReference
它們都通過compareAndSet()等方法支持CAS操作,所以它們支持多線程訪問,并且比同步操作有著更好的可伸縮性。
這里incrementAndGet()和decrementAndGet()方法是AtomicLong和AtomicInteger類提供的兩個數值操作。
另外還有getAndDecrement(),getAndIncrement(),getAndAdd(inti)和addAndGet()等可以調用的安全操作。
這個版本比同步版本更快,而且線程安全。
最后,我們只使用compareAndSet()方法實現了一個遵循CAS模式的increment()方法。
這看起來有些復雜,但這就是實現非阻塞算法的代價。其思想就是當檢測到沖突時,我們會重試,直到操作成功。
這是非阻塞算法的通用模式,我們在非阻塞流NIO操作中會經常看到這種模式的使用。
自定義復雜原子操作類型
下面我們舉一個實例來實現一個Stack類:
它確實比在這兩個方法上使用synchronized要復雜,但是如果存在爭用,它的性能也會更好,即使沒有爭用的情況下,它的性能通常也是不錯的。
總之,原子變量類是實現非阻塞算法的一種很好的方法,而且也是volatile變量的一個很好的替代方法,因為它們可以提供原子性和可見性。
以上就是動力節點java培訓機構介紹的“深入理解原子性操作,提高多線程高并發性能”的內容,希望對正在學習的你有所幫助,如果有不懂的問題可以登錄動力節點IT培訓官網咨詢在線客服老師。
相關閱讀
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習