《java并发编程实战》读书笔记12--原子变量,非阻塞算法,CAS
第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的时候要考虑到 ...
随机推荐
- 洛谷 P3952 时间复杂度 解题报告
P3952 时间复杂度 题目描述 小明正在学习一种新的编程语言A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会 ...
- 洛谷 P3380 bzoj3196 Tyvj1730 【模板】二逼平衡树(树套树)
[模板]二逼平衡树(树套树) 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作: 查询k在区间内的排名 查询区间内排名为k的值 修改某一位值上的数值 查询k在 ...
- UVA.540 Team Queue (队列)
UVA.540 Team Queue (队列) 题意分析 有t个团队正在排队,每次来一个新人的时候,他可以插入到他最后一个队友的身后,如果没有他的队友,那么他只能插入到队伍的最后.题目中包含以下操作: ...
- bzoj2144: 跳跳棋(二分/倍增)
思维好题! 可以发现如果中间的点要跳到两边有两种情况,两边的点要跳到中间最多只有一种情况. 我们用一个节点表示一种状态,那么两边跳到中间的状态就是当前点的父亲,中间的点跳到两边的状态就是这个点的两个儿 ...
- 删边(cip)
删边(cip) 给出一个没有重边和自环的无向图,现在要求删除其中两条边,使得图仍然保持连通. 你的任务是计算有多少组不合法的选边方案.注意方案是无序二元组. Sol 神题,无从下手啊. 考虑点dfs建 ...
- poj3177 BZOJ1718 Redundant Paths
Description: 有F个牧场,1<=F<=5000,现在一个牧群经常需要从一个牧场迁移到另一个牧场.奶牛们已经厌烦老是走同一条路,所以有必要再新修几条路,这样它们从一个牧场迁移到另 ...
- BAT-Java必考面试题集
2018最新<BAT Java必考面试题集> 1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象 ...
- derby数据库windows自带的客户端
本示例演示用windows自带的ij来操作derby数据库,包括建库,建表,插入数据,查询数据 首先要配置环境变量: 其次打开cmd输入如下图所示的命令: java代码如下: package com. ...
- LightOJ 1023 Discovering Permutations 水题
http://www.lightoj.com/volume_showproblem.php?problem=1023 题意:26字母全排列 思路:用next_permutation或者思维想一下都可以 ...
- jenkins修改job默认名字
${GIT_BRANCH,fullName="false"}-${BUILD_NUMBER}-${GIT_REVISION,length=12}-${deploy_rollback ...