原子操作 
本章将介绍另一种同步控制方式———原子操作。原子操作也可以保证多线程环境下的共享变量
操作的正确性。
5.1 原子性
如果一个操作是原子的,则表示该操作要么全做,要么全不做。这意味着原子操作将作为一个不
可分割的整体完成,在执行完毕前不会被任何其他任务或事件中断。
在同步控制方式中,原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运
行到结束,中间不会有任何上下文切换。
使用原子操作后,一般不需要对临界区加锁,可以实现多线程程序的同步控制。
在单处理器系统中,能够在单条指令中完成的操作都可以认为具有原子操作的潜质,因为中断只
能发生于指令之间,单条指令的执行不会发生其他指令的介入,例如a=0。然而,需要把单条指令和单
条语句的概念区分开,不是所有的程序设计语言中的一条语句都具有原子操作的潜质,例如,“a++” 
操作是一条语句,它不是一个原子操作,因为该操作可以细分为3个步骤完成: 
(1)取出a的值; 
(2)执行加1操作; 
(3)将结果存回a。
每个步骤对应一个操作,在多线程环境下,在某两个步骤之间可能存在其他操作的介入,所以a++ 
操作不是原子操作。
比较并交换(compareandswap,CAS)操作是基本的原子操作之一,现在几乎所有的CPU 都支持
CAS操作。CAS操作的算法描述如下: 
int compare_and_swap (Mem m, int oldval, int newval) { 
int old_reg_val = m; 
if (old_reg_val == oldval) 
m = newval; 
return old_reg_val; 
}
其中,Mem 是一种抽象表示,可以代表内存位置,也可以代表一种引用类型,oldval为初始值, 
newval为更新后的值。在方法compare_and_swap()的执行过程中,首先取出内存单元或引用变量的
值m,然后和oldval值进行比较,如果相同,则将内存位置设置为新值newval,否则仍然为oldval。

第5章 原子操作103 
java.util.concurrent.atomic 包中提供了AtomicBoolean、AtomicInteger、AtomicLong、
AtomicIntegerArray和AtomicReference等原子类。这些原子类提供了一种无锁的、线程安全的访问
方式,每个类都提供了对于相应类型变量进行原子更新的方法。
5.2 基本类型的原子操作
基本类型的原子操作类包括AtomicInteger、AtomicBoolean、AtomicLong,并不是每种基本类型都
提供了基本类型原子操作。
下面以原子整型类AtomicInteger为例说明基本类型原子类的方法和使用。类AtomicInteger的
定义形式如下: 
public class AtomicInteger extends Number implements Serializable 
从上面的定义可以看出,AtomicInteger从类Number继承,并实现了Serializable接口。当程序中
需要以原子方式增加或减少整数值时,通常需要用到此类。
该类有两个构造方法: 
//初始化一个AtomicInteger 类对象,初始值为0 
. public AtomicInteger() 
//用给定的初始值initialValue 初始化AtomicInteger 类对象
. public AtomicInteger(int initialValue) 
该类的常用方法如表5-1所示,其中,addAndGet()、getAndAdd()和getAndSet()都是比较常用的
方法,方法lazySet()中的lazy表示“慵懒”的意思,该方法就像一个懒汉一样,并不是马上去做set操
作,而是以慵懒的方式稍后再执行set操作。
表5-1 类AtomicInteger的常用方法
方 法含 义
intaddAndGet(intdelta) 在原值的基础上增加delta 
booleancompareAndSet(intexpect,intupdate) 如果当前值等于expect的值,则采用原子方式更新为update的值
intdecrementAndGet() 采用原子方式在原值的基础上减1 
doubledoubleValue() 将AtomicInteger类对象的值转换为double类型
floatfloatValue() 将AtomicInteger类对象的值转换为float类型
intget() 得到当前值
intgetAndAdd(intdelta) 采用原子方式在当前值的基础上增加指定的值
intgetAndDecrement() 采用原子方式在原值的基础上减1 
intgetAndIncrement() 采用原子方式在原值的基础上增1 
intgetAndSet(intnewValue) 采用原子方式设定为给定的新值,并返回原来的值
intincrementAndGet() 采用原子方式在原值的基础上增1

