Java併發之原子變量及CAS算法-下篇

Java併發之原子變量及CAS算法-下篇


概述

本文主要講在Java併發編程的時候,如果保證變量的原子性,在JDK提供的類中是怎麼保證變量原子性的呢?。對應Java中的包是:java.util.concurrent.atomic包下。因為涉及到了CAS算法,需要對CAS算法講解及CAS算法三個問題怎麼解決以及和Synchroized比較。文章比較長,所以就分為上下兩個篇幅講解。本文是上篇《Java併發之原子變量及CAS算法-下篇》

本文是《凱哥分享Java併發編程之J.U.C包講解》系列教程中的第四篇。如果想系統學習,凱哥(kaigejava)建議從第一篇開始看。

在上一篇中,我們講解了i++在多線程下變量原子性問題以及怎麼解決。在本篇中,我們詳細講解什麼是CAS算法?CAS和Synchroized區別是什麼?以及CAS算法產生的問題及怎麼解決。

CAS簡介

什麼是CAS算法?

CAS:Compare-And-Swap即比較並交換的意思。

CAS包含了三個操作的數據:

主內存中的變量值:V

預估值(可以理解為原來舊的值):A

更新值(操作後,要更新的值):B

CAS的特點:

當且僅當預估值A=內存值V的時候,才會將V的值更新為B。否則也不操作。

V==A;V=B;

Java併發之原子變量及CAS算法-下篇


使用CAS算法多線程操作的時候,有且僅有一個線程可以操作成功,其他線程都會操作失敗。失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。

失敗的線程採用自旋來進行嘗試的。

我們以AtomicInteger對象中的getAndIncrement()方法為例來看看:

Java併發之原子變量及CAS算法-下篇


模擬後的源碼:

Java併發之原子變量及CAS算法-下篇


CAS VS Synchroized比較

那麼CAS的算法的效率為什麼會比Synchronized的效率高呢?

Synchroized是阻塞算法的;而CAS是非阻塞的,採用的是樂觀鎖技術。

因為阻塞算法是CPU切換的,而CAS是CPU指令操作。CPU切換時間相對於CPU指令操作來說時間更長。所以使用CAS算法的線程比使用Synchroized的效率高。

CAS的優缺點

優點:一種線程同步的解決方案,使用CAS就可以不用加鎖來實現線程的安全性。

缺點:

1:只能保證一個共享變量的原子操作;

2:循環時間長,開銷很大;

3:會產生ABA問題。

缺點解決方案:

缺點一:

當對一個共享變量操作的時候,可以使用帶有自旋(循環)的CAS方法來保證原子性操作,但是如果是多個變量共享的時候,可以封裝到對象中或者是使用鎖來保證原子性。

缺點二:

如果採用自旋的CAS方式來保證原子性,會一直進行嘗試。如果時間太長的話,對CPU來說也會帶來很大開銷的。

缺點三:ABA問題

何為ABA問題?

如線程A修改共享變量值為A;線程B修改值為B。後來共享變量有被修改成了A,這種情況下CAS算法操作就會誤認為共享變量A沒有別修改過。這就是CAS算法的“漏洞”。

舉個很簡單的例子:

Java併發之原子變量及CAS算法-下篇


解決ABA問題

看到這裡大家或許心裡會想,我Kao,這不就是一個坑嗎?JDK埋下的坑!既然有這個坑,那還敢用嗎??淡定,保持淡定點。你能想到的問題,JDK開發者也能想到。所以補救辦法就是:

Java併發之原子變量及CAS算法-下篇


注意:是AtomicStampedReference。雖然和AtomicReference這個類有點像。但是不一樣。

查看源碼註釋:

Java併發之原子變量及CAS算法-下篇


Java併發之原子變量及CAS算法-下篇


簡單理解,就是這個類添加了一個版本號。,每次操作都對版本號進行自增,那每次CAS不僅要比較value,還要比較stamp,當且僅當兩者都相等,才能夠進行更新。

具體怎麼操作的呢?

Java併發之原子變量及CAS算法-下篇


在初始化的時候,就定義了pair對象。

在compareAndSet的時候,會對版本號進行比較。如下圖:

Java併發之原子變量及CAS算法-下篇


講明白了CAS原理之後,我們來修改i++的問題。使其成為保證原子性:

很簡單隻需要修改兩行代碼即可:

1:聲明變量的時候使用AtomicInteger對象:

private AtomicInteger shardData = new AtomicInteger(0);

new AtomicInteger(0)其中的0可以不用寫

2:修改i++的方法:

return shardData.getAndIncrement();

Java併發之原子變量及CAS算法-下篇


這樣就可以了。

總結

Java中保證變量原子性使用的是current.atomic包下的對象來實現的。

如何保證原子性呢?

1:變量都是用volatile關鍵字修飾後,保證了內存的可見性;

2:使用CAS算法,保證了原子性。

Java併發之原子變量及CAS算法-下篇


Synchroized VS volatile VS CAS

在上一篇文章中我們知道了Volatile只能保證變量共享變量在內存中的可見性;不互斥;不能保證原子性;

在本篇中,我們知道了CAS是非阻塞的使用樂觀鎖技術來實現原子性。但是會產生其他問題,不過也可以解決。

Synchroized是阻塞性算法的實現。具有互斥性


分享到:


相關文章: