引:原子变量与非阻塞同步机制相比于基于锁的方案可以拥有更高的性能和可伸缩性。
锁的劣势
- 通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有守护变量的锁,都能采用独占方式来访问这些变量,并且对变量的任何修改对随后获得这个锁的其他线程都是可见的。但是当在锁上存在激烈的竞争时,调度开销与工作开销的比值会非常高。
- volatile变量是一种更轻量级的同步机制,但是虽然他们提供了相似的可见性保证,但不能用于构建原子的复合操作。因此,当一个变量依赖其他的变量时,或者当变量的新值依赖旧值时,就不能使用volatile。
硬件对并发的支持
- 独占锁是一种悲观技术——它假设最坏的情况,并且只有在确保其他线程不会找出干扰的情况下才能执行下去。
- 比较并交换(CAS,硬件指令)是一种乐观的技术——通过这种方法可以在不发生干扰的情况下完成更新操作,不过这种方法需要借助检查机制来判断在更新过程中是否存在其他线程的干扰,如果存在,这个操作将失败,并且可以重试(也可以不重试)。
下面是模拟CAS操作代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// CAS含义:我认为V的值应该是A,如果是,那么把值更新为B,否则不修改并告诉V的值实际是多少
public class SimulatedCAS {
private int value;
public synchronized int get() { return value; }
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
value = new Value;
}
return oldValue;
}
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return (expectedValue == compareAndSwap(expectedValue, newValue));
}
}
CAS的优点:当竞争程序不高时,性能远远高于基于锁的方案。
CAS的缺点:它将使调用者处理竞争问题(通过重试、回退、放弃),而在锁中能自动处理竞争问题(线程在获得锁之前将一直阻塞)。
原子变量类
原子变量类在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持(比较并交换指令)。
共有12个原子变量类:可分为四组:标量类、更新器类、数组类以及复合变量类。最常用的原子变量类就是标量类:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。
原子变量与锁适用的不同并发场景:
- 在中低程序的竞争、锁占用时间不长的情况下,原子变量能提供更高的可伸缩性。
- 而在高强度的竞争下,锁能够更有效的地避免竞争。
非阻塞算法
无阻塞算法: 如果在某种算法中,一个线程的失败或者挂起不会导致其他线程也失败或挂起,那么这种算法就被称为无阻塞算法。
无锁算法: 如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法被称为无锁算法。
如果在算法中仅将CAS用于协调线程之间的操作,并且能正确地实现,那么它既是无阻塞算法,又是无锁算法。
利用CAS并发指令可以实现非阻塞的同步容器,例如实现非阻塞的栈、非阻塞的链表以及原子的域更新器等。
总结
- 非阻塞算法通过底层的并发原语(例如比较并交换)来维持线程的安全性。这些底层的原语通过原子变量类向外公开,从而为整数和对象引用提供原子的更新操作。
- 在JVM从一个版本升级到下一个版本的过程中,并发性能主要提升都来自于对非阻塞算法的使用。
参考
- 《Java并发编程实战》