AtomicBoolean源码解析
AtomicBoolean类介绍
AtomicBoolean类是为了解决多线程情况下,Boolean值赋值不安全问题而提供的工具类。它处在Java.concurrent.atomic包下。
为什么说Boolean的赋值在多线程情况下是不安全的呢?
先看下下面这段代码
1 | public class UseBooleanUnsae implements Runnable { |
上面的代码,定义了一个静态属性flag,设置为flase,实现了runnable接口并重载了它的run方法。在run方法中,循环1000次,将flag置为它的相反值。
最后,在main函数中,启动了5个线程,输出最后的flag值。
那么flag的值会是什么呢,false?因为一共5个线程,一共对flag取反5000次,所以flag的值一定是false。
答案是:两者皆有可能,true也有可能,多跑几次就能看到了。
究其根因,就是flag = true或者flag = false,虽然是原子类型,但是在多线程的情况下,不能保证每个线程都能及时读取到这个flag变量的最新值。因为每个线程都有自己的工作内存,每次某个线程执行完毕赋值之后,会把flag的值刷新回主内存,这个时候,其他线程就能从主内存读取到最新的值。
但如果在线程刷新完主内存之前,就读取走了flag的值,那么结果就是未知的了。
这种情况,一般我们需要用volative关键字修饰flag域,volative能保证线程每次修改完之后都把最新值刷新回主内存,每次读取都从主内存读取最新的值。
但是,今天我们说的不是这个,我们来一起看看,JDK为我们封装的Boolean原子封装类AtomicBoolean。
类图
AtomicBoolean类只是简单地实现了Serialable接口,方便序列化。
主要属性
1 | private static final Unsafe unsafe = Unsafe.getUnsafe(); |
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 | /** |
根据初始化的值,将属性value的值置为1或者0。
AtomicBoolean()
1 | /** |
属性value的值默认为0,因此,AtomicBoolean的值也默认为false。
get()
1 | /** |
返回当前AtomicBoolean对象的值,以属性value是否为0判断。
compareAndSet(boolean, boolean)
1 | /** |
该方法有两个入参,expect(期望值)和update(更新值)。
方法的逻辑就是,如果AtomicBoolean对象属性value的值刚好为expect这个值,那么就将value的值更新为update,否则不更新。
上面的逻辑其实就是完成了下面这个代码片段做的事情:
1 | if(value == true) { |
weakCompareAndSet(boolean,boolean)
1 | /** |
这个方法的本意是,调用weakCompareAndSet方法时不能保证指令重排的发生,因此,这个方法有时候会毫无理由地失败。
但是,从实现上来看,是和compareAndSet是一致的,暂时可以当作一个函数使用。
set(boolean)
1 | /** |
lazySet(boolean)
1 | /** |
最终将value设置为给给定值。延时设置value的值。
getAndSet(boolean)
1 | /** |
设置新值,并返回旧值。
toString()
1 | /** |
应用
下面我们把上面的线程不安全代码修改成使用AtomicInteger的。
1 | public class UseAtomicBoolean implements Runnable { |
这样,就不会出现flag到最后还是true的场景了。