1. 程式人生 > 程式設計 >Java CAS基本實現原理程式碼例項解析

Java CAS基本實現原理程式碼例項解析

一、前言

瞭解CAS,首先要清楚JUC,那麼什麼是JUC呢?JUC就是java.util.concurrent包的簡稱。它有核心就是CAS與AQS。CAS是java.util.concurrent.atomic包的基礎,如AtomicInteger、AtomicBoolean、AtomicLong等等類都是基於CAS。

什麼是CAS呢?全稱Compare And Swap,比較並交換。CAS有三個運算元,記憶體值V,舊的預期值E,要修改的新值N。當且僅當預期值E和記憶體值V相同時,將記憶體值V修改為N,否則什麼都不做。

Java CAS基本實現原理程式碼例項解析

二、例項

如果我們需要對一個數進行加法操作,應該怎樣去實現呢?我們模擬多個執行緒情況下進行操作。

ThreadDemo.java 實現一個Runnable介面

package com.spring.security.test;

public class ThreadDemo implements Runnable {

	private int count = 0;

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			addCount();
		}
	}

	private void addCount() {
		count++;
	}

	public int getCount() {
		return count;
	}
}

ThreadTest.java 建立執行緒池,提交10個執行緒執行,預期結果應該是1000

package com.spring.security.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadTest {
	public static void main(String[] args) {
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		ThreadDemo threadDemo = new ThreadDemo();
		for (int i = 0; i < 10; i++) {
			threadPool.submit(threadDemo);
		}
    threadPool.shutdown();
		System.out.println(threadDemo.getCount());
	}
}

執行結果:874 或其他,與預期結果不符合。

執行出來的結果並不是想象中的結果。這是為什麼呢?這跟執行緒的執行過程有關。

Java CAS基本實現原理程式碼例項解析

所以我們需要在改變count,將值從高速緩衝區重新整理到主記憶體後,讓其他執行緒重新讀取主記憶體中的值到自己的工作記憶體。

此時可以用volatile關鍵字。它的作用是保證物件在記憶體中的可見性。

修改ThreadDemo中的count欄位

private volatile int count = 0;

此時執行結果:900 或其他,與預期結果不符合。

此時還是並未得出正確執行結果。為什麼?聽我細細道來。

執行緒安全主要體現在三個方面:

  • 原子性:提供了互斥訪問,同一時刻只能有一個執行緒對它進行操作
  • 可見性:一個執行緒對主記憶體的修改可以及時的被其他執行緒觀察到
  • 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序

目前可見性已經實現了,缺少原子性的操作,因為同一時刻,多個執行緒對其操作,會將改動後的最新值讀取到自己的工作記憶體進行操作,最終只能得到後一個執行執行緒操作的結果,所以相當於少了一步操作,就會造成資料的不一致。

此時可以使用JUC的Atomic包下面的類來進行操作。

Java CAS基本實現原理程式碼例項解析

Atomic類是使用CAS+volatile來實現原子性與可見性的。

我們來改造一下TheadDemo.java中的實現方法

package com.spring.security.test;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo implements Runnable {

	private AtomicInteger count = new AtomicInteger(0);

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			// 遞增
			count.getAndIncrement();
		}
	}

	public int getCount() {
		return count.get();
	}
}

執行結果: 1000,符合預期值。

Java CAS基本實現原理程式碼例項解析

接下來我們來分析一下AtomicInteger類的原始碼:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
  try {
    valueOffset = unsafe.objectFieldOffset
      (AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

Unsafe類是不安全的類,它提供了一些底層的方法,我們是不能使用這個類的。AtomicInteger的值儲存在value中,而valueOffset是value在記憶體中的偏移量,利用靜態程式碼塊使其類一載入的時候就賦值。value值使用volatile,保證其可見性。

  /**
   * Atomically increments by one the current value.
   *
   * @return the previous value
   */
  public final int getAndIncrement() {
    return unsafe.getAndAddInt(this,valueOffset,1);
  }
public final int getAndAddInt(Object var1,long var2,int var4) {
	int var5;
	do {
		var5 = this.getIntVolatile(var1,var2);
	} while(!this.compareAndSwapInt(var1,var2,var5,var5 + var4));

	return var5;
}

var1表示當前物件,var2表示value在記憶體中的偏移量,var4為增加的值。var5為呼叫底層方法獲取value的值

compareAndSwapInt方法通過var1和var2獲取當前記憶體中的value值,並與var5進行比對,如果一致,就將var5+var4的值賦給value,並返回true,否則返回false

由do while語句可知,如果這次沒有設定進去值,就重複執行此過程。這一過程稱為自旋。

compareAndSwapInt是JNI(Java Native Interface)提供的方法,可以是其他語言寫的。

三、與synchronized比較

使用synchronized進行加法:

package com.spring.security.test;

public class ThreadDemo implements Runnable {

	private int count = 0;

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			// 遞增
			synchronized (ThreadDemo.class) {
				count++;
			}
		}
	}

	public int getCount() {
		return count;
	}
}

執行結果: 1000,符合預期值。

444

使用synchronized和AtomicInteger都能得到預期結果,但是他們之間各有什麼劣勢呢?

synchronized是重量級鎖,是悲觀鎖,就是無論你執行緒之間發不發生競爭關係,它都認為會發生競爭,從而每次執行都會加鎖。

在併發量大的情況下,如果鎖的時間較長,那將會嚴重影響系統性能。

CAS操作中我們可以看到getAndAddInt方法的自旋操作,如果長時間自旋,那麼肯定會對系統造成壓力。而且如果value值從A->B->A,那麼CAS就會認為這個值沒有被操作過,這個稱為CAS操作的"ABA"問題。

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