一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - Java教程 - 你還在用Synchronized?Atomic你了解不?

你還在用Synchronized?Atomic你了解不?

2021-06-26 12:05Java3y Java教程

這篇文章主要介紹了你還在用Synchronized?Atomic你了解不? 文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

 

只有光頭才能變強

之前已經寫過多線程相關的文章了,有興趣的同學可以去了解一下:

https://github.com/zhongfucheng3y/3y/blob/master/src/thread.md

你還在用Synchronized?Atomic你了解不?

在閱讀《阿里巴巴 java開發手冊》讀后感時,還有未解決的問題:

如果是count++操作,使用如下類實現: atomicinteger count = new atomicinteger(); count.addandget(1);如果是 jdk8,推薦使用 longadder 對象,比 atomiclong 性能更好(減少樂觀鎖的重試次數)。

之前在學習的時候也看過atomicinteger類很多次了,一直沒有去做相關的筆記。現在遇到問題了,于是就過來寫寫筆記,并希望在學習的過程中解決掉問題。

一、基礎鋪墊

 

首先我們來個例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class atomicmain {
 
 public static void main(string[] args) throws interruptedexception {
 
  executorservice service = executors.newcachedthreadpool();
 
  count count = new count();
  // 100個線程對共享變量進行加1
  for (int i = 0; i < 100; i++) {
   service.execute(() -> count.increase());
  }
 
  // 等待上述的線程執行完
  service.shutdown();
  service.awaittermination(1, timeunit.days);
 
 
  system.out.println("公眾號:java3y---------");
  system.out.println(count.getcount());
 }
 
}
 
class count{
 
 // 共享變量
 private integer count = 0;
 public integer getcount() {
  return count;
 }
 public void increase() {
  count++;
 }
}

你們猜猜得出的結果是多少?是100嗎?

多運行幾次可以發現:結果是不確定的,可能是95,也可能是98,也可能是100

你還在用Synchronized?Atomic你了解不?

根據結果我們得知:上面的代碼是線程不安全的!如果線程安全的代碼,多次執行的結果是一致的!

我們可以發現問題所在:count++并不是原子操作。因為count++需要經過讀取-修改-寫入三個步驟。舉個例子:

  • 如果某一個時刻:線程a讀到count的值是10,線程b讀到count的值也是10
  • 線程a對count++,此時count的值為11
  • 線程b對count++,此時count的值也是11(因為線程b讀到的count是10)
  • 所以到這里應該知道為啥我們的結果是不確定了吧。

要將上面的代碼變成線程安全的(每次得出的結果是100),那也很簡單,畢竟我們是學過synchronized鎖的人:

increase()加synchronized鎖就好了

?
1
2
3
public synchronized void increase() {
 count++;
}

無論執行多少次,得出的都是100:

你還在用Synchronized?Atomic你了解不?

從上面的代碼我們也可以發現,只做一個++這么簡單的操作,都用到了synchronized鎖,未免有點小題大做了。

synchronized鎖是獨占的,意味著如果有別的線程在執行,當前線程只能是等待!

于是我們原子變量的類就登場了!

1.2cas再來看看

在寫文章之前,本以為對cas有一定的了解了(因為之前已經看過相關概念,以為自己理解了)..但真正敲起鍵盤寫的時候,還是發現沒完全弄懂...所以再來看看cas吧。

來源維基百科:

比較并交換(compare and swap, cas),是原子操作的一種,可用于在多線程編程中實現不被打斷的數據交換操作,從而避免多線程同時改寫某一數據時由于執行順序不確定性以及中斷的不可預知性產生的數據不一致問題。 該操作通過將內存中的值與指定數據進行比較,當數值一樣時將內存中的數據替換為新的值。

cas有3個操作數:

  • 內存值v
  • 舊的預期值a
  • 要修改的新值b

當多個線程嘗試使用cas同時更新同一個變量時,只有其中一個線程能更新變量的值(a和內存值v相同時,將內存值v修改為b),而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試(或者什么都不做)。

我們畫張圖來理解一下:

你還在用Synchronized?Atomic你了解不?

我們可以發現cas有兩種情況:

如果內存值v和我們的預期值a相等,則將內存值修改為b,操作成功!

如果內存值v和我們的預期值a不相等,一般也有兩種情況:

  • 重試(自旋)
  • 什么都不做

我們再繼續往下看,如果內存值v和我們的預期值a不相等時,應該什么時候重試,什么時候什么都不做。

1.2.1cas失敗重試(自旋)

比如說,我上面用了100個線程,對count值進行加1。我們都知道:如果在線程安全的情況下,這個count值最終的結果一定是為100的。那就意味著:每個線程都會對這個count值實質地進行加1。

我繼續畫張圖來說明一下cas是如何重試(循環再試)的:

你還在用Synchronized?Atomic你了解不?

上面圖只模擬出兩個線程的情況,但足夠說明問題了。

