亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時(shí)間:2018年12月29日 10:15:30   作者:Java3y  
這篇文章主要介紹了你還在用Synchronized?Atomic你了解不? 文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

只有光頭才能變強(qiáng)

之前已經(jīng)寫(xiě)過(guò)多線(xiàn)程相關(guān)的文章了,有興趣的同學(xué)可以去了解一下:

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

在閱讀《阿里巴巴 Java開(kāi)發(fā)手冊(cè)》讀后感時(shí),還有未解決的問(wèn)題:

如果是count++操作,使用如下類(lèi)實(shí)現(xiàn): AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀鎖的重試次數(shù))。

之前在學(xué)習(xí)的時(shí)候也看過(guò)AtomicInteger類(lèi)很多次了,一直沒(méi)有去做相關(guān)的筆記?,F(xiàn)在遇到問(wèn)題了,于是就過(guò)來(lái)寫(xiě)寫(xiě)筆記,并希望在學(xué)習(xí)的過(guò)程中解決掉問(wèn)題。

一、基礎(chǔ)鋪墊

首先我們來(lái)個(gè)例子:

public class AtomicMain {

 public static void main(String[] args) throws InterruptedException {

  ExecutorService service = Executors.newCachedThreadPool();

  Count count = new Count();
  // 100個(gè)線(xiàn)程對(duì)共享變量進(jìn)行加1
  for (int i = 0; i < 100; i++) {
   service.execute(() -> count.increase());
  }

  // 等待上述的線(xiàn)程執(zhí)行完
  service.shutdown();
  service.awaitTermination(1, TimeUnit.DAYS);


  System.out.println("公眾號(hào):Java3y---------");
  System.out.println(count.getCount());
 }

}

class Count{

 // 共享變量
 private Integer count = 0;
 public Integer getCount() {
  return count;
 }
 public void increase() {
  count++;
 }
}

你們猜猜得出的結(jié)果是多少?是100嗎?

多運(yùn)行幾次可以發(fā)現(xiàn):結(jié)果是不確定的,可能是95,也可能是98,也可能是100

根據(jù)結(jié)果我們得知:上面的代碼是線(xiàn)程不安全的!如果線(xiàn)程安全的代碼,多次執(zhí)行的結(jié)果是一致的!

我們可以發(fā)現(xiàn)問(wèn)題所在:count++并不是原子操作。因?yàn)?code>count++需要經(jīng)過(guò)讀取-修改-寫(xiě)入三個(gè)步驟。舉個(gè)例子:

  • 如果某一個(gè)時(shí)刻:線(xiàn)程A讀到count的值是10,線(xiàn)程B讀到count的值也是10
  • 線(xiàn)程A對(duì)count++,此時(shí)count的值為11
  • 線(xiàn)程B對(duì)count++,此時(shí)count的值也是11(因?yàn)榫€(xiàn)程B讀到的count是10)
  • 所以到這里應(yīng)該知道為啥我們的結(jié)果是不確定了吧。

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

increase()加synchronized鎖就好了

public synchronized void increase() {
 count++;
}

無(wú)論執(zhí)行多少次,得出的都是100:

從上面的代碼我們也可以發(fā)現(xiàn),只做一個(gè)++這么簡(jiǎn)單的操作,都用到了synchronized鎖,未免有點(diǎn)小題大做了。

Synchronized鎖是獨(dú)占的,意味著如果有別的線(xiàn)程在執(zhí)行,當(dāng)前線(xiàn)程只能是等待!

于是我們?cè)幼兞康念?lèi)就登場(chǎng)了!

1.2CAS再來(lái)看看

在寫(xiě)文章之前,本以為對(duì)CAS有一定的了解了(因?yàn)橹耙呀?jīng)看過(guò)相關(guān)概念,以為自己理解了)..但真正敲起鍵盤(pán)寫(xiě)的時(shí)候,還是發(fā)現(xiàn)沒(méi)完全弄懂...所以再來(lái)看看CAS吧。

來(lái)源維基百科:

