并发编程实战 - 原子变量与非阻塞同步机制

在java.util.concurrent包的许多类中,例如Semaphore和ConcurrentLinkedQueue,都提供了比Synchronized机制更高的性能和可伸缩性。这种性能提升的主要来源:原子变量和非阻塞的同步机制。

近年来,在并发算法领域的大多数研究都侧重于非阻塞算法,这种算法用底层的原子机器指令代替锁来确保数据在并发访问中的一致性。非阻塞算法被广泛应用于在操作系统和JVM中实现线程/进程调度机制、垃圾回收机制以及锁和其他并发数据结构。

由于非阻塞算法可以使多个线程在竞争相同的数据时不会发生阻塞,因此它能在粒度更细的层次上进行协调,并且极大的减小调度开销。而且,在非阻塞算法中不存在死锁和其他活跃性问题。从Java 5.0开始,可以使用原子变量类(例如AtomicInteger和AtomicReference)来构建高效的非阻塞算法。

即使原子变量没有用于非阻塞算法的开发,它们也可以用做一种“更好的Volatile类型变量”。原子变量提供了与Volatile类型变量相同的内存语义,此外还支持原子的更新操作,从而使它们更加适用于实现计数器、序列发生器等,同时还能比基于锁的方法提供更高的可伸缩性。

1、硬件对并发的支持

在针对多处理器操作而设计的处理器中提供了一些特殊指令,用于管理对共享数据的并发访问。在早期的处理器中支持原子的测试并设置,获取并递增以及交换等指令,这些指令足以实现各种互斥体,而这些互斥体又可以实现一些更加复杂的并发对象。现在,几乎所有的现代处理器中都包含了某种形式的原子读-改-写指令,例如比较并交换或者关联加载/条件存储。操作系统和JVM使用这些指令来实现锁和并发的数据结构。

1.1、比较并交换

在大多数处理器架构中采用的方法是实现一个比较并交换(CAS)指令。CAS包含了三个操作数 - 需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当V的值等于A的值时,CAS才会通过原子的方式用新值B来更新V的值,否则不执行任何操作。无论位置V的值是否等于A,都将返回V原有的值。如下是模仿CAS的示例:
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 = newValue;
return oldValue;
   }
  }
}
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都将失败。然而失败的线程并不会被挂起,而是被告知在这次的竞争中失败,并可以再次尝试。由于CAS能检测到来自其他线程的干扰,因此即使不使用锁也能实现原子的读 - 改 - 写操作。

1.2、JVM对CAS的支持

Java5.0 引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS操作,并且JVM把它们编译为底层硬件提供的最有效的方法。在支持CAS的平台上,运行时把它们编译为相应的(多条)机器指令。在最坏的情况下,如果不支持CAS指令,那么JVM将使用自旋锁。在原子变量类(例如java.util.concurrent.atomic中的AtomicXXX)中使用了这些底层的JVM支持为数字类型和引用类型提供了一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时则直接或间接地使用了这些原子变量类。

1.3、原子变量类

在使用基于原子变量而非锁的算法中,线程在执行时更不容易出现延迟,并且如果遇到竞争,也更容易恢复过来。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作,在发生竞争的情况下能够提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
共有12个原子变量类,可分为4组:标量类(Scalar)、更新器类、数组类以及复合变量类。最常用的就是标量类:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。所有的这些类都支持CAS,此外,AtomicInteger、AtomicLong还支持算术运算。
原子数组类(只支持Integer、Long和Reference版本)中的元素可以实现原子更新。院子数组类为数组的元素提供了volatile类型的访问语义,这是普通数组不具备的特性 - volatile类型的数组仅在数组引用上具有volatile语义,而在其元素上没有。

1.4、非阻塞算法

如果在某种算法中,一个线程的失败或者挂起不会导致其他线程的失败或挂起,那么这种算法就被称为非阻塞算法。如果在算法的每一个步骤中都存在某个线程能够执行下去,那么这种算法也被称为无锁算法。如果在算法中仅将CAS用于协调线程之间的操作,并且能正确地实现,那么它既是一种无阻塞的算法,也是一种无锁的算法。

在非阻塞的算法中通常不会出现死锁和优先级反转(非公平显式锁等)问题。在许多常见的数据结构中都可以使用非阻塞算法,包括栈、队列、优先级队列以及散列表等,而要设计一些新的数据结构,最好还是由专家们来完成。

1.5、原子的域更新器

原子的域更新器类表示现有的volatile域的一种基于反射的“视图”,从而能够在已有的volatile域上使用CAS。在更新器类中没有构造函数,要创建一个更新器对象,可以调用newUpdater工厂方法,并制定类和域的名字。域更新器类没有与某个特定的实例关联在一起,因而可以更新目标类的任意实例中的域。更新器类提供的原子性保证比普通原子类更弱一些,因为无法保证底层的域不被直接修改 - compareAndSet以及其他算术方法只能确保其他使用原子域更新器方法的线程的原子性。

private class Node<T>{
private final E item;
private volatile Node<T> next;

public Node(T item){
this.item = item;
}
}

private static AtomicReferenceFieldUpdater<Node,Node> nextUpdater  = AtomicReferenceFieldUpdater.newUpdater(Node.class,Node.class,"next"

猜你喜欢

转载自blog.csdn.net/json_it/article/details/79212650