1.2.2cas失敗什么都不做

上面是每個線程都要為count值加1,但我們也可以有這種情況:將count值設置為5

我也來畫個圖說明一下:

你還在用Synchronized?Atomic你了解不?

理解cas的核心就是:cas是原子性的,雖然你可能看到比較后再修改(compare and swap)覺得會有兩個操作,但終究是原子性的!

二、原子變量類簡單介紹

 

原子變量類在java.util.concurrent.atomic包下,總體來看有這么多個:

你還在用Synchronized?Atomic你了解不?

我們可以對其進行分類:

基本類型:

  • atomicboolean:布爾型
  • atomicinteger:整型
  • atomiclong:長整型

數組:

  • atomicintegerarray:數組里的整型
  • atomiclongarray:數組里的長整型
  • atomicreferencearray:數組里的引用類型

引用類型:

  • atomicreference:引用類型
  • atomicstampedreference:帶有版本號的引用類型
  • atomicmarkablereference:帶有標記位的引用類型

對象的屬性:

  • atomicintegerfieldupdater:對象的屬性是整型
  • atomiclongfieldupdater:對象的屬性是長整型
  • atomicreferencefieldupdater:對象的屬性是引用類型

jdk8新增doubleaccumulator、longaccumulator、doubleadder、longadder

  • 是對atomiclong等類的改進。比如longaccumulator與longadder在高并發環境下比atomiclong更高效。

atomic包里的類基本都是使用unsafe實現的包裝類。

unsafe里邊有幾個我們喜歡的方法(cas):

?
1
2
3
4
5
6
// 第一和第二個參數代表對象的實例以及地址,第三個參數代表期望值,第四個參數代表更新值
public final native boolean compareandswapobject(object var1, long var2, object var4, object var5);
 
public final native boolean compareandswapint(object var1, long var2, int var4, int var5);
 
public final native boolean compareandswaplong(object var1, long var2, long var4, long var6);

從原理上概述就是:atomic包的類的實現絕大調用unsafe的方法,而unsafe底層實際上是調用c代碼,c代碼調用匯編,最后生成出一條cpu指令cmpxchg,完成操作。這也就為啥cas是原子性的,因為它是一條cpu指令,不會被打斷。

2.1原子變量類使用

既然我們上面也說到了,使用synchronized鎖有點小題大作了,我們用原子變量類來改一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class count{
 
 // 共享變量(使用atomicinteger來替代synchronized鎖)
 private atomicinteger count = new atomicinteger(0);
 
 public integer getcount() {
  return count.get();
 }
 public void increase() {
  count.incrementandget();
 }
}
 
 
// main方法還是如上

修改完,無論執行多少次,我們的結果永遠是100!

其實atomic包下原子類的使用方式都不會差太多,了解原子類各種類型,看看api,基本就會用了(網上也寫得比較詳細,所以我這里果斷偷懶了)...

2.2aba問題

使用cas有個缺點就是aba的問題,什么是aba問題呢?首先我用文字描述一下:

  • 現在我有一個變量count=10,現在有三個線程,分別為a、b、c
  • 線程a和線程c同時讀到count變量,所以線程a和線程c的內存值和預期值都為10
  • 此時線程a使用cas將count值修改成100
  • 修改完后,就在這時,線程b進來了,讀取得到count的值為100(內存值和預期值都是100),將count值修改成10
  • 線程c拿到執行權,發現內存值是10,預期值也是10,將count值修改成11

上面的操作都可以正常執行完的,這樣會發生什么問題呢??線程c無法得知線程a和線程b修改過的count值,這樣是有風險的。

下面我再畫個圖來說明一下aba的問題(以鏈表為例):

你還在用Synchronized?Atomic你了解不?

2.3解決aba問題

要解決aba的問題,我們可以使用jdk給我們提供的atomicstampedreference和atomicmarkablereference類。

atomicstampedreference:

an {@code atomicstampedreference} maintains an object referencealong with an integer "stamp", that can be updated atomically.

簡單來說就是在給為這個對象提供了一個版本,并且這個版本如果被修改了,是自動更新的。

原理大概就是:維護了一個pair對象,pair對象存儲我們的對象引用和一個stamp值。每次cas比較的是兩個pair對象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// pair對象
 private static class pair<t> {
  final t reference;
  final int stamp;
  private pair(t reference, int stamp) {
   this.reference = reference;
   this.stamp = stamp;
  }
  static <t> pair<t> of(t reference, int stamp) {
   return new pair<t>(reference, stamp);
  }
 }
 
 private volatile pair<v> pair;
 
 // 比較的是pari對象
 public boolean compareandset(v expectedreference,
         v newreference,
         int expectedstamp,
         int newstamp) {
  pair<v> current = pair;
  return
   expectedreference == current.reference &&
   expectedstamp == current.stamp &&
   ((newreference == current.reference &&
    newstamp == current.stamp) ||
    caspair(current, pair.of(newreference, newstamp)));
 }

