首页 / 更多教程 / 原子变量与非阻塞同步机制
原子变量与非阻塞同步机制
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了原子变量与非阻塞同步机制,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含6542字,纯文字阅读大概需要10分钟。
内容图文
目录
在java.util.concurrent包的许多类中,如Semaphore和ConcurrentLinkedQueue,都提供了比Synchronized机制更高的性能和可伸缩性。
近几年,在并发算法领域的大多数研究都侧重于非阻塞算法,这种算法用底层的原子机器指令(例如比较交换指令)代替锁来确保数据在并发访问中的一致性。非阻塞算法被广泛地用于操作系统和JVM中实现线程/进程调度机制、垃圾回收机制和锁和其他并发数据结构。
与基于锁的方案相比,非阻塞算法尽管在设计和实现上复杂得多,但它们在可伸缩性和活跃性上却拥有巨大的优势。非阻塞算法可使多个线程在竞争相同数据时不会发生阻塞,因此它能在粒度更细的层次上进行协调,并极大地减少调度开销。而且,在非阻塞算法中不存在死锁和其他活跃性问题。在基于锁的算法中,如果一个线程在休眠或自旋的同时持有一个锁,那么其他线程都无法执行下去,而非阻塞算法不会受到单个线程失败的影响。从Java 5.0开始,可以使用原子变量类(如AtomicInteger和AtomicReference)来构建高效的非阻塞算法。此外,原子变量除了用于非阻塞算法的开发,还可用做一种“更好的volatile类型变量”使用。原子变量除了提供与volatile类型变量相同的内存语义外,还支持原子的更新操作。
锁的劣势
使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有守护变量的锁,都能采用独占方式来访问这些变量,并且对变量的任何修改对随后所获得这个锁的其他线程都是可见的。
如果有多个线程同时请求锁,那么JVM就需要借助操作系统的功能。如果出现这种情况,那么这些线程的挂起和恢复等过程会存在很大的开销,并且存在较长时间的中断。如果在基于锁的类中包含细粒度的操作,那么当锁上存在激烈的竞争时,调度开销与工作开销的比值会非常高。
锁还存在一些其他缺点。当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下被延迟执行,那么所有需要这个锁的情况下被延迟执行。如果被阻塞线程的优先级较高,而持有锁的线程优先级较低,那么这将是一个严重的问题——也被称为优先级反转(Priority Inversion)。如果持有锁的线程被永久地阻塞,那么所有等待这个锁的线程就永远无法执行下去。
除此之外,锁定方式对细粒度的操作来说仍是一种高开销的机制。
硬件对并发的支持
独占锁是一种悲观技术——它假设最坏的情况,并且只有在确保其他线程不会造成干扰的情况下才能执行下去。
对于细粒度的操作,还有一种更高效的方法,也是一种乐观的方法,通过这种方法可以在不发生干扰的情况下完成更新操作。
在针对多处理器操作而设计的处理器中提供了一些特殊指令,用于管理对共享数据的并发访问。现在,几乎所有的现代处理器都包含了某种形式的原子读-改-写指令,如比较并交换(Compare-and-Swap)或者关联加载/条件存储(Load-Linked/Store-Conditional)。操作系统和JVM使用这些指令来实现锁和并发的数据结构,但在Java 5.0之前,在Java类中还不能直接使用这些指令。
比较并交换(Compare-And-Swap,CAS)
在大多数处理器架构中采用的方法是实现一种比较并交换指令。CAS包含3个操作数——需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当V的值等于A时,CAS才会通过原子的方式用新值B来更新V的值,否则不会执行任何操作。无论位置V的值是否等于A,都将返回V原有的值。CAS的含义是:“我认为V的值应该是A,如果是,那么将V的值更新成B,否则不修改并告诉V的值实际是多少”。CAS是一项乐观技术,它希望能成功地执行操作,并且如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都将失败。然而,失败的线程并不会被挂起,而是被告知在这次竞争中失败,并可再次尝试。这种灵活性大大减少了与锁相关的活跃性风险。
CAS的典型使用模式是:首先从V中读取A,并根据A计算新值B,然后再通过CAS以原子方式将V中的值由A变成B(只要在这期间没有任何线程将V的值修改为其他值)。由于CAS能检测到来自其他线程的干扰,因此即使不使用锁也能实现原子的读—改—写操作序列。
CAS的主要缺点是,它将使调用者处理竞争问题(通过重试,回退,放弃),而在锁定中能自动处理竞争问题(线程在获得锁之前将一直阻塞)。(事实上,CAS最大的缺点在于难以围绕着CAS正确地构建外部算法)
CAS的性能要进行实测。
JVM对CAS的支持
在Java 5.0中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS操作,并且JVM把它们编译为底层硬件提供的最有效方法。在支持CAS的平台上,运行时把它们编译为相应的(多条)机器指令。在最坏的情况下,如果不支持CAS指令,那么JVM将使用自旋锁。
在原子变量类(如java.util.conncurrent.atomic中的AtomicXxx)中使用了这些底层的JVM支持为数字类型和引用类型提供一种高效的CAS操作,而且在java.util.concurrent中大多数类在实现时则直接或间接的使用这些原子变量类。
原子变量类
原子变量比锁的粒度更细,量级更轻,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上,从而获得最细的粒度。在使用基于原子变量而非锁的算法中,线程在执行时更不易出现延迟,并且如果遇到竞争,也更容易恢复过来。
原子变量类相当于一个泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。以AtomicInteger为例,该原子类表示一个int类型的值,并提供get和set方法,这些volatile类型的int变量在读取和写入上有着相同的语义。它还提供了一原子的compareAndSet方法,以及原子的添加、递增和递减等方法。AtomicInteger直接利用了硬件对并发的支持,因此在发生竞争的情况下,能提供更高的可伸缩性。
共有12个原子变量类,可分为四组:标量类(Scalar)、更新器类、数组类及复合变量类。所有这些类都支持CAS,此外AtomicInteger和AtomicLong还支持算术运算。
原子的标量类没有扩展一些基本的包装类,这是因为:基本类型的包装类是不可修改的,而原子变量类是可修改的。在原子变量类中同样没有重新定义hashCode或equals方法,每个实例都是不同的。
非阻塞算法
如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法被称为非阻塞算法。如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法被称为无锁(Lock-Free)算法。如果在算法中仅将CAS用于协调线程之间的操作,并且能正确地实现,那么它既是一种无阻塞算法,又是一种无锁算法。
创建非阻塞算法的关键在于,找出如何将原子修改的范围缩小到单位变量上,同时还要维护数据的一致性。
非阻塞的栈
暂无
非阻塞的链表
暂无
原子的域更新器
原子的域更新器表示现有volatile域的一种基于反射的“视图”,从而能够在已有的volatile域上使用CAS。
ConcurrentLinkedQueue
ABA问题
CAS存在ABA问题。在某些算法中,如果V的值首先由A变成B,再由B变成A,那么仍然被认为是发生了变化,并需要重新执行算法中的某些步骤。
如果在算法中采用自己的方式来管理节点对象的内存,那么可能出现ABA问题。一种相对简单的解决方案是:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号。即使这个值由A变为B,然后又变为A,版本号也将是不同的。
AtomicStampedReference将更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题。
总结
非阻塞同步机制比基于锁的阻塞同步机制,尽管设计和实现更复杂,但在可伸缩性和活跃性上却拥有巨大的优势。从Java 5.0开始,可以使用原子变量类来构建高效的非阻塞算法。原子变量除了用于非阻塞算法的开发,还可用做一种“更好的volatile类型变量”使用。原子变量除了提供与volatile类型变量相同的内存语义外,还支持原子的更新操作。
非阻塞算法在设计和实现上非常困难,但通常能提供更高的可伸缩性,并能更好的防止活跃性故障的发生。
内容总结
以上是互联网集市为您收集整理的原子变量与非阻塞同步机制全部内容,希望文章能够帮你解决原子变量与非阻塞同步机制所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。