java多線程併發之原子操做/CAS以及原子類atomic

volatile

介紹

可見性問題
  • 讓一個線程對共享變量的修改,可以及時的被其餘線程看到。
  • 根據JMM中規定的happen before和同步原則:
    • 對某個 volatile字段的寫操做happens-before每一個後續對該volatile字段的讀操做。
    • 對volatile變量v的寫入,與全部其餘線程後續對v的讀同步
要避免可見性問題,volatile 須要具備哪些功能
  1. 禁止緩存;
  2. volatile變量的訪問控制符會加個ACC _VOLATILE
  3. docs.oracle.com/javase/spec…
  4. 對volatile變 量相關的指令不作重排序;

線程安全

臨界區和競態條件

  1. 競態條件:臨界區內,引起線程安全問題的代碼。
  2. 臨界區:多線程狀況下,會發生線程安全問題的區域。

共享資源

  • 若是一段代碼是線程安全的,則它不包含競態條件。只有當多個線程更新共享資源時,纔會發生競態條件。
  • 棧封閉時,不會在線程之間共享的變量,都是線程安全的。
  • 局部對象引用自己不共享,可是引用的對象存儲在共享堆中。若是方法內建立的對象,只是在方法中傳遞,而且不對其餘線程可用,那麼也是線程安全的。
public void someMethod(){
    Localobject localobject = new Localobject();
    localobject.callMethod();
    method2(localobject);
}
public void method2(Localobject localobject{ localobject.setValue("value");
}
複製代碼

不可變對象
html

建立不可變的共享對象來保證對象在線程間共享時不會被修改,從而實現線程安全。 實例被建立,value變量 就不能再被修改,這就是不可變性。java

public class Demo{
    private int value = O;
    public Demo(int value){
    	this.value = value;
    }
    public int getValue(){
    	return this.value;
    }
}
複製代碼

原子操做

本質:數據的一致性緩存

原子性的定義

  1. 原子操做影響到的變量,在一塊兒原子操做中,保持一致性(不能發生變更或者說不能被修改)。

CAS機制

  1. 接近於直接性的內存操做

利用 Unsafe 實現原子性

  1. 取 unsafe.getIntVolatile
static{
    try{
        //反射技術拿到對象
        Field theUnsafe = Unsafe.class. getDeclaredField( name: "theUnsafe");
        theUnsafe.setAccessible(true);
        unsafe = (Unsafe)theUnsafe.get(null):
        //利用unsafe,經過屬性的偏離量(定位到內存中對象內具體的屬性的內存地址)
        iOffset = unsafe.objectFieldPffset(LockDemo1.class.getDeclaredField(name: "i")) :
    } catch (Exception ex) {
        ex.printStackTrace();
	}
}
//優化
public void add(){
    int current; 
    //i++;
    current = unsafe.getIntVolatile( o: this, iOffset);
    unsafe.compareAndSwapInt( o: this.iOffset, current, i1: current + 1):
    // CAS命令
}
複製代碼
  • getIntVolatile 注意這個 volatile,禁止了指令重排以及緩存
  • 全都是 native 修飾的本地方法
    • native 非java方法,使用 C 實現的。
  • unsafe 是根據變量相對於當前對象的偏移量,去獲取到當前變量在內存中位置的
  • 缺點:高併發的狀況下,會不斷重試,形成資源損耗。

使用 Atomic 實現原子性

原理:自旋鎖安全

// volatile int i = 0;
AtomicInteger i = new AtomicInteger(initialValue: 0) ;
public void add() {
    i.incrementAndGet():// i++
}
複製代碼

使用 LongAdder 實現原子性

since:JDK 1.8+服務器

  • 大數據處理思路 >>> 分而治之
  • 每個線程都有一個獨享的累加器,在須要求和的時候,再取出來相加。避免了重試(Atomic)或者競爭(sync)。
  • 適合高寫,低讀的場景

CAS 存在的幾個問題

ABA問題

  • What
    • 本該失敗的操做確成功了,致使沒辦法體現出數據的變化。
  • Why
    • 線程1 和 線程2 同時發起了對於變量 i 的 CAS請求。正確狀況下,應該某一線程會失敗(這裏假設 線程2 會失敗)
    • 但 在線程1 成功執行後,線程3 先於 線程2 執行了。將 i 改回了線程2
    • 線程2 認爲,i 的值仍是爲 1 ,是我要找到,因此又把 i 的值變成了 2
  • How
    • 不要將數據定義爲簡單的基本數據類型

如何避免

  • 使用帶有版本號的 J.U.C 類
  • 不在只是判斷數值結果
  • 數據的變更會經過版本號被記錄下來
  • compare 的時候會增長對於版本號的對比

J.U.C 包提供的原子操做類

思想

分而治之

統計一個圖片的點擊數

  • 多臺服務器去分別統計,別分別記錄
  • 彙總服務器彙總全部記錄

image.png


版權聲明

  • 本文做者:** 留夕
  • 本文連接: **www.yuque.com/diamond/ndv…
  • 版權聲明: **本博客全部文章除特別聲明外,均採用 CC BY-SA 4.0 許可協議。轉載請註明出處!
  • 首發日期: **2019-6-26 22:09:01
相關文章
相關標籤/搜索