2.2 AtomicBoolean详解
AtomicBoolean提供了一种原子性地读写布尔类型变量的解决方案,通常情况下,该类将被用于原子性地更新状态标识位,比如flag。
2.2.1 AtomicBoolean的基本用法
AtomicBoolean提供的方法比较少也比较简单,本节只对其做简单介绍,其基本原理与AtomicInteger极为类似。
(1)AtomicBoolean的创建
// AtomicBoolean 无参构造 AtomicBoolean ab = new AtomicBoolean(); assert !ab.get(); // AtomicBoolean 无参构造,等价于 AtomicBoolean(false) ab = new AtomicBoolean(false); assert !ab.get();
(2)AtomicBoolean值的更新
▪ compareAndSet(boolean expect, boolean update):对比并且设置boolean最新的值,类似于AtomicInteger的compareAndSet方法,期望值与Atomic Boolean的当前值一致时执行新值的设置动作,若设置成功则返回true,否则直接返回false。
// 无参构造AtomicBoolean,默认为false AtomicBoolean ab = new AtomicBoolean(); // 更改失败 assert !ab.compareAndSet(true, false); // ab.get()==false assert !ab.get(); // 更改成功 assert ab.compareAndSet(false, true); // 更改后的值为true assert ab.get();
▪ weakCompareAndSet(boolean expect, boolean update):同上。
▪ set(boolean newValue):设置AtomicBoolean最新的value值,该新值的更新对其他线程立即可见。
// 无参构造AtomicBoolean,默认为false AtomicBoolean ab = new AtomicBoolean(); assert !ab.get(); // 设置新值,AtomicBoolean的最新值为true ab.set(true); assert ab.get();
▪ getAndSet(boolean newValue):返回AtomicBoolean的前一个布尔值,并且设置新的值。
// 无参构造AtomicBoolean,默认值为false AtomicBoolean ab = new AtomicBoolean(); assert !ab.get(); // 前值依然为false assert !ab.getAndSet(true); // 更新后的结果为true assert ab.get();
▪ lazySet(boolean newValue):设置AtomicBoolean的布尔值,关于lazySet方法的原理已经在2.1节中介绍过了,这里不再赘述。
(3)其他方法
▪ get():获取AtomicBoolean的当前布尔值。
2.2.2 AtomicBoolean内幕
AtomicBoolean的实现方式比较类似于AtomicInteger类,实际上AtomicBoolean内部的value本身就是一个volatile关键字修饰的int类型的成员属性。
public class AtomicBoolean implements java.io.Serializable { private static final long serialVersionUID = 4654671469794556979L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); // valueOffset将用于存放value的内存地址偏移量 private static final long valueOffset; static { try { // 获取value的内存地址偏移量 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
2.2.3 Try Lock显式锁的实现
在《Java高并发编程详解:多线程与架构设计》一书的第4章和第5章两个章节中,我们分别详细介绍了synchronized关键字的使用以及synchronized关键字存在的缺陷,其中,当某个线程在争抢对象监视器(object monitor)的时候将会进入阻塞状态,并且是无法被中断的,也就是说synchronized关键字并未提供一种获取monitor锁失败的通知机制,执行线程只能等待其他线程释放该monitor的锁进而得到一次机会,本节将借助于AtomicBoolean实现一个可立即返回并且退出阻塞的显式锁Lock。
程序代码:TryLock.java
package com.wangwenjun.concurrent.juc.automic; import java.util.concurrent.atomic.AtomicBoolean; public class TryLock { // ①在TryLock内部,我们借助于AtomicBoolean 的布尔原子性操作方法 // 因此需要先定义一个AtomicBoolean并且使其初值为false private final AtomicBoolean ab = new AtomicBoolean(false); // ②线程保险箱,用于存放与线程上下文关联的数据副本 private final ThreadLocal<Boolean> threadLocal = ThreadLocal.withInitial(() -> false); // 可立即返回的lock方法 public boolean tryLock() { // ③借助于AtomicBoolean的CAS操作对布尔值进行修改 boolean result = ab.compareAndSet(false, true); if (result) { // ④当修改成功时,同步更新threadLocal的数据副本值 threadLocal.set(true); } return result; } // 锁的释放 public boolean release() { // ⑤判断调用release方法的线程是否成功获得了该锁 if (threadLocal.get()) { // ⑥标记锁被释放,并且原子性地修改布尔值为false threadLocal.set(false); return ab.compareAndSet(true, false); } else { // 直接返回 return false; } } }
上述代码虽然非常简短,但是其借助于AtomicBoolean的原子性布尔值更新操作的方法compareAndSet可以以Lock Free的方式进行方法同步操作。下面就来简单分析一下操作的过程。
▪ 在注释①处,我们定义了一个AtomicBoolean类型的属性ab,其初始值为false,表明当前的锁未被任何线程获得,也就是说某线程可以成功获得对该锁的持有。
▪ 在注释②处,我们定义了一个ThreadLocal<Boolean>,并且重写其初始化方法返回false,该ThreadLocal的使用在TryLock中非常关键,我们都知道显式锁为了确保锁能够被正确地释放,一般会借助于try..finally语句块以确保release方法能够被执行,因此为了防止某些未能成功获取锁的线程在执行release方法的时候改变ab的值,我们需要借助于ThreadLocal<Boolean>中的数据副本进行标记和判断。
▪ 在注释③处,我们使用AtomicBoolean的compareAndSet方法对ab当前的布尔值进行CAS操作,当预期值与ab当前值一致时操作才能成功,否则操作将直接失败,因此执行该方法的线程不会进入阻塞,这一点很关键。
▪ 如果某线程成功执行了对ab当前布尔值的修改,那么我们需要将其在(注释④处)ThreadLocal<Boolean>关联的数据副本标记为true,以标明当前线程成功获取了对TryLock的持有。
▪ release方法需要秉承一个原则,那就是只有成功获得该锁的线程才有资格对其进行释放,反映到我们的代码中就是执行对ab当前值布尔值的更新动作,详见注释⑤。
▪ 在注释⑥处确认当前有资格进行锁的释放以后,就可以对ab当前布尔值进行更新操作了,并且标记当前线程已将锁释放。
完成了TryLock代码的开发及详细分析之后,我们就需要使用它了,并且能够验证在同一时刻是否只有一个线程才能成功获得TryLock显式锁。
程序代码:TryLockExample.java
package com.wangwenjun.concurrent.juc.automic; import java.util.concurrent.TimeUnit; import static java.lang.Thread.currentThread; import static java.util.concurrent.ThreadLocalRandom.current; public class TryLockExample { private final static Object VAL_OBJ = new Object(); public static void main(String[] args) { // 定义TryLock锁 final TryLock lock = new TryLock(); final List<Object> validation = new ArrayList<>(); // 启动10个线程,并且不断地进行锁的获取和释放动作 for (int i = 0; i < 10; i++) { new Thread(() -> { while (true) { try { // 尝试获取该锁,该方法并不会导致当前线程进入阻塞 if (lock.tryLock()) { System.out.println(currentThread() + ": get the lock."); // 进行校验,以确保validation中只存在一个元素 if (validation.size() > 1) { throw new IllegalStateException("validation failed."); } validation.add(VAL_OBJ); TimeUnit.MILLISECONDS.sleep(current().nextInt(10)); } else { // 未获得锁,简单做个休眠,以防止出现CPU过高电脑死机的情况发生 TimeUnit.MILLISECONDS.sleep(current().nextInt(10)); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 在finally语句块中进行锁的释放操作 if (lock.release()) { System.out.println(currentThread() + ": release the lock."); validation.remove(VAL_OBJ); } } } }).start(); } } }
在上面的代码中,我们启动的10个线程在一个while死循环中不断地进行锁的获得以及释放过程,运行上面的代码不难看出,在同一时刻只会有一个线程能够成功获得对该锁的持有。
程序输出:TryLockExample.java
...省略 Thread[Thread-3,5,main]: get the lock. Thread[Thread-3,5,main]: release the lock. Thread[Thread-3,5,main]: get the lock. Thread[Thread-3,5,main]: release the lock. Thread[Thread-0,5,main]: get the lock. Thread[Thread-0,5,main]: release the lock. Thread[Thread-0,5,main]: get the lock. Thread[Thread-0,5,main]: release the lock. Thread[Thread-0,5,main]: get the lock. Thread[Thread-0,5,main]: release the lock. Thread[Thread-9,5,main]: get the lock. Thread[Thread-9,5,main]: release the lock. Thread[Thread-2,5,main]: get the lock. Thread[Thread-2,5,main]: release the lock. Thread[Thread-2,5,main]: get the lock. Thread[Thread-2,5,main]: release the lock. ...省略
2.2.4 AtomicBoolean总结
本节学习了AtomicBoolean的使用方法,通常情况下,我们可以使用AtomicBoolean来进行某个flag的开关控制。为了加深大家对AtomicBoolean的理解,我们借助于AtomicBoolean实现了一个Try Lock,该显式锁旨在提供线程获取锁失败立即返回的解决方案,本章中的显式锁Lock、ReentrantLock、StampedLock等都提供了Try Lock的方法。