比較并交換(compare and swap, CAS),是原子操作的一種,可用于在多線(xiàn)程編程中實(shí)現(xiàn)不被打斷的數(shù)據(jù)交換操作,從而避免多線(xiàn)程同時(shí)改寫(xiě)某一數(shù)據(jù)時(shí)由于執(zhí)行順序不確定性以及中斷的不可預(yù)知性產(chǎn)生的數(shù)據(jù)不一致問(wèn)題。 該操作通過(guò)將內(nèi)存中的值與指定數(shù)據(jù)進(jìn)行比較,當(dāng)數(shù)值一樣時(shí)將內(nèi)存中的數(shù)據(jù)替換為新的值。

CAS有3個(gè)操作數(shù):

  • 內(nèi)存值V
  • 舊的預(yù)期值A(chǔ)
  • 要修改的新值B

當(dāng)多個(gè)線(xiàn)程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線(xiàn)程能更新變量的值(A和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B),而其它線(xiàn)程都失敗,失敗的線(xiàn)程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試(或者什么都不做)。

我們畫(huà)張圖來(lái)理解一下:

我們可以發(fā)現(xiàn)CAS有兩種情況:

如果內(nèi)存值V和我們的預(yù)期值A(chǔ)相等,則將內(nèi)存值修改為B,操作成功!

如果內(nèi)存值V和我們的預(yù)期值A(chǔ)不相等,一般也有兩種情況:

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

我們?cè)倮^續(xù)往下看,如果內(nèi)存值V和我們的預(yù)期值A(chǔ)不相等時(shí),應(yīng)該什么時(shí)候重試,什么時(shí)候什么都不做。

1.2.1CAS失敗重試(自旋)

比如說(shuō),我上面用了100個(gè)線(xiàn)程,對(duì)count值進(jìn)行加1。我們都知道:如果在線(xiàn)程安全的情況下,這個(gè)count值最終的結(jié)果一定是為100的。那就意味著:每個(gè)線(xiàn)程都會(huì)對(duì)這個(gè)count值實(shí)質(zhì)地進(jìn)行加1。

我繼續(xù)畫(huà)張圖來(lái)說(shuō)明一下CAS是如何重試(循環(huán)再試)的:

上面圖只模擬出兩個(gè)線(xiàn)程的情況,但足夠說(shuō)明問(wèn)題了。

1.2.2CAS失敗什么都不做

上面是每個(gè)線(xiàn)程都要為count值加1,但我們也可以有這種情況:將count值設(shè)置為5

我也來(lái)畫(huà)個(gè)圖說(shuō)明一下:

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

二、原子變量類(lèi)簡(jiǎn)單介紹

原子變量類(lèi)在java.util.concurrent.atomic包下,總體來(lái)看有這么多個(gè):

我們可以對(duì)其進(jìn)行分類(lèi):

基本類(lèi)型:

  • AtomicBoolean:布爾型
  • AtomicInteger:整型
  • AtomicLong:長(zhǎng)整型

數(shù)組:

  • AtomicIntegerArray:數(shù)組里的整型
  • AtomicLongArray:數(shù)組里的長(zhǎng)整型
  • AtomicReferenceArray:數(shù)組里的引用類(lèi)型

引用類(lèi)型:

  • AtomicReference:引用類(lèi)型
  • AtomicStampedReference:帶有版本號(hào)的引用類(lèi)型
  • AtomicMarkableReference:帶有標(biāo)記位的引用類(lèi)型

對(duì)象的屬性:

  • AtomicIntegerFieldUpdater:對(duì)象的屬性是整型
  • AtomicLongFieldUpdater:對(duì)象的屬性是長(zhǎng)整型
  • AtomicReferenceFieldUpdater:對(duì)象的屬性是引用類(lèi)型

JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder

  • 是對(duì)AtomicLong等類(lèi)的改進(jìn)。比如LongAccumulator與LongAdder在高并發(fā)環(huán)境下比AtomicLong更高效。

Atomic包里的類(lèi)基本都是使用Unsafe實(shí)現(xiàn)的包裝類(lèi)。

Unsafe里邊有幾個(gè)我們喜歡的方法(CAS):