续表
104 并行编程
方 法含 义
intintValue() 以int类型返回值
voidlazySet(intnewValue) 最后设置为给定的值newValue 
longlongValue() 将AtomicInteger类对象的值转换为long类型
voidset(intnewValue) 设置为给定的值 
下面通过例子演示类AtomicInteger的用法。
【例5-1】 使用两个线程对同一个整型变量进行原子更新,更新时,在每个线程中分别对整型变量
执行100次增1操作。
【解题分析】
如果两个线程对同一个整型变量进行操作时不施加任何控制,则会出错,读者可以自行尝试。对
整型变量进行原子更新可以使用类AtomicInteger,增1操作可以使用方法getAndIncrement()实现。
【程序代码】 
//Counter.java 
package book.ch5.IntegerAtomic; 
import java.util.concurrent.atomic.AtomicInteger; 
public class Counter { 
//定义原子整型变量ia 
private AtomicInteger ia = new AtomicInteger(); 
//调用方法getAndIncrement()进行原子更新 
public void increase(){ 
ia.getAndIncrement(); 
} 
//读取数据时要使用原子操作方法get() 
public int get(){ 
return ia.get(); 
} 
} //Worker.java 
package book.ch5.IntegerAtomic; 
public class Worker extends Thread { 
Counter counter; 
Worker(Counter counter) { 
this.counter = counter; 
} 
public void run() { 
for(int i=0; i<100; i++){ 
counter.increase(); 
} 
}

第5章 原子操作105 
} //Index.java 
package book.ch5.IntegerAtomic; 
public class Index { 
public static void main(String[] args) { 
//定义类对象counter,是两个线程同时操作的对象 
Counter counter = new Counter(); 
//定义两个线程,将同一个counter 对象作为参数传入线程 
Thread t1 = new Worker(counter); 
Thread t2 = new Worker(counter); 
//启动两个线程 
t1.start(); 
t2.start(); 
//通过join()方法让主线程等待线程t1 和t2 的完成 
try { 
t1.join(); 
t2.join(); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
System.out.println("counter.get() = " + counter.get()); 
} 
}
【程序分析】
当使用两个线程同时操作同一个对象时,要将同一个对象传入线程,本例将类Counter对象实例
分别传入了线程t1和t2。
【运行结果】
程序运行结果如图5-1所示。
图5-1 运行结果
【相关讨论】
从运行结果可以看出,两个线程各执行了100次增1操作,结果为200。虽然两个线程同时对同一
个变量进行了操作,但变量可以原子性的更新,以保证增加后的值为200。

106 并行编程
5.3 引用类型的原子操作
引用类型的原子操作使用类AtomicReference创建,定义的形式如下: 
public class AtomicReference<V> extends Object implements Serializable 
其中,V 用来指明对象引用的类型,可以根据自定义的类型指定。
类AtomicReference有两个构造方法,如下所示: 
//用初始值null 创建类AtomicReference 的实例
. public AtomicReference() 
//用初始值initialValue 创建类AtomicReference 的实例,其中,类型由参数V 指定
. public AtomicReference(V initialValue) 
第二个构造方法在生成对象实例时,会把一个V 类型的对象实例initialValue放到原子引用对象
中,这不会让对象本身是线程安全的,而是对该对象的获取和设置操作是线程安全的。
类AtomicReference的常用方法如表5-2所示。
表5-2 类AtomicReference的常用方法
方 法含 义
booleancompareAndSet(Vexpect,Vupdate) 如果当前值等于预期值expect,则以原子方式将该值设置
为给定的更新值update 
Vget() 获取当前值
VgetAndSet(VnewValue) 以原子方式设置为给定的新值newValue,并返回旧值
voidlazySet(VnewValue) 采用慵懒的方式设置为给定值newValue 
voidset(VnewValue) 设置为给定值newValue 
VgetAndUpdate(UnaryOperator<V> updateFunction) 以原子方式使用updateFunction的结果更新当前值,返回
更新前的值
VupdateAndGet(UnaryOperator<V> updateFunction) 以原子方式使用updateFunction的结果更新当前值,返回
更新后的值
VgetAndAccumulate(Vx,BinaryOperator<V> 
accumulatorFunction) 
以原子方式使用accumulatorFunction的结果更新当前值, 
返回更新前的值
VaccumulateAndGet(Vx,BinaryOperator<V> 
accumulatorFunction) 
以原子方式使用accumulatorFunction的结果更新当前值, 
返回更新后的值 
在后面四个方法的参数中,UnaryOperator 和BinaryOperator 都是功能接口,其中, 
UnaryOperator用于处理单个操作数,BinaryOperator可以处理两个操作数,它们都会返回与操作数类
型相同的结果,可以通过Lambda表达式的形式作为参数传递。
【例5-2】 有5个选手参加一个抢答节目,谁先抢到,答题权就归谁。
【解题分析】
5个选手可以生成5个线程,对引用变量进行原子更新,谁能更新成功则说明谁抢到了答题权。

第5章 原子操作107 
【程序代码】 
//Resource.java 
package book.ch5.ar; 
public class Resource { 
public Resource(){ 
} 
public Resource(String name){ 
} 
} //Player.java 
package book.ch5.ar; 
import java.util.concurrent.atomic.AtomicReference; 
public class Player extends Thread{ 
int id; 
Resource resource; 
AtomicReference<Resource> ar; 
public Player(int id, Resource r, AtomicReference<Resource> ar){ 
this.id = id; 
resource = r; 
this.ar = ar; 
} 
public void run(){ 
if(ar.compareAndSet(resource, new Resource(this.getName()))) 
System.out.println(this.id + "号选手抢到了答题权"); 
else 
System.out.println(this.id + "号选手没抢到"); 
} 
} //Index.java 
package book.ch5.ar; 
import java.util.concurrent.atomic.AtomicReference; 
public class Index { 
public static void main(String[] args){ 
Resource resource = new Resource(); 
AtomicReference<Resource> ar = new AtomicReference<Resource>(resource); 
for(int i=1; i<=5; i++){ 
new Player(i, resource, ar).start(); 
} 
} 
}
【运行结果】
程序运行结果如图5-2所示。

108 
并行编程


图5-
2 
运行结果

【相关讨论】

从运行结果可以看出,在3号选手抢到答题权后,由于已经对ar进行了更新,故其他线程无法再次
执行更新操作。

4 
ABA 
问题

5.
原子操作对于无阻塞的操作有一定的优势,但它是不是“完美”的? 显然不是的,本节将介绍使用
原子操作时可能出现的ABA 问题。

为了帮助读者理解ABA 问题,首先通过一个生活中的例子进行说明。例如,你准备去打篮球,在
去打篮球之前,你在宿舍晾了一杯水,准备打球回来后喝掉,然后你出去了,碰巧你的舍友在你不在的
这段时间回到宿舍,他也刚刚打完球,口渴得厉害,直接拿起你晾好的水喝了半杯,喝完后,舍友又帮你
把水倒满了,你回来后看到晾着的水以为还是你之前晾的那杯,想也没想就直接喝光。虽然水杯仍然
是满的,但是水已经不是你出去之前晾的那一杯了。可以将原来满杯的水叫作A,舍友喝完又满杯的
水叫作B,你回来后看到的水叫作A,这就是ABA 问题。

CAS 操作一般比较某一个对象引用的当前值和期望值,如果当前值和期望值相等,则将其替换为
新值。CAS 操作的核心是看某一个值是否已经改变,也就是说,要保证在对某一变量进行操作时该变
量没有被其他线程改变过,只要没有改变,就可以更新。如果期间某一线程对该值进行了改变,然后又
恢复了原值,则这种情况就是ABA 问题。

例如,有多个线程可能同时对某一个变量x进行更新,x的初始值是A,线程1现要将x的值替换
为D,它执行了CAS 操作CAS(A,D)。但在程序并行执行的过程中,可能会发生这样一种情况:线程
2将x的值由A改写为B,然后又改写为A。等到线程1执行CAS 操作时,发现当前值A与期望值A 
相同,则更新为D,看似是正确的,但实际上更新后的A值与之前的A值的程序执行环境已经改变,如
果继续执行,则可能对程序造成影响。

下面通过一个具体应用的例子进行说明,以栈结构为例,如图5-3(a)所示,堆栈中有两个元素x和
z,其中x为栈顶元素,线程2想在栈里只有两个元素的情况下将栈顶元素x通过CAS 操作更新为t,正
确的结果应该如图5-3(b)所示。如果线程1和2同时进行操作,在线程2即将对该堆栈进行操作之
前,线程1获得了执行的机会,将栈顶元素x出栈,然后将y和x重新推入栈内,如图5-3(c)所示,当线
程2执行时,发现栈顶元素仍然为x,则更新为t,如图5-3(d)所示。

ABA 问题的出现在于CAS 操作只是简单地比较了当前值和期望值,没有考虑到有可能出现的中


第5章 原子操作109 
图5-3 ABA 问题演示
间变化。在Java语言中,ABA 问题可以通过使用类AtomicStampedReference解决,这个类维护了一
个类似于版本号的整数引用,每次更新后都会更新stamp,可以避免ABA 问题的出现。
【例5-3】 通过原子引用操作模拟ABA 问题。
【解题分析】
为了模拟ABA问题,令一个线程a将一个整型数的值由0更改为1,然后马上将其值由1更改为0, 
另一个线程b与线程a同时启动,观察ABA 问题。
【程序代码】 
//ABAThread.java 
package book.ch5.aba; 
import java.util.concurrent.atomic.AtomicReference; 
//通过扩展类Thread 创建类ABAThread 
public class ABAThread extends Thread { 
//原子引用类型对象ar 
AtomicReference<Integer> ar; 
public ABAThread(AtomicReference<Integer> ar){ 
this.ar = ar; 
} 
@Override 
public void run(){ 
//将ar 的值使用原子操作由0 更新为1,此后再将其值由1 更新为0 
ar.compareAndSet(0, 1); 
System.out.println("已经将值由0 改为1"); 
ar.compareAndSet(1, 0); 
System.out.println("已经将值由1 改为0"); 
} 
} //NormalThread.java 
package book.ch5.aba; 
import java.util.concurrent.atomic.AtomicReference; 
//定义类NormalThread 
public class NormalThread extends Thread{ 
//原子引用类型对象ar 
AtomicReference<Integer> ar; 
//在构造方法中,对ar 进行赋值

110 并行编程 
public NormalThread(AtomicReference<Integer> ar){ 
this.ar = ar; 
} 
@Override 
public void run(){ 
//线程运行前先休眠1ms,给ABAThread 对象切换0 和1 的值留出时间 
try { 
sleep(1); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
//验证是否更新成功 
if(ar.compareAndSet(0, 1)) 
System.out.println("更新成功"); 
else 
System.out.println("更新失败"); 
} 
} //Index.java 
package book.ch5.aba; 
import java.util.concurrent.atomic.AtomicReference; 
public class Index { 
public static void main(String[] args){ 
//原子引用类型ar 定义及初始化 
AtomicReference<Integer> ar = new AtomicReference<Integer>(0); 
//abaThread 线程对象定义,将原子引用ar 作为参数传递给相应的对象 
ABAThread abaThread = new ABAThread(ar); 
//normalThread 线程对象定义 
NormalThread normalThread = new NormalThread(ar); 
//启动两个线程 
abaThread.start(); 
normalThread.start(); 
} 
}
【程序分析】
该程序同时启动了两个线程abaThread和normalThread,但是normalThread线程由于启动后休
息了0.001s,让abaThread线程对象有机会将ar的值由0变为1,然后由1变为0,虽然ar的值依然为
0,但是已经经历了一次状态的改变。当normalThread线程再次执行时,会检测到0,然后更新为1,并
显示更新成功。
【运行结果】
程序运行结果如图5-4所示。
【相关讨论】
从运行结果可以看出,当线程ABAThread执行了ABA 操作后,normalThread仍然能够成功更
新,并不知道之前在ar值上发生的变化。

第5章 原子操作111 
图5-4 运行结果
5.5 扩展的原子引用类型
本节将分别对扩展的原子引用类型进行介绍,扩展的原子引用类型在引用类型的基础上加上了一
些额外的标记。
5.5.1 类AtomicMarkableReference 
类AtomicMarkableReference是一个线程安全的类,该类封装了一个对象的引用reference和一个
布尔型值mark,可以原子性地对这两个值同时进行更新。该类的定义形式如下: 
public class AtomicMarkableReference<V> extends Object 
其中,V 为泛型,通常为需要标记的原子操作的类型。
类AtomicMarkableReference的构造方法如下: 
AtomicMarkableReference(V initialRef, boolean initialMark) 
该构造方法使用给定的初始值initialRef对引用reference进行赋值,使用initialMark 对标记
mark进行赋值。
例如,定义一个初始值为null,未标记的AtomicMarkableReference对象为amr。 
AtomicMarkableReference<Node> amr= 
new AtomicMarkableReference<Node>(null, false); 
其中,Node为用户自定义类。
类AtomicMarkableReference的常用方法如表5-3所示。
表5-3 类AtomicMarkableReference的常用方法
方 法说 明
booleanattemptMark(VexpectedReference,boolean 
newMark) 
如果当前引用与expectedReference的值相同,则原子性地
设定标记的值为newMark值
booleancompareAndSet(VexpectedReference,V 
newReference,booleanexpectedMark,booleannewMark) 
如果当前引用与expectedReference相同,并且当前标记值
和expectedMark值相同,则原子性地更新引用和标记为
新值newReference和newMark