Java多线程:新特性—原子变量和CAS

硬件对并发的支持


现在,几乎所有的现代处理器中都包含了某种形式的原子读-改-写指令,例如比较和交换(CAS)或者关联加载和条件存储(Load-Linked/Store-Conditional)。

操作系统和JVM可以使用这些指令来实现锁和并发的数据结构。例如Java的ConcurrentlinkedQueue就是使用CAS实现的。

Java5以后也提供了对这些指令的支持。当多个线程尝试使用CAS同时更新同一个变量时候,只有一个线程能更新变量的值,而其他线程都将失败。

然而,失败的线程并不会被挂起(这与获取锁的情况不同:当获取锁失败时,线程将会被挂起),而是被告知在这次竞争中失败,并且可以重试。

由于一个线程在竞争CAS时失败不会被阻塞,因此它可以决定是否重新尝试,或者执行一些恢复操作,也或者不执行任何操作。

这种灵活性就大大减少了与锁相关的活跃性风险。

实际上,当竞争程度不高时,基于CAS的计数器在性能上远远高于基于锁的计数器,而在没有竞争时候甚至更加高。

CAS的主要缺点是:它将使调用者处理竞争问题(通过重试、回退、放弃),而在锁中能自动处理竞争问题(线程在获得锁之前将一直阻塞)。也可能产生ABA问题。

锁的劣势


若使用锁机制,当多个线程同时请求锁,那么JVM就需要借助操作系统的功能。

那么一些线程将会被挂起并且在稍后恢复运行。当线程恢复执行时,必须等待其他线程执行完它们的时间片以后,才能被调度执行。

在挂起和恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断。

与锁相比,volatile变量是一种更加轻量级的同步机制,因为在使用这些变量时候不会发生上下文切换或者线程调度等操作。

Java5之后,专门提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,称为原子变量类。

原子变量类直接利用了硬件对并发的支持,采用CAS算法提供了更高的可伸缩性。

例子


使用原子变量不再需要对变量加锁,例如:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
}
}