您的位置:首页 > 编程语言 > Java开发

Java并发读书学习笔记(十一)——原子变量与非阻塞同步机制

2018-04-02 23:45 966 查看
与基于锁的方案相比,非阻塞算法在实现上要复杂得多,但它在可伸缩性和活跃性上都有巨大的优势。由于非阻塞算法可以使多个线程在竞争相同的数据时不会发生阻塞,因此它能在粒度更细的层次上进行协调,并极大地减少调度开销。而且,在非阻塞算法中不存在死锁和其他活跃性问题。在基于锁的算法中,如果一个线程在休眠或自旋的同时持有一个锁,那么其他线程都无法执行下去,而非阻塞算法不会受到单个线程失败的影响。可以使用原子变量类来构建高效的非阻塞算法。

11.1 锁的劣势

与锁相比,volatile变量是一种更轻量级的同步机制,因为在使用这些变量时不会发生上下文切换或线程调度等操作。然而,volatile变量同样存在一些局限:虽然他们提供相似的可见性保证,但不能用于构建原子的复合操作。因此,当一个变量依赖于前天的变量时,或者当变量的新值依赖于旧值时,就不能用volatile变量。这些都限制了他的使用,因此它不能用了实现一些常见的工具,例如计数器或互斥体。

11.2 硬件对并发的支持

在针对对处理器操作而设计的处理器中提供了一些特殊指令,用于管理对共享数据的并发访问。在早期的处理器中支持原子的测试并设置,获取并递增以及交换等指令,这些指令足以实现各种互斥体,而这些互斥体又可以实现一些更复杂的并发对象。现在,几乎所有的现代处理器中都包含了某种形式的原子读-改-写指令,例如比较并交换或者关联加载/条件存储。操作系统和JVM使用这些指令来实现锁和并发的数据结构。
11.2.1 比较并交换在大多数处理器架构中采用的方法是实现一个CAS指令。CAS包含了3个操作数——需要读写的内存位置V、进行比较的值A和拟写入的新值B。当且仅当A的值等于V时,CAS才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。无论位置V是否等于A,都将返回V原有的值。CAS是一项乐观的技术,它希望能成功地执行更新操作,并且如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。
11.2.2 非阻塞的计数器基于CAS的计数器似乎比基于锁的计数器在性能上更差一些,因为它需要执行更多的操作和更负责的控制流,并且还依赖看似负责的CAS操作。但实际上,当竞争程度不高时,基于CAS的计数器在性能上远远超过基于锁的计数器,在没有竞争时甚至更高。如果要快速获取无竞争的锁,那么至少需要一次CAS操作再加上与其他锁相关的操作,因此基于锁的计数器即使在最好的情况下也会比基于CAS的计数器在一般情况下执行更多的操作。由于CAS在大多数情况下都能执行成功,因此硬件能够正确地预测while循环中的分支,从而把复杂控制逻辑的开销降至最低。

11.3 原子变量类

原子变量比锁的粒度更细,量级更轻,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上,这是你获得的粒度最细的情况。更新原子变量的快速路径不会比获取锁的快速路径慢,并且通常会更快,而它的慢速路径肯定比锁的慢速路径要快,因为它不需要挂起或重新调度线程。在使用基于原子变量而非锁的算法中,线程在执行时更不易出现延迟,并且如果遇到竞争,也更容易恢复过来。

11.4 非阻塞算法

在基于锁的算法中可能会发生各种活跃性故障。如果有线程在持有锁时由于阻塞I/O,内存页缺失或其他延迟而导致推迟执行,那么很可能所有线程都不能执行下去。如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法被称为非阻塞算法。如果在算法的某个步骤中都能存在某个线程能够执行下去,那么这种算法也被称为无锁算法。如果在算法中仅将CAS用于协调线程之间的操作,并且能正确地实现,那么它既是一种无阻塞算法,又是一种无锁算法。无竞争的CAS通常能够执行成功,如果有多个线程竞争同一个CAS,那么总会有一个线程在竞争中胜出并执行下去。在非阻塞算法中通常不会出现死锁和优先级反转问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息