并发编程之AtomicBoolean

上一篇讲解了AtomicInteger的基本用法与大致原理,你学会了吗?

想必只要是认真看过文章的肯定都熟悉AtomicInteger的相关用法了吧。


那今天我们来学习另一个原子类:AtomicBoolean


简介

AtomicBoolean提供了一种原子性地读写布尔型变量的解决方案,通常情况下,该类将被用于原子性地更新状态标志位,比如:flag。


看到以上特征讲解,然后对比AtomicInteger的相关知识,是不是觉得AtomicBoolean的底层原理应该是使用一个volatile修饰的boolean属性呢?


那我们来看看源码:

public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    
    private static final long valueOffset;
    //volatile修饰的int类型的value属性
    private volatile int value;
}

什么情况?

居然和AtomicInteger的一样!

聪明的读者肯定以前就知道了吧!
反正最开始我以为应该是boolean类型的属性,看来还是不能瞎猜,得实际去看看了才知道。

那又是如何变成布尔类型的了呢?

我们来看看AtomicBoolean的构造函数和CAS核心方法:

public AtomicBoolean(boolean initialValue) {
    value = initialValue ? 1 : 0;
}

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);
}

再看看get方法:

public final boolean get() {
    return value != 0;
}

是不是有种拨开云雾见青天的感觉,原来是结合三目运算转换为int类型,再使用unsafe.compareAndSwapInt这个native方法达到我们想要的结果。


基本用法

method的基本用法我在这里就不再详细讲解了,其实和AtomicInteger基本一致。

好啦,那今天我们就到这里!

嗯……这是不可能的,怎么可以这么短呢,你们都还没尽兴呢。


实战

上一篇文章中我们解决i++的线程安全,使用到了synchronized关键字和显示锁Lock,但是对比它们的性能后,发现其实性能差别很大;synchronized关键字的BLOCKED线程占比太高,Lock虽然WAITING的线程占比较高,但是不会占用CPU太多资源。


其原因是synchronized关键字在争夺锁时,没有争到Monitor Enter的线程将进入阻塞状态,并且是一种不能中断的阻塞,其线程只能等待持有锁的线程释放monitor监视器后才能继续进行争夺。


本文就利用AtomicBoolean的特性,实现一个简单的非阻塞Lock:

public class BooleanLock {

    /**
     * 全局标志位
     */
    private final AtomicBoolean flag = new AtomicBoolean(false);

    /**
     * 存储数据副本
     */
    private final ThreadLocal<Boolean> threadLocal = ThreadLocal.withInitial(() -> false);

    /**
     * 尝试获取锁
     */
    public boolean tryLock() {
        boolean result = flag.compareAndSet(false, true);
        if (result) {
            threadLocal.set(true);
        }
        return result;
    }

    /**
     * 释放锁
     */
    public boolean unLock() {
        if (threadLocal.get()) {
            threadLocal.set(false);
            return flag.compareAndSet(true, false);
        }
        return false;
    }
}

1、首先我们使用AtomicBoolean定义标志位

2、创建tryLock方法,用于尝试获取锁,由于AtomicBoolean的原子特性,从而达到只有一个线程能修改成功

3、创建unLock方法,用于释放锁,重置flag标志位

4、这里使用ThreadLocal初始化线程的标志位,也是为了防止没有抢到锁的线程在执行unLock操作的时候把flag给修改了(具体看下面测试就明白了)

到这里我们的简易版Lock就搞定了,那我们来进行测试吧!

测试代码如下:

@Slf4j
public class BooleanLockTest {

    public static void main(String[] args) {

        AddThread addThread = new AddThread();
        IntStream.range(0, 10).forEach(
                value -> new Thread(addThread).start()
        );
    }

    static class AddThread implements Runnable {
        //初始化锁
        private final BooleanLock booleanLock = new BooleanLock();
        //初始化i值
        private int i = 0;

        @Override
        public void run() {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while (true) {
                try {
                    //尝试获取锁
                    if (booleanLock.tryLock()) {
                        //自增操作
                        i++;
                        log.info("当前线程:{} ,i值为:{}", Thread.currentThread().getName(), i);
                        TimeUnit.MILLISECONDS.sleep(current().nextInt(100));
                    } else {
                        TimeUnit.MILLISECONDS.sleep(current().nextInt(100));
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    if (booleanLock.unLock()) {
                        log.info("当前线程:{} 释放锁", Thread.currentThread().getName());
                    }
                }
            }
        }
    }
}

测试结果如下:

……省略
[Thread-5] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-5 ,i值为:16
[Thread-5] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-5 释放锁
[Thread-5] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-5 ,i值为:17
[Thread-5] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-5 释放锁
[Thread-6] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-6 ,i值为:18
[Thread-6] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-6 释放锁
[Thread-6] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-6 ,i值为:19
[Thread-6] INFO com.xiaozhi.atomic.boo.BooleanLockTest - 当前线程:Thread-6 释放锁
……省略

此处省略了很多日志,其结果能看出是线程安全的,同时只有一个线程在执行核心操作。

特别说明:我们这样实现的lock在没有获得锁时,直接执行了finally(这也是为什么要使用ThreadLocal进行存储数据的原因,防止没有抢到锁的线程直接修改flag),则不会存在wait状态等待CPU调度,于是使用while死循环进行无限制的抢占锁。

这里主要是利用AtomicBoolean的原子特性,实现一个简易版的显示锁,对比于synchronized来讲,BooleanLock达到的效果是线程获取锁失败时,会立即返回失败,则不会阻塞的解决方案。


AtomicBoolean的讲解就到这里;针对AtomicInteger、AtomicLong、AtomicBoolean基本上算是对java基础类型的操作了,那我们自己的java类如何保证原子操作呢?

请看下回分解:AtomicRefrence,AtomicStampedRefrence

我们拭目以待吧!

公众号传送门——《并发编程之AtomicBoolean》


以下是我码云的地址,会上传并发编程系列的所有代码:
https://gitee.com/songyanzhi/JUC


扫描下方二维码获得更多精彩:
在这里插入图片描述
感谢阅读。最后祝大家工作愉快、身体健康!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值