// 第一和第二個(gè)參數(shù)代表對(duì)象的實(shí)例以及地址,第三個(gè)參數(shù)代表期望值,第四個(gè)參數(shù)代表更新值
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包的類(lèi)的實(shí)現(xiàn)絕大調(diào)用Unsafe的方法,而Unsafe底層實(shí)際上是調(diào)用C代碼,C代碼調(diào)用匯編,最后生成出一條CPU指令cmpxchg,完成操作。這也就為啥CAS是原子性的,因?yàn)樗且粭lCPU指令,不會(huì)被打斷。

2.1原子變量類(lèi)使用

既然我們上面也說(shuō)到了,使用Synchronized鎖有點(diǎn)小題大作了,我們用原子變量類(lèi)來(lái)改一下:

class Count{

 // 共享變量(使用AtomicInteger來(lái)替代Synchronized鎖)
 private AtomicInteger count = new AtomicInteger(0);
 
 public Integer getCount() {
  return count.get();
 }
 public void increase() {
  count.incrementAndGet();
 }
}


// Main方法還是如上

修改完,無(wú)論執(zhí)行多少次,我們的結(jié)果永遠(yuǎn)是100!

其實(shí)Atomic包下原子類(lèi)的使用方式都不會(huì)差太多,了解原子類(lèi)各種類(lèi)型,看看API,基本就會(huì)用了(網(wǎng)上也寫(xiě)得比較詳細(xì),所以我這里果斷偷懶了)...

2.2ABA問(wèn)題

使用CAS有個(gè)缺點(diǎn)就是ABA的問(wèn)題,什么是ABA問(wèn)題呢?首先我用文字描述一下:

  • 現(xiàn)在我有一個(gè)變量count=10,現(xiàn)在有三個(gè)線(xiàn)程,分別為A、B、C
  • 線(xiàn)程A和線(xiàn)程C同時(shí)讀到count變量,所以線(xiàn)程A和線(xiàn)程C的內(nèi)存值和預(yù)期值都為10
  • 此時(shí)線(xiàn)程A使用CAS將count值修改成100
  • 修改完后,就在這時(shí),線(xiàn)程B進(jìn)來(lái)了,讀取得到count的值為100(內(nèi)存值和預(yù)期值都是100),將count值修改成10
  • 線(xiàn)程C拿到執(zhí)行權(quán),發(fā)現(xiàn)內(nèi)存值是10,預(yù)期值也是10,將count值修改成11

上面的操作都可以正常執(zhí)行完的,這樣會(huì)發(fā)生什么問(wèn)題呢??線(xiàn)程C無(wú)法得知線(xiàn)程A和線(xiàn)程B修改過(guò)的count值,這樣是有風(fēng)險(xiǎn)的。

下面我再畫(huà)個(gè)圖來(lái)說(shuō)明一下ABA的問(wèn)題(以鏈表為例):

2.3解決ABA問(wèn)題

要解決ABA的問(wèn)題,我們可以使用JDK給我們提供的AtomicStampedReference和AtomicMarkableReference類(lèi)。

AtomicStampedReference:

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

簡(jiǎn)單來(lái)說(shuō)就是在給為這個(gè)對(duì)象提供了一個(gè)版本,并且這個(gè)版本如果被修改了,是自動(dòng)更新的。

原理大概就是:維護(hù)了一個(gè)Pair對(duì)象,Pair對(duì)象存儲(chǔ)我們的對(duì)象引用和一個(gè)stamp值。每次CAS比較的是兩個(gè)Pair對(duì)象

// Pair對(duì)象
 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對(duì)象
 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)));
 }

因?yàn)槎嗔艘粋€(gè)版本號(hào)比較,所以就不會(huì)存在ABA的問(wèn)題了。

2.4LongAdder性能比AtomicLong要好

如果是 JDK8,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀鎖的重試次數(shù))。

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

使用AtomicLong時(shí),在高并發(fā)下大量線(xiàn)程會(huì)同時(shí)去競(jìng)爭(zhēng)更新同一個(gè)原子變量,但是由于同時(shí)只有一個(gè)線(xiàn)程的CAS會(huì)成功,所以其他線(xiàn)程會(huì)不斷嘗試自旋嘗試CAS操作,這會(huì)浪費(fèi)不少的CPU資源。

