淺析CompareAndSet(CAS)


最近無意接觸了AtomicInteger類compareAndSet(從JDK5開始),搜了搜相關資料,整理了一下

首先要說一下,AtomicInteger類compareAndSet通過原子操作實現了CAS操作,最底層基於匯編語言實現。

簡單說一下原子操作的概念,“原子”代表最小的單位,所以原子操作可以看做最小的執行單位,該操作在執行完畢前不會被任何其他任務或事件打斷。

CAS是Compare And Set的一個簡稱,如下理解:

1,已知當前內存里面的值current和預期要修改成的值new傳入

2,內存中AtomicInteger對象地址對應的真實值(因為有可能別修改)real與current對比,

      相等表示real未被修改過,是“安全”的,將new賦給real結束然后返回;不相等說明real已經被修改,結束並重新執行1直到修改成功

 

CAS相比Synchronized,避免了鎖的使用,總體性能比Synchronized高很多.

compareAndSet典型使用為計數,如i++,++i,這里以i++為例:

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            //獲取當前值
            int current = get();
            //設置期望值
            int next = current + 1;
            //調用Native方法compareAndSet,執行CAS操作
            if (compareAndSet(current, next))
                //成功后才會返回期望值,否則無線循環
                return next;
        }
    }

 


compareAndSet方法實現:

JDK文檔對該方法的說明如下:如果當前狀態值等於預期值,則以原子方式將同步狀態設置為給定的更新值。

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

  


這里解釋一下valueOffset變量,首先valueOffset的初始化在static靜態代碼塊里面,代表相對起始內存地址的字節相對偏移量:

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

  


在生成一個AtomicInteger對象后,可以看做生成了一段內存,對象中各個字段按一定順序放在這段內存中,字段可能不是連續放置的,

unsafe.objectFieldOffset(Field f)這個方法准確地告訴我"value"字段相對於AtomicInteger對象的起始內存地址的字節相對偏移量。

    private volatile int value;
 
    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
 
    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

  

value是一個volatile變量,不同線程對這個變量進行操作時具有可見性,修改與寫入操作都會存入主存中,並通知其他cpu中該變量緩存行無效,保證了每次讀取都是最新的值


找到sun.misc.Unsafe.java:

/**
 * Atomically update Java variable to <tt>x</tt> if it is currently
 * holding <tt>expected</tt>.
 * @return <tt>true</tt> if successful
 */
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);

  


繼續查找unsafe.cpp,(http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe.cpp):

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

  

實現主要方法為Atomic::cmpxchg , 這個本地方法的最終實現在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(對應於windows操作系統,X86處理器) 

// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:
 
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

  


如上面源代碼所示,用嵌入的匯編實現的, CPU指令是 cmpxchg,程序會根據當前處理器的類型來決定是否為cmpxchg指令添加lock前綴。如果程序是在多處理器上運行,就為cmpxchg指令加上lock前綴(lock cmpxchg).反之,如果程序是在單處理器上運行,就省略lock前綴(單處理器自身會維護單處理器內的順序一致性,不需要lock前綴提供的內存屏障效果).lock前綴的作用說明:1禁止該指令與之前和之后的讀和寫指令重排序,2把寫緩沖區中的所有數據刷新到內存中。
總的來說,Atomic實現了高效無鎖(底層還是用到排它鎖,不過底層處理比java層處理要快很多)與線程安全(volatile變量特性),CAS一般適用於計數;多線程編程也適用,多個線程執行AtomicXXX類下面的方法,當某個線程執行的時候具有排他性,在執行方法中不會被打斷,直至當前線程完成才會執行其他的線程。

 

參考文章:http://www.infoq.com/cn/articles/java-memory-model-5

                    http://hllvm.group.iteye.com/group/topic/37940

                    http://www.cnblogs.com/dolphin0520/p/3920373.html
---------------------
原文:https://blog.csdn.net/u013404471/article/details/47297123

 

下面在看java並發編程藝術時有一段關於CAS的實現! 

在Java中可以通過鎖和循環CAS的方式來實現原子操作。

(1)使用循環CAS實現原子操作
  JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實現的。自旋CAS實現的基本思路就是循環進行CAS操作直到成功為止,以下代碼實現了一個基於CAS線程安全的計數器方法safeCount和一個非線程安全的計數器count。

  從Java 1.5開始,JDK的並發包里提供了一些類來支持原子操作,如AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。這些原子包裝類還提供了有用的工具方法,

  比如以原子的方式將當前值自增1和自減1。

package com.odyun;

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

public class CASTest {

    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 0;
    public static void main(String[] args) {
        final CASTest cas = new CASTest();
        List<Thread> ts = new ArrayList<Thread>(600);
        long start = System.currentTimeMillis();
        for (int j = 0; j < 100; j++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        cas.count();
                        cas.safeCount();
                    }
                }
            });
            ts.add(t);
        }
        for (Thread t : ts) {
            t.start();
        }
// 等待所有線程執行完成
        for (Thread t : ts) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(cas.i);
        System.out.println(cas.atomicI.get());
        System.out.println(System.currentTimeMillis() - start);
    }
    /** * 使用CAS實現線程安全計數器 */
    private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
    }
    /**
     * 非線程安全計數器
     */
    private void count() {
        i++;
    }
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM