開發與維運

java併發原子類AtomicBoolean解析

一、為什麼使用AtomicBoolean?


我們平時一般都是使用的boolean來表示布爾變量,但是在多線程情況下boolean是非線程安全的。為什麼是非線程安全的呢?我們看下面的這個例子:

private volatile Boolean flag = false;
publich void test() {
  synchronized(flag) {
    //去做其他的事:訪問臨界資源
    flag = !flag;
  }
}

大家可以看到,這個操作好像並沒有什麼問題,我們使用了synchronized關鍵字對flag對象進行上鎖,這時候同一時刻就只能有一個線程去運行test方法中的代碼了。如果你這樣想那就大錯特錯了,其實此時synchronized對這塊資源不起任何作用。為什麼不起作用呢?我們來分析一下:

對於對象flag來說主要有兩個值true和false。但是true和false卻是兩個不同的常量對象,也就是說synchronized關鍵字其實鎖住的只是false對象,當下面test方法中把flag改為true就表示了另外一個對象。這就是為什麼synchronized關鍵字失效的原因。

如何去解決這個問題呢?這時候我們的AtomicBoolean類就可以出馬了,他可以很好的去解決這個問題。下面我們就來好好地分析一下AtomicBoolean類吧。

二、AtomicBoolean的使用


在一開始我們曾經也說到,在單線程中我們使用boolean是完全沒有問題的,我們看如下代碼:

public class Test6 implements Runnable{
    public static boolean flag = true;
    private String name;
    public Test6(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        if(flag) {
             System.out.println(name + ",起床");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",上班");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",下班");
            flag = !flag;
        }else {
             System.out.println(name + "想進來卻進不來");
        }
    }
}

上面的代碼功能是這樣的,起床上班下班這三件事,一個人做完另外一個才可以繼續做。這種boolean情況,在單線程狀態下是安全的,但是在多線程條件下就是非線程安全的。我們可以創建兩個線程去測試一下:

v2-d828133b50addde53c5dce9a7767d91a_1440w.jpg

原本我們想的是起床上班下班這三件事,一個人完成另外一個人再做,但是通過運行結果我們會發現,並列執行了。怎麼才能實現我們的功能呢?我們再看下面的代碼:

public class Test6 implements Runnable{
     private static AtomicBoolean flag = new AtomicBoolean(false);
     private String name;
    public Test6(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        if(flag.compareAndSet(false, true)) {
             System.out.println(name + ",起床");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",上班");
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ",下班");
            flag.set(false);
        }else {
             System.out.println(name + "想進來卻進不來");
        }
    }
}

此時我們換成AtomicBoolean,在運行一下看看:

v2-fc0e35d390c52244097e0ab05a20cad4_1440w.jpg

我們會看到,此時執行的順序就確定了張無忌想進來卻進不來了。這就是其基本使用。下面我們分析一下其原理。

三、源碼分析


想要了解其原理我們就必須要到源碼中去看。在上面我們使用了compareAndSet方法,下面我們進入到這個方法中看看其源碼實現:

/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

這個compareAndSet源碼裡面調用了unsafe的compareAndSwapInt方法,也就是使用了CAS機制,舉一個我之前舉的例子,這裡expect和update是什麼意思呢?也就是說我們現在的boolean如果不是except那就不更新,如果是我們預期的except,那就更新,更新的值就是update。也就是CAS原理,我們通過例子解釋一下:

要給兒子訂婚,你預期的兒媳婦是西施,但是兒子找的女朋友是貂蟬,你一看不是你預期的西施(except),一氣之下就什麼也不做,如果是預期的西施,那就給他們訂婚。

注意:在這裡我們還會發現一個問題,那就是我們的Boolean其實轉化成了int類型,1表示true 0表示false。

這就是compareAndSet實現,底層使用的是CAS機制。當然還有很多其他的方法,我們可以看一下:

//返回當前值
public final boolean get() {
    return value != 0;
}
//先返回舊值,再設置新值
public final boolean getAndSet(boolean newValue) {
    boolean prev;
    do {
        prev = get();
    } while (!compareAndSet(prev, newValue));
    return prev;
}
//設置新值
public final void set(boolean newValue) {
    value = newValue ? 1 : 0;
}
//設置新值,該操作會讓Java插入Store內存屏障,避免發生寫操作重排序
public final void lazySet(boolean newValue) {
    int v = newValue ? 1 : 0;
    unsafe.putOrderedInt(this, valueOffset, v);
}

對於AtomicBoolean類其實是非常簡單的。也是java併發機制中比較簡單的類。這篇文章就先到這。如有問題還請指正。

Leave a Reply

Your email address will not be published. Required fields are marked *