java基礎之執行緒 認識原子類

楚棠發表於2019-03-16

把java基礎擼一邊,從簡單的開始。

執行緒部分:

學習原子類,先來了解一下一個概念,樂觀鎖與悲觀鎖。

樂觀鎖與悲觀鎖:

樂觀鎖:

簡單理解:就是認為該資料在獲取的時候不會被其他執行緒修改,更新的時候判斷一下就可以了。是否一樣,像是快取。更新給個標誌就可以。在前面的介紹的volatile就是個樂觀鎖,自認為不會被其他執行緒修改,其他執行緒讀就不會出現髒資料的出現。樂觀鎖,感覺就像是個快取資料一樣,會更新,明知會更新,但取的時候放心大膽樂觀地取。

悲觀鎖

簡單理解,就是和樂觀鎖相反。其他執行緒必定會對資料進行修改,不敢放心大膽得取資料,所以要多這個資料要強行鎖住,像是synchronize,對執行緒進行互斥。保證資料讀取的時候不會被修改。

推薦文章:樂觀鎖與悲觀鎖

原子類:

悲觀鎖這樣對資料進行了保護,運算元據的時候,只有一個執行緒在場。synchronize就證明了這點,但是這樣讓其他執行緒堵塞又執行是非常消耗效能的。那麼樂觀鎖是怎麼保證樂觀鎖的,volatile是肯定不行的。

volatile,這個修飾詞只能保證可見性。不能保證原子性。

例項<一>:

public class Demo41 {

    public volatile int val = 0 ;

    public void set(){
        val++;
    }

    public static void main(String[] age){
        Demo41 demo41 = new Demo41();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (;;){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    demo41.set();
                    System.out.println(Thread.currentThread().getName() + "   val : "+demo41.val);
                }

            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (;;){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    demo41.set();
                    System.out.println(Thread.currentThread().getName() + "   val : "+demo41.val);
                }
            }
        }).start();
        ...
    }

}複製程式碼

列印結果:

Thread-2   val : 12
Thread-0   val : 14
Thread-1   val : 14
Thread-2   val : 15
Thread-0   val : 17
複製程式碼

java除了synchronize提供原子性,還提供了很多。比如原子類系列

import java.util.concurrent.atomic.Atomic**;複製程式碼

例項<二>:

public class Demo41 {

    public AtomicInteger val = new AtomicInteger(0) ;

    public int set(){
        return val.getAndIncrement();//自加1
    }

    public static void main(String[] age){
        Demo41 demo41 = new Demo41();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (;;){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "   val : "+demo41.set());
                }

            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (;;){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "   val : "+demo41.set());
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (;;){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "   val : "+demo41.set());
                }
            }
        }).start();
    }

}複製程式碼

列印

Thread-0   val : 0
Thread-1   val : 1
Thread-2   val : 2
Thread-0   val : 3
Thread-1   val : 4
Thread-2   val : 5
Thread-0   val : 6複製程式碼

簡單看原始碼:

AtomicIn系列有很多,下面是AtomicInteger的原始碼。我拿個比較簡單的講,標題是簡單看原始碼。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // 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;複製程式碼

發現:

1:繼承了Number。這個類有一些對數值型別的操作。

2:實現了Serialzable介面。

3:成員變數建立了Unsafe。

4:成員變數中有Long型別valueOffset,private修飾,不能被其他類改動,能猜到這個欄位就是這個類操作的核心了。

4:靜態程式碼塊中,對valueOffset做了處理。unsafe.objectFieldOffset(File)這個方法,獲取該屬性在記憶體中的偏移位置。Long型別用來儲存這個獲取的地址。

5:擁有private和volatile修飾的value int型別值,也就是這個類計算的值了。

使用方法探究:

在上面例項的程式碼中,有兩處使用一個是例項化的時候,一個是自增的時候。

public AtomicInteger val = new AtomicInteger(0) ;

public int set(){
    return val.getAndIncrement();
}複製程式碼

看構造方法的原始碼

public AtomicInteger(int initialValue) {
    value = initialValue;
}複製程式碼

只是個普通的複製功能。注意,發現5

在來看自加方法

/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
 */
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}複製程式碼

這裡使用到了unsafe類的操作。進行檢視這個方法的原始碼

//記憶體地址V,舊的預期值A,即將要更新的目標值B。
/*public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = this.getIntVolatile(o, offset); //拿到記憶體位置的最新值
        //拿到記憶體位置的最新值v,使用CAS嘗試修將記憶體位置的值修改為目標值v+delta,如果修改失敗,
則獲取該記憶體位置的新值v,然後繼續嘗試,直至修改成功
    } while(!this.compareAndSwapInt(o, offset, v, v + delta));

    return var5;
}*/複製程式碼

這裡看到了的是CSA演算法,即比較並替換,是一種實現併發演算法時常用的技術。

這個比較是個do...while迴圈比較,這裡可以看到那個Long型別存地址是幹嘛的了,就是為了取資料的,獲得現在記憶體存取的值,然後拿需要修改的值進行比較,如果是樣的說明修改成功,如果沒有修改成功,說明還有人改,所以繼續迴圈(沒有做重量鎖,如synchronize所以會有多個執行緒進入)上面的註釋寫了,大家放大點看,也可以從下面推薦文章中看。

還要注意,為什麼可以及時通知到資料的更改,是擁有有volatile修飾,可見性。在建立原子類的時候,value被volatile修飾。不得不說句,牛逼。

原子類系列,還有可以操作物件,陣列。大同小異,就不一一介紹了。謝謝各位觀看。

推薦文章:面試畢問的CAS,你懂嗎?



相關文章