Java高并发编程详解:深入理解并发核心库
上QQ阅读APP看书,第一时间看更新

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的方法。