日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

并发的核心:CAS 是什么?Java8是如何优化 CAS 的?

發布時間:2025/3/21 java 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发的核心:CAS 是什么?Java8是如何优化 CAS 的? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文轉載自微信公眾號:苦逼的碼農

大家可能都聽說說?Java?中的并發包,如果想要讀懂?Java?中的并發包,其核心就是要先讀懂?CAS?機制,因為?CAS?可以說是并發包的底層實現原理。

今天就帶大家讀懂?CAS?是如何保證操作的原子性的,以及?Java8?對?CAS?進行了哪些優化。

synchronized:大材小用

我們先來看幾行代碼:

public?class?CASTest?{static?int?i?=?0;public?static?void?increment()?{i++;} }

假如有100個線程同時調用?increment()?方法對?i?進行自增操作,i?的結果會是?100?嗎?

學會多線程的同學應該都知道,這個方法是線程不安全的,由于 i++ 不是一個原子操作,所以是很難得到?100?的。

這里稍微解釋下為啥會得不到?100(知道的可直接跳過),?i++?這個操作,計算機需要分成三步來執行。
1、讀取?i?的值。
2、把 i 加 1.
3、把?最終?i?的結果寫入內存之中。

所以,(1)、假如線程?A?讀取了?i?的值為?i?=?0,(2)、這個時候線程?B?也讀取了?i?的值?i?=?0。(3)、接著?A把?i?加?1,然后寫入內存,此時?i?=?1。(4)、緊接著,B也把?i?加?1,此時線程B中的?i?=?1,然后線程?B?把?i?寫入內存,此時內存中的?i?=?1。也就是說,線程?A,?B?都對?i?進行了自增,但最終的結果卻是?1,不是?2.

那該怎么辦呢?解決的策略一般都是給這個方法加個鎖,如下

public?class?CASTest?{static?int?i?=?0;public?synchronized?static?void?increment()?{i++;} }

加了?synchronized?之后,就最多只能有一個線程能夠進入這個?increment()?方法了。這樣,就不會出現線程不安全了。

然而,一個簡簡單單的自增操作,就加了 synchronized 進行同步,好像有點大材小用的感覺,加了 synchronized 關鍵詞之后,當有很多線程去競爭 increment 這個方法的時候,拿不到鎖的方法是會被阻塞在方法外面的,最后再來喚醒他們,而阻塞/喚醒這些操作,是非常消耗時間的。

這里可能有人會說,synchronized?到了JDK1.6之后不是做了很多優化嗎?是的,確實做了很多優化,增加了偏向鎖、輕量級鎖等,??但是,就算增加了這些,當很多線程來競爭的時候,開銷依然很多,

CAS?:這種小事交給我

那有沒有其他方法來代替?synchronized?對方法的加鎖,并且保證?increment()?方法是線程安全呢?

大家看一下,如果我采用下面這種方式,能否保證?increment?是線程安全的呢?步驟如下:

1、線程從內存中讀取?i?的值,假如此時?i?的值為?0,我們把這個值稱為?k?吧,即此時?k?=?0。

2、令?j?=?k?+?1。

3、用?k?的值與內存中i的值相比,如果相等,這意味著沒有其他線程修改過?i?的值,我們就把?j(此時為1)?的值寫入內存;如果不相等(意味著i的值被其他線程修改過),我們就不把j的值寫入內存,而是重新跳回步驟?1,繼續這三個操作。

翻譯成代碼的話就是這樣:

public?static?void?increment()?{do{int?k?=?i;int?j?=?k?+?1;}while?(compareAndSet(i,?k,?j)) }

如果你去模擬一下,就會發現,這樣寫是線程安全的。

這里可能有人會說,第三步的?compareAndSet?這個操作不僅要讀取內存,還干了比較、寫入內存等操作,,,這一步本身就是線程不安全的啊?

如果你能想到這個,說明你是真的有去思考、模擬這個過程,不過我想要告訴你的是,這個 compareAndSet 操作,他其實只對應操作系統的一條硬件操作指令,盡管看似有很多操作在里面,但操作系統能夠保證他是原子執行的。

對于一條英文單詞很長的指令,我們都喜歡用它的簡稱來稱呼他,所以,我們就把 compareAndSet 稱為?CAS?吧。

所以,采用?CAS?這種機制的寫法也是線程安全的,通過這種方式,可以說是不存在鎖的競爭,也不存在阻塞等事情的發生,可以讓程序執行的更好。

在?Java?中,也是提供了這種?CAS?的原子類,例如:

  • AtomicBoolean

  • AtomicInteger

  • AtomicLong

  • AtomicReference

  • 具體如何使用呢?我就以上面那個例子進行改版吧,代碼如下:

    public?class?CASTest?{static?AtomicInteger?i?=?new?AtomicInteger(0);public?static?void?increment()?{//?自增?1并返回之后的結果i.incrementAndGet();} }

    CAS:誰偷偷更改了我的值

    雖然這種?CAS?的機制能夠保證increment()?方法,但依然有一些問題,例如,當線程A即將要執行第三步的時候,線程?B?把?i?的值加1,之后又馬上把?i?的值減?1,然后,線程?A?執行第三步,這個時候線程?A?是認為并沒有人修改過?i?的值,因為?i?的值并沒有發生改變。而這,就是我們平常說的ABA問題

    對于基本類型的值來說,這種把數字改變了在改回原來的值是沒有太大影響的,但如果是對于引用類型的話,就會產生很大的影響了。

    來個版本控制吧

    為了解決這個?ABA?的問題,我們可以引入版本控制,例如,每次有線程修改了引用的值,就會進行版本的更新,雖然兩個線程持有相同的引用,但他們的版本不同,這樣,我們就可以預防?ABA?問題了。Java?中提供了?AtomicStampedReference?這個類,就可以進行版本控制了。

    Java8?對?CAS?的優化。

    由于采用這種?CAS?機制是沒有對方法進行加鎖的,所以,所有的線程都可以進入?increment()?這個方法,假如進入這個方法的線程太多,就會出現一個問題:每次有線程要執行第三個步驟的時候,i?的值老是被修改了,所以線程又到回到第一步繼續重頭再來。

    而這就會導致一個問題:由于線程太密集了,太多人想要修改?i?的值了,進而大部分人都會修改不成功,白白著在那里循環消耗資源。

    為了解決這個問題,Java8?引入了一個?cell[]?數組,它的工作機制是這樣的:假如有?5?個線程要對?i??進行自增操作,由于?5?個線程的話,不是很多,起沖突的幾率較小,那就讓他們按照以往正常的那樣,采用?CAS?來自增吧。

    但是,如果有?100?個線程要對?i?進行自增操作的話,這個時候,沖突就會大大增加,系統就會把這些線程分配到不同的?cell?數組元素去,假如?cell[10]?有?10?個元素吧,且元素的初始化值為?0,那么系統就會把?100?個線程分成?10?組,每一組對?cell?數組其中的一個元素做自增操作,這樣到最后,cell?數組?10?個元素的值都為?10,系統在把這?10?個元素的值進行匯總,進而得到?100,二這,就等價于?100?個線程對?i?進行了?100?次自增操作。

    當然,我這里只是舉個例子來說明?Java8?對?CAS?優化的大致原理,具體的大家有興趣可以去看源碼,或者去搜索對應的文章哦。

    總結

    理解?CAS?的原理還是非常重要的,它是?AQS?的基石,而?AQS?又是并發框架的基石,接下來有時間的話,還會寫一篇?AQS?的文章。

    總結

    以上是生活随笔為你收集整理的并发的核心:CAS 是什么?Java8是如何优化 CAS 的?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。