上一篇讲解了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
扫描下方二维码获得更多精彩:
感谢阅读。最后祝大家工作愉快、身体健康!