一、為什麼使用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情況,在單線程狀態下是安全的,但是在多線程條件下就是非線程安全的。我們可以創建兩個線程去測試一下:
原本我們想的是起床上班下班這三件事,一個人完成另外一個人再做,但是通過運行結果我們會發現,並列執行了。怎麼才能實現我們的功能呢?我們再看下面的代碼:
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,在運行一下看看:
我們會看到,此時執行的順序就確定了張無忌想進來卻進不來了。這就是其基本使用。下面我們分析一下其原理。
三、源碼分析
想要了解其原理我們就必須要到源碼中去看。在上面我們使用了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併發機制中比較簡單的類。這篇文章就先到這。如有問題還請指正。