1. 程式人生 > 程式設計 >Java多執行緒CAS操作原理程式碼例項解析

Java多執行緒CAS操作原理程式碼例項解析

CAS操作號稱無鎖優化,也叫作自旋;對於一些常見的操作需要加鎖,然後jdk就提供了一些以Atomic開頭的類,這些類內部自動帶了鎖,當然這裡的鎖並非是用synchronized來實現的,而是通過CAS操作來實現的;

一、下面是 AtomicInteger 的使用:

package com.designmodal.design.juc01;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author D-L
 * @Classname T03_AtomicInteger
 * @Version 1.0
 * @Description 使用 AtomicInteger 類解決常見的 多執行緒count++
 *        其內部使用了CAS操作來保證原子性 但是不能保證多個方法連續呼叫都是原子性
 * @Date 2020/7/21 0:35
 */
public class T03_AtomicInteger {
  //使用AtomicInteger類
  AtomicInteger count = new AtomicInteger(0);

  public void m(){
    for (int i = 0; i < 10000; i++) {
      //等同於 在 count++ 上加鎖
      count.incrementAndGet();
    }
  }

  public static void main(String[] args) {
    T03_AtomicInteger t = new T03_AtomicInteger();
    List<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      threads.add(new Thread(t::m,"Thread" + i));
    }
    threads.forEach((o) -> o.start());
    threads.forEach(o ->{
      try {
        o.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });

    System.out.println(t.count);
  }
}

二、當然達到使用的級別很簡單,看一下API就好了,通過上面的小程式,下面主要來聊一聊原理:

1、通過原始碼分析AtomicInteger

首先小程式中定義了一個 AtomicInteger 型別的變數count;

 AtomicInteger count = new AtomicInteger(0);
 public void add(){
  count.incrementAndGet();
 }

呼叫了AtomicInteger類中incrementAndGet();

/**
   * Atomically increments by one the current value.
   *
   * @return the updated value
   */
  public final int incrementAndGet() {
    return unsafe.getAndAddInt(this,valueOffset,1) + 1;
  }

呼叫unsafe類中的 getAndAddInt(Object var1,long var2,int var4)方法;

public native int getIntVolatile(Object var1,long var2);
public final native boolean compareAndSwapInt(Object var1,int var4,int var5);


 public final int getAndAddInt(Object var1,int var4) {
    int var5;
    do {
      var5 = this.getIntVolatile(var1,var2);
    } while(!this.compareAndSwapInt(var1,var2,var5,var5 + var4));

    return var5;
  }

這裡通過以上三步的操作,最終會進入Unsafe類這裡呼叫的 compareAndSwapInt 意思就是比較然後交換,通過一個while迴圈,在這裡轉呀轉,直到修改成功;

CAS(compareAndSwap)(比較並交換):原來想改變的值為0 ,現在想修改成1 ,這裡想做到執行緒安全就必須要加synchronized,現在想用另外一種方式來替換加鎖的方法,就是所謂的CAS操作;你可以把它想象成擁有三個引數的方法cas(V,Expected,NewValue); 第一個引數V是你要改的那個值,Expected第二個引數是你期望當前的值是多少(也就是如果沒有執行緒修改的時,這個值應該是多少,如果不是期望值那就證明有別的執行緒修改過),NewValue是要設定的新值;

Java多執行緒CAS操作原理程式碼例項解析

上圖簡單模擬了CAS操作的過程,當執行緒1和執行緒2同時讀取了共享變數count = 0;線上程1修改的過程中,執行緒2已經將count值修改為1,那麼線上程1修改的時候發現Expected值和V已經匹配不上了,證明已經有執行緒快我一步將count值改了(可能這裡併發量大的時候已經有n多個執行緒已經修改過了),怎麼辦呢?那我只能將我的期望值修改成V的值、newValue 在這基礎上加1,然後繼續在這自旋操作,直到修改成功,這就是自旋操作;

2、Unsafe類(java併發包底層實現的核心)

CAS操作不需要加鎖是如何做到的呢?原因就在於Unsafe這個類,這個類除了你使用反射之外,你是不能夠直接使用的,這裡不能使用的原因和ClassLoader有關係,所有AtomicXXX 類內部都是CompareAndSwap / CompareAndSet(新版jdk),這個類中存在好多native方法;

Unsafe類使Java擁有了像C語言的指標一樣操作記憶體空間的能力,一旦能夠直接操作記憶體,這也就意味著(1)不受JVM管理,也就意味著無法被GC,需要我們手動GC,稍有不慎就會出現記憶體洩漏。(2)Unsafe的不少方法中必須提供原始地址(記憶體地址)和被替換物件的地址,偏移量要自己計算,一旦出現問題就是JVM崩潰級別的異常,會導致整個JVM例項崩潰,表現為應用程式直接crash掉。(3)直接操作記憶體,也意味著其速度更快,在高併發的條件之下能夠很好地提高效率。

CAS:CompareAndSwap,記憶體偏移地址(var2),預期值(var4),新值(var5)。如果變數在當前時刻的值和預期值expected相等,嘗試將變數的值更新為新值(var5)。如果更新成功,返回true;否則,返回false。

/**
   * CAS操作 :Unsafe類中的本地方法 由於Java語言無法訪問作業系統底層資訊(比如:底層硬體裝置等),
   * 這時候就需要藉助C C++語言來完成了
   * @param var1 物件
   * @param var2 偏移量
   * @param var4 期望值
   * @param var5 新值
   * @return 修改成功返回true 失敗返回false
   */
  public final native boolean compareAndSwapObject(Object var1,Object var4,Object var5);

  public final native boolean compareAndSwapInt(Object var1,int var5);

  public final native boolean compareAndSwapLong(Object var1,long var4,long var6);

三、CAS操作帶來的ABA問題

ABA問題說白了就是線上程進行CAS操作過程中有多個執行緒對這個共享變數進行修改,有加有減,兜兜轉轉又回到起始值,這時該執行緒渾然不知;打個不恰當的比喻:這個過程就好像你前女友跟你分手以後,在時隔一年之後又找你複合來了,說兜兜轉轉還是覺得你好,在此期間你前女友已經換了幾個男朋友你卻渾然不知,那個好看的她穿著你喜歡的小短裙,扎著清純的馬尾辮又回來,好了言歸正傳,意思就是結果是你期望的,可是這個值是經過很多版本的。

下面簡單模擬ABA操作圖:

Java多執行緒CAS操作原理程式碼例項解析

如何解決ABA問題呢?

如果是int型別,最終的值也是你期望的,真的是沒有所謂,你也不用去糾結這問題;如果你確實就想管一管,那就加一個版本號,做一次修改操作加一,比較檢查時連帶版本號一起檢查。

基礎型別:沒有必要管,對你真的沒有所謂;

引用型別:就像是你女朋友和你分手之後又複合,中間經歷了多少個男朋友,這個是有所謂的,這時可以通過加版本號來解決;

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。