您的位置:首页 > 其它

原子变量和非阻塞同步机制

2013-02-22 23:00 603 查看
现在并发算法领域多数研究都侧重于非阻塞算法,这种算法利用底层的原子机器指令代替锁来确保数据在并发中的一致性。非阻塞算法在设计和实现上比锁方案要复杂的多,但是它能够提供更好的可伸缩性和活跃性。
我们可以使用原子变量来构建高效的非阻塞算法。原子变量不光可以用在构建非阻塞算法上,它还可以当做volatile同时还可以支持原子更新操作。
锁的劣势
现在JVM对非竞争的锁获取和释放等操作进行了极大的优化,但如果有多个线程同时请求锁,那么JVM就需要借助操作系统的功能。如果出现了这种情况,那么一些线程将被挂起并且在稍后运行。当线程恢复执行时,必须等待其他线程执行完它们的时间片。在挂起和恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断。
与锁相比,volatile变量是一种更轻量级的同步机制,因为在使用这些变量时不会发生上下文切换或线程调度等操作。然而,volatile也存在有局限:虽然它们提供了相似的可见性保证,但是不能用于构建原子的复合操作。因此,当一个变量依赖其他的变量时,或变量新值依赖于旧值时,就不能使用volatile变量。
锁还有一个问题就是,当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下被延迟执行,那么所有需要这个锁的线程都无法执行下去。
锁对于细粒度的操作(如:递增计数器)开销还是比较高的,这里就要用到原子变量。
硬件堆并发的支持
独占锁是一种悲观技术---它认为只有在确保其他线程无法干扰到它时才可以开始执行。
而对于细粒度的操作,还有一种更高效的方法,一种乐观的方法。这种方法借助冲突检查机制来判断在更新过程中是否存在来自其他线程的干扰。如果存在,这个操作将失败,并可以重试(也可以不重试)。这里思想有点类似于锁的自旋,但是判别条件是不同的。
现在的处理器都包含了某种形式的原子读-改-写指令,例如比较并交换或关联加载/条件存储。操作系统和JVM利用这些指令来实现锁和并发的数据结构。JAVA5之前,java类不能使用这些指令。
比较并交换
比较并交换(Compare-and-Swap),简称CAS。在大多数处理器(IA32和Sparc)实现了这个指令。
CAS包含了3个操作数。需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当V的值等于A的值,才可以用B的值来替代V的值。无论成功更新V值,最后都将返回原有的V值。
CAS的经典使用模型是:首先从V中读取A,然后根据A计算B,然后在通过CAS以原子方式将V中的值由A变成B。
CAS的主要缺点在于它需要调用者处理竞争问题(通过重试、回退、放弃),而在锁中能自动处理竞争问题(线程在获得锁之前将一直阻塞)。
ABA问题
对于CAS,可能存在V值不变,但是A值都先变为值X然后又变会A的这种情况,其实是不满足CAS条件的。类似的处理时,我们一般会使用数据的版本号来解决。
JVM对CAS的支持
java5之前不支持。5之后如果机器支持CAS,运行时会把它们编译为相应的机器指令,如果不支持,则会使用自旋锁。原子变量中就使用了这些底层的JVM支持为数字类型和引用类型提供一种高效的CAS。
原子变量
原子变量比锁的粒度更细,量级更轻,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上。
原子变量相当于一种泛化的volatile变量,能够支持原子的和有条件的读-写-改操作。
对于中低程度的竞争下,原子变量能提供更高的可伸缩性,而在高强度的竞争下,锁能更有效的避免竞争。
非阻塞算法
一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法就被称为非阻塞算法。如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法也被称为无锁算法。
非阻塞算法中不太会出现死锁和优先级反转问题,但有可能会出现活锁和饥饿问题,因为在算法中会反复的重试。
构建一个非阻塞算法的关键在于找出如何将原子修改的范围缩小到单个变量上,同时还要维护数据的一致性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: