第15章 原子变量与非阻塞同步机制
近年来,在并发算法领域的大多数研究都侧重于非阻塞算法,这种算法用底层的原子机器指令(例如比较并交换指令)代替锁老确保数据在并发访问中的一致性。
15.1 锁的劣势
这个不多说了,详细见p262
15.2 硬件对并发的支持
独占锁是一项悲观的技术,它假设最坏的情况。对于细粒度的操作,还有一种乐观的方法,通过这种方法可以在不发生干扰的情况下完成更新操作。这种方法需要借助冲突检查机制来判断在更新过程中是否存在来自其他线程的干扰,如果存在,这个操作将失败,并且可以重试(听起来很像数据库中的乐观锁啊)。
在针对多处理器操作而设计的处理器中提供了一些特殊指令,用于管理对共享数据的并发访问。在早期的处理器中支持原子的测试并设置(Test-and-Set),获取并递增(Fetch-and-Increment)以及交换(Swap)等指令,这些指令足以实现各种互斥体,而这些互斥体又可以实现一些更复杂的并发对象。
15.2.1 比较并交换
在大多数处理器架构中采用的方法是实现一个比较并交换(CAS)指令。
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都将失败。然而,失败的线程并不会挂起(这与获取锁的情况不同:当获取锁失败时,线程将被挂起),而是被告知在这次竞争中失败,并可以再次尝试。
15.2.2 非阻塞的计数器
程序15-2中的CaseCounter使用了CAS实现了一个线程安全的计数器。
CaseCounter不会阻塞,但如果其他线程同时更新计数器,那么会多次执行重试操作。
15.2.3 JVM对CAS的支持
在java5.0之前,如果不编写明确的代码,那么就无法执行CAS。在java5.0中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS操作(好像就是那些原子变量),并且JVM把它们编译为底层硬件提供的最有效方法。在支持CAS的平台上,运行时把他们编译为相应的机器指令。在最坏情况下,如果不支持CAS指令,那么JVM将使用自旋锁。
15.3 原子变量类
15.3.1 原子变量是一种“更好的volatile”
从这节的描述来看,原子变量利用的是硬件对并发的支持,即CAS
15.3.2 性能比较:锁与原子变量
构造一个测试基准,其中将比较为随机数字生成器(PRNG)的几种不同的实现。在PRNG中,在生成下一个随机数字时需要用到上一个数字,所以在PRNG中必须记录前一个数值并将其作为状态的一部分。程序15-4和15-5给出了线程安全的PRNG的两种实现,一种使用ReentrantLock,另一种使用AtomicInteger。
15.4 非阻塞算法
如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法就被称为非阻塞算法。如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法也被成为无锁(Lock-Free)算法。如果在算法中仅将CAS用于协调线程之间的操作,并且能正确实现,那么这它既是一种无阻塞算法,又是一种无锁算法。
15.4.1 非阻塞的栈
非阻塞算法通常比基于锁的算法更为复杂。创建非阻塞算法的关键在于,找出如何将原子修改的范围缩小到单个变量上,同时还要维护数据的一致性。程序清单15-6的ConcurrentStack中给出了如何通过原子引用来构建栈的示例。
15.4.2 非阻塞的链表
链接队列需要单独维护头指针和尾指针。有两个指针指向位于尾部的节点:当前最后一个元素的next指针,以及尾节点。当成功插入一个元素时,这两个指针都要采用原子操作来更新。初看起来,这个操作无法通过原子变量来实现。在更新这两个指针时需要不同的CAS操作,并且如果第一个CAS成功,但第二个CAS失败,那么队列将处于不一致的状态。而且即使这两个CAS都成功了,那么在执行这两个CAS之间,仍可能有另一个线程会访问这个队列。
程序15-7的LinkedQueue中给出了Michael-Scott提出的非阻塞链接队列算法中的插入部分,在ConcurrentLinkedQueue中使用的正是该算法。在许多队列算法中,空队列通常都包含一个“哨兵”节点或者“哑”节点,并且头节点和尾节点在初始化都指向该哨兵节点。尾节点通常要么指向哨兵节点(如果队列为空),即队列的最后一个元素,要么(当有操作正在执行更新时)指向倒数第二个元素。
这里的重点是理解那个“中间状态”以及“稳定状态”。(上面的图印刷错了,是队列不是对立)。上面操作中的“中间状态”如图15-4所示:
当第二次更新完成后(更新尾节点指针),队列将再次处于稳定状态,如图15-5所示:
LinkedQueue.put方法在插入新元素之前,将首先检查队列是否处于中间状态(步骤A)。如果是,那么有另一个线程正在插入元素(在步骤C和D)之间。此时当前线程不会等待其他线程执行完成,而是帮助它完成操作,并将尾节点向前推进一个节点(步骤B)。然后,它将重复执行这种检查,以免另一个线程已经开始插入元素,并继续推进尾节点,知道它发现队列处于稳定状态之后,才会开始执行自己的插入操作。
15.4.3 原子域的更新器
原子的域更新器类表示现有volatile域的一种基于反射的“视图”,从而能够在已有的volatile域上使用CAS。在更新器类中没有构造函数,要创建一个更新器对象,可以调用newUpdater工厂方法,并定制类和域的名字。域更新器没有与某个特定的实例关联在一起,因而可以更新目标类的任意实例中的域。它提供的原子保证性比普通的原子类更弱一些。在ConcurrentLinkedQueue,使用nextUpdater的compareAndSet方法来更新Node中的next域,完全是为了提升性能。对于一些频繁分配并且生命周期短暂的对象,如队列中的链接节点,如果能去掉每个Node的AtomicRefrence创建过程,那么将极大地降低插入操作的开销。然而,几乎在所有情况下,普通原子变量的性能都很不错,只有在很少的情况下才会使用原子的域更新器。(如果在执行原子更新的同时还需要维持现有类的串行化形式,那么原子的域更新器将非常有用)
15.4.4 ABA问题
如果在算法中采用自己的方式来管理节点对象的内存,那么可能出现ABA问题。一个简单的解决方案是:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号。即使这个值有A变为B,然后又变为A,版本号也将是不同的。AtomicStampedReference和AtomicMarkableReference支持在两个变量上执行原子的条件更新。
小结:
《java并发编程实战》读书笔记12--原子变量,非阻塞算法,CAS的更多相关文章
-
Java并发编程实战 读书笔记(一)
最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一 线程与进程 进程与线程的解释 个人觉 ...
-
Java并发编程实战 第15章 原子变量和非阻塞同步机制
非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatil ...
-
Java并发编程实战 读书笔记(二)
关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...
-
《java并发编程实战》笔记
<java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为: Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...
-
Java多线程编程实战读书笔记(一)
多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.
-
《Java并发编程实战》笔记-非阻塞算法
如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败和挂起,那么这种算法就被称为非阻塞算法.如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法也被称为无锁(Lock-Free)算法 ...
-
《Java并发编程实战》笔记-锁与原子变量性能比较
如果线程本地的计算量较少,那么在锁和原子变量上的竞争将非常激烈.如果线程本地的计算量较多,那么在锁和原子变量上的竞争会降低,因为在线程中访问锁和原子变量的频率将降低. 在高度竞争的情况下,锁的性能将超 ...
-
《Java并发编程实战》笔记-OneValueCache与原子引用技术
/** * NumberRange * <p/> * Number range class that does not sufficiently protect its invariant ...
-
Java并发编程艺术读书笔记
1.多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存.所以,可合理控制线程数量. 如何控制: (1)使用ps -ef|grep appn ...
-
Java并发编程实践读书笔记(5) 线程池的使用
Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...
随机推荐
-
[NOIP2016]换教室 D1 T3 Floyed+期望DP
[NOIP2016]换教室 D1 T3 Description 对于刚上大学的牛牛来说, 他面临的第一个问题是如何根据实际情况中情合适的课程. 在可以选择的课程中,有2n节课程安排在n个时间段上.在第 ...
-
Unity3D优化总结
1.在使用数组或ArrayList对象时应当注意 length=myArray.Length; for(int i=0;i<length;i++) { } 避免 for(int i=0;i< ...
-
Android RelativeLayout用到的一些重要的属性
转载自 http://mobile.51cto.com/android-265842.htm 第一类:属性值为true或false android:layout_centerHrizontal 水平 ...
-
jquery validate 自定义验证方法
query validate有很多验证规则,但是更多的时候,需要根据特定的情况进行自定义验证规则. 这里就来聊一聊jquery validate的自定义验证. jquery validate有一个方法 ...
-
转:python webdriver API 之控制浏览器滚动条
有时候 web 页面上的元素并非直接可见的,就算把浏览器最大化,我们依然需要拖动滚动条才能看到想要操作的元素, 这个时候就要控制页面滚动条的拖动, 但滚动条并非页面上的元素, 可以借助 JavaScr ...
-
UVALive 6187 Never Wait for Weights 带权并查集
题意:每次给出每两个数之间的大小差值.在给出关系的过程中插入询问:数a和数b的差值,若不能确定,输出UNKNOWN 解法:相对大小关系的处理:并查集 1.给出两点的相对大小关系后,找到两个点的根节点, ...
-
Lvs+Keepalived+Bind+web构建高可用负载均衡系统
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://hatech.blog.51cto.com/8360868/1417899 --- ...
-
Linux 出现telnet: 127.0.0.1: Connection refused错误解决办法
Linux 出现telnet: connect to address 127.0.0.1: Connection refused错误解决办法 没有xinetd服务: 1./etc/init.d目录中放 ...
-
C# 全文搜索Lucene
全文出自:https://blog.csdn.net/huangwenhua5000/article/details/9341751 1 lucene简介1.1 什么是luceneLucene是一个全 ...
-
.NET 简易方法拦截器
伟大的无产阶级Willaim曾说过:"无论你觉得自己多么的了不起,也永远有人比你更强".对,我说过!我就是william. 今天想记录一下在项目中遇到的一个比较有意思的东西,异常拦 ...