而LongAdder可以概括成這樣:內(nèi)部核心數(shù)據(jù)value分離成一個(gè)數(shù)組(Cell),每個(gè)線(xiàn)程訪問(wèn)時(shí),通過(guò)哈希等算法映射到其中一個(gè)數(shù)字進(jìn)行計(jì)數(shù),而最終的計(jì)數(shù)結(jié)果,則為這個(gè)數(shù)組的求和累加。

簡(jiǎn)單來(lái)說(shuō)就是將一個(gè)值分散成多個(gè)值,在并發(fā)的時(shí)候就可以分散壓力,性能有所提高。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • MyBatis通用的10種寫(xiě)法總結(jié)大全

    MyBatis通用的10種寫(xiě)法總結(jié)大全

    這篇文章主要給大家介紹了關(guān)于MyBatis通用的10種寫(xiě)法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • java語(yǔ)法糖之jdk迭代的新特性匯總

    java語(yǔ)法糖之jdk迭代的新特性匯總

    什么是語(yǔ)法糖?泛型、自動(dòng)裝箱拆箱、變長(zhǎng)參數(shù)、增強(qiáng)for循環(huán)、switch字符類(lèi)型、lambda表達(dá)式等,這些其實(shí)都是語(yǔ)法糖。這篇文章主要給大家介紹了關(guān)于java語(yǔ)法糖之jdk迭代的新特性的相關(guān)資料,需要的朋友可以參考下
    2021-05-05
  • Java面試題沖刺第二十七天--JVM2

    Java面試題沖刺第二十七天--JVM2

    這篇文章主要為大家分享了最有價(jià)值的三道關(guān)于JVM的面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java中的HashMap源碼詳解

    Java中的HashMap源碼詳解

    這篇文章主要介紹了Java中的HashMap源碼詳解,當(dāng)我們確切知道HashMap將要處理的數(shù)據(jù)量為n時(shí),推薦調(diào)用構(gòu)造函數(shù)public?HashMap(int?initialCapacity)來(lái)創(chuàng)建?HashMap,這樣就不會(huì)發(fā)生擴(kuò)容,需要的朋友可以參考下
    2023-09-09
  • Java集合List與Array的相互轉(zhuǎn)換

    Java集合List與Array的相互轉(zhuǎn)換

    本篇文章主要介紹了Java集合List與Array的相互轉(zhuǎn)換,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • Netty分布式ByteBuf使用命中緩存的分配解析

    Netty分布式ByteBuf使用命中緩存的分配解析

    這篇文章主要為大家介紹了Netty分布式ByteBuf?使用命中緩存的分配解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • 初識(shí)Spring Boot框架和快速入門(mén)

    初識(shí)Spring Boot框架和快速入門(mén)

    這篇文章主要介紹了初識(shí)Spring Boot框架學(xué)習(xí),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • Spring中的Actuator使用詳解

    Spring中的Actuator使用詳解

    這篇文章主要介紹了Spring中的Actuator使用詳解,在生產(chǎn)環(huán)境中運(yùn)行的程序,并不總是穩(wěn)定、安靜、正確的,往往會(huì)遇到各式各樣的現(xiàn)場(chǎng)狀況,這個(gè)時(shí)候,就需要獲取該程序足夠多的運(yùn)行狀態(tài)信息,然后分析并對(duì)其進(jìn)行有效管理,需要的朋友可以參考下
    2023-09-09
  • 淺談Java并發(fā)之同步器設(shè)計(jì)

    淺談Java并發(fā)之同步器設(shè)計(jì)

    這篇文章主要介紹Java并發(fā)之同步器設(shè)計(jì),本文以記錄方式并發(fā)編程中同步器設(shè)計(jì)的一些共性特征。并簡(jiǎn)單介紹了Java中的AQS,需要的朋友可以參考一下文章的詳細(xì)內(nèi)容
    2021-10-10
  • 構(gòu)建多模塊的Spring Boot項(xiàng)目步驟全紀(jì)錄

    構(gòu)建多模塊的Spring Boot項(xiàng)目步驟全紀(jì)錄

    這篇文章主要給大家介紹了關(guān)于如何構(gòu)建多模塊的Spring Boot項(xiàng)目的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05

最新評(píng)論