AtomicBoolean源码解析

AtomicBoolean源码解析

AtomicBoolean类介绍

AtomicBoolean类是为了解决多线程情况下,Boolean值赋值不安全问题而提供的工具类。它处在Java.concurrent.atomic包下。

为什么说Boolean的赋值在多线程情况下是不安全的呢?

先看下下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class UseBooleanUnsae implements Runnable {

private static boolean flag = false;

@Override
public void run() {
for (int i = 0;i < 10000;i++) {
if (flag) {
flag = false;
} else {
flag = true;
}
}

System.out.println(Thread.currentThread().getName() + "------" + flag);
}


public static void main(String[] args) {
UseBooleanUnsae ubUnsae = new UseBooleanUnsae();
for (int i = 0;i < 5;i++) {
Thread t = new Thread(ubUnsae);
t.start();
}
System.out.println("Flag is " + flag);
}
}

上面的代码,定义了一个静态属性flag,设置为flase,实现了runnable接口并重载了它的run方法。在run方法中,循环1000次,将flag置为它的相反值。

最后,在main函数中,启动了5个线程,输出最后的flag值。

那么flag的值会是什么呢,false?因为一共5个线程,一共对flag取反5000次,所以flag的值一定是false。

答案是:两者皆有可能,true也有可能,多跑几次就能看到了。

结果为true

结果为false

究其根因,就是flag = true或者flag = false,虽然是原子类型,但是在多线程的情况下,不能保证每个线程都能及时读取到这个flag变量的最新值。因为每个线程都有自己的工作内存,每次某个线程执行完毕赋值之后,会把flag的值刷新回主内存,这个时候,其他线程就能从主内存读取到最新的值。

但如果在线程刷新完主内存之前,就读取走了flag的值,那么结果就是未知的了。

这种情况,一般我们需要用volative关键字修饰flag域,volative能保证线程每次修改完之后都把最新值刷新回主内存,每次读取都从主内存读取最新的值。

但是,今天我们说的不是这个,我们来一起看看,JDK为我们封装的Boolean原子封装类AtomicBoolean

类图

AtomicBoolean类图

AtomicBoolean类只是简单地实现了Serialable接口,方便序列化。

主要属性

1
2
3
4
5
6
7
8
9
10
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;

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

AtomicBoolean 主要有三个属性:unSafe,valueoffset,value

unSafe:JDK提供的一个本地方法封装类,它就是AtomicBoolean实现的核心,它提供了CAS方式更新AtomicBoolean的值。

valueOffset:该值保存的是当前value值在这个类中的内存所在地址的偏移量。valueOffset是在上面的静态代码块中初始化的

value:当前值。

需要注意的是value的类型是一个int,而不是boolean。那是因为Unsafe类中只提供了3中类型的CAS操作:int,long,object。

所以,此处使用int型的值替代,如果为true,就将value置为1,否则,置为0.

主要方法

AtomicBoolean(boolean)

1
2
3
4
5
6
7
8
/**
* Creates a new {@code AtomicBoolean} with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}

根据初始化的值,将属性value的值置为1或者0。

AtomicBoolean()

1
2
3
4
5
/**
* Creates a new {@code AtomicBoolean} with initial value {@code false}.
*/
public AtomicBoolean() {
}

属性value的值默认为0,因此,AtomicBoolean的值也默认为false。

get()

1
2
3
4
5
6
7
8
/**
* Returns the current value.
*
* @return the current value
*/
public final boolean get() {
return value != 0;
}

返回当前AtomicBoolean对象的值,以属性value是否为0判断。

compareAndSet(boolean, boolean)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 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);
}

该方法有两个入参,expect(期望值)和update(更新值)。

方法的逻辑就是,如果AtomicBoolean对象属性value的值刚好为expect这个值,那么就将value的值更新为update,否则不更新。

上面的逻辑其实就是完成了下面这个代码片段做的事情:

1
2
3
if(value == true) {
value = false;
}

weakCompareAndSet(boolean,boolean)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public boolean weakCompareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

这个方法的本意是,调用weakCompareAndSet方法时不能保证指令重排的发生,因此,这个方法有时候会毫无理由地失败。

但是,从实现上来看,是和compareAndSet是一致的,暂时可以当作一个函数使用。

set(boolean)

1
2
3
4
5
6
7
8
/**
* Unconditionally sets to the given value.
*
* @param newValue the new value
*/
public final void set(boolean newValue) {
value = newValue ? 1 : 0;
}

lazySet(boolean)

1
2
3
4
5
6
7
8
9
10
/**
* Eventually sets to the given value.
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(boolean newValue) {
int v = newValue ? 1 : 0;
unsafe.putOrderedInt(this, valueOffset, v);
}

最终将value设置为给给定值。延时设置value的值。

getAndSet(boolean)

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();
} while (!compareAndSet(prev, newValue));
return prev;
}

设置新值,并返回旧值。

toString()

1
2
3
4
5
6
7
/**
* Returns the String representation of the current value.
* @return the String representation of the current value
*/
public String toString() {
return Boolean.toString(get());
}

应用

下面我们把上面的线程不安全代码修改成使用AtomicInteger的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class UseAtomicBoolean implements Runnable {
private static AtomicBoolean flag = new AtomicBoolean(false);

@Override
public void run() {
for (int i = 0;i < 10000;i++) {
if (flag.get()) {
flag.set(false);
} else {
flag.set(true);;
}
}

System.out.println(Thread.currentThread().getName() + "------" + flag.toString());
}


public static void main(String[] args) {
UseBooleanUnsae ubUnsae = new UseBooleanUnsae();
for (int i = 0;i < 5;i++) {
Thread t = new Thread(ubUnsae);
t.start();
}
System.out.println("Flag is " + flag.toString());
}
}

这样,就不会出现flag到最后还是true的场景了。