從原子類和Unsafe來理解Java內存模型,AtomicInteger的incrementAndGet方法和Unsafe部分源碼介紹,valueOffset偏移量的理解


例子

i++的簡單流程

眾所周知,i++分為三步:

1. 讀取i的值

2. 計算i+1

3. 將計算出i+1賦給i

 

保證i++操作的線程安全

用鎖和volatile

可以使用來保持操作的原子性變量可見性,用volatile保持值的可見性和操作順序性

從一個小例子引發的JAVA內存可見性的簡單思考和猜想以及DCL單例模式中的VOLATILE的作用:https://www.cnblogs.com/theRhyme/p/12145461.html

 

用java.util.concurrent.atomic包下的原子類

如果僅僅是計算操作,我們自然就想到了java.util.concurrent.atomic包下的原子類,則不必考慮鎖的升級、獲取、釋放等消耗,也不必考慮鎖的粒度、種類、可重入性等;

由於atomic由於底層是Unsafe對象的CAS操作,缺點也很明顯:需要循環時間開銷(在LongAdder中有優化),只能是單個變量CASABA問題(通過AtomicStampedReference解決——增加了stamp類似於version標識)。

 

AtomicInteger方法源碼

incrementAndGet方法源碼

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

incrementAndGet,先increment,再get,所以獲取的是increment后的值,而unsafe.getAndAddInt先get,所以這里需要"+1";

valueOffset是什么呢?

  private static final long valueOffset;

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

AtomicInteger的靜態屬性valueOffset屬性value的偏移量,在類加載的時候,通過AtomicInteger的Field——"value"初始化,后續通過當前的AtomicInteger實例和該valueOffset obtain該實例value屬性的值;

 

個人對valueOffset的理解

如果想獲取一個對象的屬性的值,我們一般通過getter方法獲得,而sun.misc.Unsafe卻不同,我們可以把一個對象實例想象成一塊內存,而這塊內存中包含了一些屬性,如何獲取這塊內存中的某個屬性呢?那就需要該屬性在該內存的偏移量了,每個屬性在該對象內存中valueOffset偏移量不同,每個屬性的偏移量在該類加載或者之前就已經確定了(則該類的不同實例的同一屬性偏移量相等),所以sun.misc.Unsafe可以通過一個對象實例該屬性的偏移量用原語獲得該對象對應屬性的值

 

sun.misc.Unsafe#getAndAddInt IDEA反編譯后的源碼(與JMM相呼應)

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;
    }

unsafe.getAndAddInt具體實現是循環不停的compare主存中取到的值var5(this.getIntVolatile)和當前的線程的工作內存中的值(通過對象實例var1和偏移量var2獲得),直到兩者equal(保證原子性)則更新為新值var5+var4(delta) ,從這里我們也體會到了Java內存模型(線程不能直接操作主存中的值,需要復制一份到自己的工作內存中)

getIntVolatile(主存)和compareAndSwapInt都是sun.misc.Unsafe的native方法。

 

總結

AtomicInteger通過Unsafe對象保證原子性,而Unsafe對象的getAndAddInt方法通過循環比較主存和線程工作內存中的屬性值相等后更新(即CAS)來保證原子性

AtomicInteger的value屬性也被volatile關鍵字修飾:volatile關鍵字的作用

private volatile int value;

 

保證i++原子性的幾種方式

下面是一個非常非常簡單的小例子,分別是非線程安全的i++,鎖同步以及Atomic Class:

public class Counter {
    @Getter
    private volatile int i = 0;

    @Getter
    private volatile AtomicInteger atomicInteger = new AtomicInteger(0);

    public void increment(){
        i++;
        // 1. 讀取i的值
        // 2. 計算i+1
        // 3. 把i+1的值賦給i
    }

    public void incrementSync(){
        synchronized(this) {
            i++;
            // 1. 讀取i的值
            // 2. 計算i+1
            // 3. 把i+1的值賦給i
        }
    }

    public void incrementAtomic(){
        // 先increment再返回
        atomicInteger.incrementAndGet();
    }
}

測試類:

public class AtomicityTest {
    private Counter counter;
    /**
     * 每個線程打印的次數
     */
    private int count;

    @Before
    public void init(){
        counter = new Counter();
        count = 10000;
    }

    /**
     * 非線程安全的i++
     */
    @Test
    public void increment() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < count; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < count; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        // 單元測試必須新起的線程要在主線程里join,否則主線程運行完畢,新起的線程還執行完
        t1.join();
        t2.join();
        /*
        ThreeParamsEquals<Enum, Enum, Enum> equals = (Enum p1, Enum p2, Enum p3) -> p1.equals(p2) && p1.equals(p3);
        while (true){
            if (equals.equals(Thread.State.TERMINATED, t1.getState(), t2.getState())) {
                break;
            }
        }*/
        System.out.println(t1.getState());
        System.out.println(t2.getState());
        // 由於不是原子性操作,兩個線程執行總共20000次i++,結果i的值都小於20000
        System.out.println(counter.getI());
    }


    /**
     * 線程安全的i++
     */
    @Test
    public void incrementSync() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < count; i++) {
                counter.incrementSync();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < count; i++) {
                counter.incrementSync();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(t1.getState());
        System.out.println(t2.getState());
        // 由於保證了原子性,順序性,可見性操作,兩個線程執行總共20000次i++,結果i的值都等於20000
        System.out.println(counter.getI());
    }

    /**
     * 原子類AtomicInteger
     */
    @Test
    public void incrementAtomic() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < count; i++) {
                counter.incrementAtomic();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < count; i++) {
                counter.incrementAtomic();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(t1.getState());
        System.out.println(t2.getState());
        // 由於保證了原子性,順序性,可見性操作,兩個線程執行總共20000次i++,結果i的值都等於20000
        System.out.println(counter.getAtomicInteger());
    }
}

 


免責聲明!

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



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