因為多了一個版本號比較,所以就不會存在aba的問題了。

2.4longadder性能比atomiclong要好

如果是 jdk8,推薦使用 longadder 對象,比 atomiclong 性能更好(減少樂觀鎖的重試次數)。

去查閱了一些博客和資料,大概的意思就是:

使用atomiclong時,在高并發下大量線程會同時去競爭更新同一個原子變量,但是由于同時只有一個線程的cas會成功,所以其他線程會不斷嘗試自旋嘗試cas操作,這會浪費不少的cpu資源。

而longadder可以概括成這樣:內部核心數據value分離成一個數組(cell),每個線程訪問時,通過哈希等算法映射到其中一個數字進行計數,而最終的計數結果,則為這個數組的求和累加。

簡單來說就是將一個值分散成多個值,在并發的時候就可以分散壓力,性能有所提高。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

延伸 · 閱讀

精彩推薦
  • Java教程Java回調方法詳解

    Java回調方法詳解

    本篇文章主要介紹了Java回調方法的步驟、回調的例子、異步回調與同步回調、回調方法在通信中的應用等。具有一定的參考價值,下面跟著小編一起來看下...

    byhieg2992020-07-22
  • Java教程Jersey框架的統一異常處理機制分析

    Jersey框架的統一異常處理機制分析

    初學者往往不清楚java的異常為什么會設計成這個樣子,他們通常會對異常只進行簡單的處理 ...

    小白盤1552020-06-01
  • Java教程SpringMVC上傳文件的三種實現方式

    SpringMVC上傳文件的三種實現方式

    本篇文章主要介紹了SpringMVC上傳文件的三種實現方式,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧...

    Jackvin5072020-07-20
  • Java教程詳解Java內存泄露的示例代碼

    詳解Java內存泄露的示例代碼

    這篇文章通過一個Demo來簡要介紹下ThreadLocal和ClassLoader導致內存泄露最終OutOfMemory的場景。下面通過示例代碼給大家分享Java內存泄露的相關知識,感興趣的...

    ezlippi11492021-03-01
  • Java教程java反射機制實戰示例分享

    java反射機制實戰示例分享

    這篇文章主要介紹了java反射機制實戰示例,需要的朋友可以參考下 ...

    java教程網4042019-11-12
  • Java教程簡述JAVA同步、異步、阻塞和非阻塞之間的區別

    簡述JAVA同步、異步、阻塞和非阻塞之間的區別

    這篇文章主要介紹了JAVA同步、異步、阻塞和非阻塞之間的區別,文中講解非常細致,幫助大家更好的理解和學習,感興趣的朋友可以了解下...

    PinXiong6552020-08-02
  • Java教程java實現將ftp和http的文件直接傳送到hdfs

    java實現將ftp和http的文件直接傳送到hdfs

    前面幾篇文章,我們已經做了很好的鋪墊了,幾個要用到的工具我們都做了出來,本文就是將他們集合起來,說下具體的用法,小伙伴們可以參考下。 ...

    hebedich6692019-12-13
  • Java教程Java中多態性的實現方式

    Java中多態性的實現方式

    這篇文章主要介紹了Java中多態性的實現方式,什么是多態?通過簡單的一道題目幫大家理解java多態性,具有一定的參考價值,感興趣的小伙伴們可以參考...

    Jack2046912021-01-07
主站蜘蛛池模板: 四虎成人国产精品视频 | α级毛片 | 动漫美女胸被狂揉扒开吃奶动态图 | 奇米小说| 久久久久国产一级毛片高清片 | 天堂资源在线www中文 | 亚洲精品私拍国产福利在线 | 国产91对白在线观看 | 日本精品久久久久中文字幕 1 | 黑人好大好硬好深好爽想要h | 欧美日韩亚洲一区二区三区在线观看 | 日韩视频在线观看中字 | 护士的小嫩嫩好紧好舒服 | 四虎免费影院4hu永久免费 | 亚洲人的天堂男人爽爽爽 | 亚洲色图欧美色 | 日韩网新片免费 | 国内9lporm自拍视频区 | 色老板在线免费视频 | 欧美日韩在线成人看片a | 日本www视频在线观看 | 精品久久免费视频 | 国产午夜精品久久久久 | 色批网站www | 深夜免费在线观看 | 欧美日本道免费一区二区三区 | 日本卡一卡2卡3卡4精品卡无人区 | 午夜在线观看免费完整直播网 | 亚洲欧美优优色在线影院 | 香港三级系列在线播放 | 欧美一区不卡二区不卡三区 | 边吃奶边扎下面 | 精彩国产萝视频在线 | 日日摸日日添日日透 | 草逼的视频 | 国产精品女主播自在线拍 | 成人伊人亚洲人综合网站222 | 欧美一区二区三 | 国产视频a区 | 欧美在线成人免费国产 | 免费成年网站 |