文章大纲
Java的Queue接口
影响队列性能的因素
数组和链表
锁和CAS
非线程安全队列
“渣猪”队列
线程安全队列-非阻塞队列
线程安全队列-阻塞队列
JCTools包中的队列
Disruptor队列
相关资料
速记卡-队列方法
速记卡-队列分类
最近对JAVA的队列比较感兴趣,今天就来探讨下。
有句话怎么说来着, 一流企业定标准、二流企业做品牌、三流企业卖技术、四流企业做产品。
所以了解队列当然就是先从接口开始啦。
1 数组和链表
队列的底层结构只有两种, 数组Array, 链表Linked。
2 锁和CAS
我们先介绍下锁,后面再谈谈为什么要用到锁。
synchronized, ReentrantLock, 是Java中的悲观锁。
CAS,是Compare And Swap的缩写,顾名思义就“比较并替换”,是一种乐观锁实现。
下面用Benchmark在Java8环境下测试一下悲观锁,CAS和无锁时的性能。我们用两个线程对一个long类型的字段进行递增操作。
@BenchmarkMode({Mode.SampleTime}) // 测试方法平均执行时间
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 输出结果的时间粒度为微秒
@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.MILLISECONDS) // 预热次数
@Measurement(iterations = 1, batchSize = 1000) // 测试次数
@Threads(2)
@Fork(1)
@State(Scope.Benchmark)
public class LockTest {
private java.util.concurrent.locks.Lock lock = new ReentrantLock();
private long lockIndex = 0;
private long noLockIndex = 0;
private long synIndex = 0;
private AtomicLong atomicLong = new AtomicLong(0);
@Benchmark
public void measureLock() {
lock.lock();
lockIndex++;
lock.unlock();
}
@Benchmark
public void measureSyn() {
synchronized (this) {
synIndex++;
}
}
@Benchmark
public void measureCAS() {
atomicLong.incrementAndGet();
}
@Benchmark
public void measureNoLock() {
noLockIndex++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(LockTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}下面是测试的结果,虽然测试不是很严谨,但是依旧可以看到,无锁时的性能是最好的,其次是CAS,最后才是悲观锁。
Benchmark Score Error Units
LockTest.measureNoLock 0.003 ± 0.001 ms/op
LockTest.measureCAS 0.038 ± 0.001 ms/op
LockTest.measureSyn 0.086 ± 0.001 ms/op
LockTest.measureLock 0.178 ± 0.001 ms/op实现一个队列,选择锁技术的优先级是:ReentrantLock < synchronized < CAS < 无锁。
经过上面两点总结,我们发现:要想实现一个性能好的队列,最好是基于数组的,并且最好是无锁的。
那么我们就先去Java中找找有没有采用“数组+无锁”方案的队列。
3 非线程安全队列
然而,无锁的队列虽然是快了,但是却无法支撑多人运动呐。
无锁,也就代表着非线程安全。无锁的队列在同一时间只能支持一个生产者线程或者一个消费者线程。
当出现多个生产者同时往一个队列放入元素,或者多个消费者同时从一个队列中取出元素时,就会导致多个线程争抢头指针或者尾指针。
4“渣猪”队列
我们对“渣猪”的这种行为是表示谴责的,但是 “渣猪” 这种队列绝对是我们梦寐以求的高性能队列。
首先,他生活的一切都处理的井井有条,聊妹,跳舞,上节目。这还不是重点,重点是,并发聊妹,多人运动,对多线程场景的并发处理能力堪称惊人!这妥妥的高并发属性啊。
半夜三四点刚给你道晚安,凌晨五点就可以起来发声明,也即是说不管什么时候找他,都能给你响应,这妥妥的高可用属性啊!
一天24小时都能完美地利用起来,也即是说,如果给他一个性能100分机器, “渣猪”能给你跑出110分的效果来。这简直就是高性能框架的典范。
哎呀,话题扯远了!
“沐浴露还有,迪丽热巴也依旧爱着我”,队列怎么才能支持多人运动呢?
我们需要的是线程安全的队列。
5 线程安全队列
非阻塞队列
我们优先考虑的方案当然是最快最强的“数组+CAS”。然而,现实再次打脸,我们没有在Java并发包下找到这种队列。
那么有没有基于“链表+CAS”方案的队列呢?幸运的是,ConcurrentLinkedQueue队列正是基于这个方案实现,采用了Michael-Scott算法。
其实基于CAS的实现方案都非常复杂,这个就放在下一篇再讲解了。
阻塞队列
put方法和take方法的特点就是阻塞了,这也是阻塞队列的特点。
当队列是空时,消费者线程想从队列中读取元素会被阻塞;当队列是满时,生产者想往队列中插入元素也会被阻塞。
原有的offer方法和poll方法,也可以支持设置超时时间了。
时间不早了,阻塞队列就先介绍到这,下一篇我们再一起探讨下阻塞队列ArrayBlockingQueue和LinkedBlockingQueue之间的故事。
6 后续
X References
速记卡