1. 程式人生 > >Atomic類和CAS

Atomic類和CAS

工作 ont wapi isp 通過 相關 final nac rri

說Atomic類之前,先聊一聊volatile。

對volatile的第一印象就是可見性。所謂可見性,就是一個線程對共享變量的修改,別的線程能夠感知到。

但是對於原子性,volatile是不能保證的。來看看自增操作的問題:

volatile int i;

i++;

i++ 在多線程環境下,是不能保證最終的結果正確的。比如某個時刻,i=5,線程A讀取了i的值,說時遲那時快,就在馬上要執行++操作時,線程A突然就被切換走了;然後線程B也讀取i的值,進行了++操作。這時i的值是6,即使線程A的工作內存中的緩存已經失效,線程A已經讀取了i的值為5,不會再去讀取,所以++操作後,i的值還是6。

關於volatile的底層實現,有好多文章分析的很透徹,這裏不再贅述。

那麽除了使用synchronized,還有沒有其它的方式來解決上述問題呢?天空一聲巨響,Atomic類閃亮登場!!!

先看看AtomicInteger類,其它類型包裝成的Atomic類,請讀者自習。

AtomicInteger i = new AtomicInteger(0);

i.incrementAndGet();

用法相當簡單,incrementAndGet 方法能實現原子性的自增操作。如何實現,看源碼。

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

Unsafe類:

    /**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object <code>o</code>
     * at the given <code>offset</code>.
     *
     * 
@param o object/array to update the field/element in * @param offset field/element offset * @param delta the value to add * @return the previous value * @since 1.8 */ public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }
/**
     * 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);

可以看到原子性的實現沒有用synchronized,說明是非阻塞同步。最核心的方法是compareAndSwapInt,也就是所謂的CAS操作。

CAS操作依賴底層硬件的CAS指令,CAS指令有兩個步驟:沖突檢測和更新操作,但是這兩個步驟合起來成為一個原子性操作。

CAS指令需要3個操作數:內存位置(V),舊的預期值(A),新值(B)。CAS指令執行時,首先比較內存位置V處的值和A的值是否相等(沖突檢測),如果相等,就用新值B覆蓋A(更新操作),否則,就什麽也不做。所以,一般循環執行CAS操作,直到成功為止。

Unsafe類裏面的compareAndSwapXXX 方法最後都會變成與硬件相關的CAS指令。從Unsafe這個類名就可以看出,作者不希望我們隨便使用,因為是不安全的。為什麽不安全呢,因為這個類可以直接操作內存;還有其他的一些底層操作,比如上篇文章提到的將線程掛起,就是調用了Unsafe類的park方法(感興趣的,出門左拐)。

了解了CAS和Unsafe類,接著再說AtomicInteger類:

    // setup to use Unsafe.compareAndSwapInt for updates
    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;

value就是我們需要操作的真正int值,unsafe就是Unsafe類的單例,valueOffset在static語句塊裏面,被設置成了value變量在AtomicInteger類的實例對象裏面的偏移量(可以看成內存地址)。這裏對對象的內存布局如有疑問,同樣出門一路左拐,找到那篇將Oop和Klass的文章。

也就是說,Atomic類通過循環進行CAS操作,直到成功,來實現非阻塞同步,進而變成原子操作。

在java.util.concurrent.atomic包下還有一些看上去比較奇怪的類,XXXFieldUpdater類,這玩意兒是用來幹什麽的呢?

一句話概括,就是亡羊補牢。比如說,你先自己寫了一個類,定義了一個基礎類型的變量。後來涉及到多線程,那麽原來對該變量的一些操作就變得不安全。如果,你立馬想到的是手動修改代碼,那就太low了,破壞了設計模式裏面比較重要的開閉原則,而且很多情況下,你是接觸不到別人的源代碼的。這個時候,XXXFieldUpdater類就派上用場了。這個時候,猜也能猜到,這個類肯定是通過反射的方式實現這個功能的。另外,有一點原來定義的繼承類型變量,必須是volatile的。

直接上代碼,看效果:

public class Test{
    public volatile int a = 100;
    
    // 多線程下不安全
    public void incr() {
        a++;
    }  
}

public class SafeTest{
    private static AtomicIntegerFieldUpdater<Test> update = AtomicIntegerFieldUpdater.newUpdater(Test.class, "a");
    private static Test test = new Test();

    // 多線程下安全
    public void incr() {
       update.incrementAndGet(test);
    }
}
update.incrementAndGet(test)無非也是調用了Unsafe類的CAS操作,核心方法是AtomicIntegerFieldUpdater.newUpdater(Test.class, "a"),看看源碼:
    @CallerSensitive
    public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                              String fieldName) {
        return new AtomicIntegerFieldUpdaterImpl<U>
            (tclass, fieldName, Reflection.getCallerClass());
    }



    AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                      final String fieldName,
                                      final Class<?> caller) {
            // 反射邏輯
            final Field field;
            final int modifiers;
            try {
                field = AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Field>() {
                        public Field run() throws NoSuchFieldException {
                            return tclass.getDeclaredField(fieldName);
                        }
                    });
                modifiers = field.getModifiers();
                sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                    caller, tclass, null, modifiers);
                ClassLoader cl = tclass.getClassLoader();
                ClassLoader ccl = caller.getClassLoader();
                if ((ccl != null) && (ccl != cl) &&
                    ((cl == null) || !isAncestor(cl, ccl))) {
                  sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                }
            } catch (PrivilegedActionException pae) {
                throw new RuntimeException(pae.getException());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            Class<?> fieldt = field.getType();
            if (fieldt != int.class)
                throw new IllegalArgumentException("Must be integer type");
            // 必須是volatile
            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

            this.cclass = (Modifier.isProtected(modifiers) &&
                           caller != tclass) ? caller : null;
            this.tclass = tclass;
            // 得到變量的偏移量,相當於內存地址
            offset = unsafe.objectFieldOffset(field);
        }

總的來說,原子性的實現是依賴於Unsafe類的CAS操作,直接修改內存裏的值,既危險又刺激。我們甚至可以用Unsafe類來直接分配內存,要不試一試!!!

Unsafe類是單例,可以通過它的getUnsafe方法獲取這個單例。

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        // 調用方的類的類加載器必須是啟動類加載器
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

這個方法對調用方有限制,就是說你隨隨便便定義的類,調用這個方法是會報錯的。但是Unsafe類既然已經被加載,我們可以通過反射的方式去獲取裏面的單例對象。

package test;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
import sun.reflect.Reflection;

class User {
    private String name = "";
    private int age = 0;

    public User() {
        this.name = "test";
        this.age = 25;
    }
    
    @Override
    public String toString() {
        return name + ": " + age;
    }
}


public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        // 通過反射得到theUnsafe對應的Field對象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 設置該Field為可訪問
        field.setAccessible(true);
        // 通過Field得到該Field對應的具體對象,傳入null是因為該Field為static的
        Unsafe unsafe = (Unsafe) field.get(null);

        // 直接分配相應大小的內存,不執行構造方法
        User user = (User) unsafe.allocateInstance(User.class);
        System.out.println(user);
        
        User userFromNormal = new User();
        System.out.println(userFromNormal);

    }
}

這個例子演示了Unsafe直接為某個類的實例分配內存,註意是只分配內存,沒有順便調用構造方法。

運行結果:

null: 0
test: 25

當然,Unsafe類裏面還有好多的底層操作,寶藏後面慢慢挖掘。就到這裏吧!!!

 

Atomic類和CAS