上一篇学习了AtomicInteger的核心api,本文继续学习原子引用类型。
CAS示例
AtomicLong、AtomicBoolean的核心方法和AtomicInteger的核心api类似,主要是提供了原子增减、比较交换等方法。我们对AtomicInteger、AtomicBoolean的CAS方法进行举例,其他方法不再详解。
(1)原子长整型对象
public class AtomicCas {
//原子长整型对象
private static AtomicLong counter = new AtomicLong(0);
public static void main(String[] args) throws InterruptedException {
System.out.println("before cas result: " + counter.get());
System.out.println("cas result:"+counter.compareAndSet(0, 1));
System.out.println("after cas result: " + counter.get());
}
}
运行结果:
before cas result: 0
cas result:true
after cas result: 1
从运行结果可以看出,上面的代码中,原子长整型对象counter的初始值是0,调用compareAndSet()方法后,值被设置为1。
(2)原子布尔对象
public class AtomicCas {
//原子布尔对象
private static AtomicBoolean ret = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
System.out.println("before cas result: " + ret.get());
System.out.println("cas result:"+ret.compareAndSet(false, true));
System.out.println("after cas result: " + ret.get());
}
}
运行结果:
before cas result: false
cas result:true
after cas result: true
从运行结果可以看出,上面的代码中,原子布尔对象ret的初始值是false,调用compareAndSet()方法后,值被设置为true。
AtomicInteger、AtomicLong、AtomicBoolean均是基本数据类型对应的原子类,如果要原子更新引用类型的变量,则需要使用原子引用类型了。
原子引用类型
AtomicReference是用于实现引用对象原子操作的线程安全工具类。其核心方法可分为基础操作、原子更新和函数式编程增强三类。
基础操作方法
(1)构造方法
AtomicReference():初始化为null引用。
/**
* Creates a new AtomicReference with null initial value.
*/
public AtomicReference() {
}
AtomicReference(V initialValue):初始化指定对象引用。
/**
* Creates a new AtomicReference with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicReference(V initialValue) {
value = initialValue;
}
(2)值存取
get():获取当前引用值。
/**
* Returns the current value,
* with memory effects as specified by {@link VarHandle#getVolatile}.
*
* @return the current value
*/
public final V get() {
return value;
}
set(V newValue):无条件更新引用。
/**
* Sets the value to {@code newValue},
* with memory effects as specified by {@link VarHandle#setVolatile}.
*
* @param newValue the new value
*/
public final void set(V newValue) {
value = newValue;
}
原子更新方法
(1)CAS操作
compareAndSet(V expect, V update):核心方法,当当前值等于expect时原子更新为update,返回是否成功。
/**
* Atomically sets the value to {@code newValue}
* if the current value {@code == expectedValue},
* with memory effects as specified by {@link VarHandle#compareAndSet}.
*
* @param expectedValue the expected value
* @param newValue the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expectedValue, V newValue) {
return VALUE.compareAndSet(this, expectedValue, newValue);
}
(2)复合操作
getAndSet(V newValue):原子获取当前值并更新,返回旧值。
/**
* Atomically sets the value to {@code newValue} and returns the old value,
* with memory effects as specified by {@link VarHandle#getAndSet}.
*
* @param newValue the new value
* @return the previous value
*/
@SuppressWarnings("unchecked")
public final V getAndSet(V newValue) {
return (V)VALUE.getAndSet(this, newValue);
}
compareAndExchange(V expect, V update):JDK9新增,类似CAS但返回原值而非布尔结果。
/**
* Atomically sets the value to {@code newValue} if the current value,
* referred to as the <em>witness value</em>, {@code == expectedValue},
* with memory effects as specified by
* {@link VarHandle#compareAndExchange}.
*
* @param expectedValue the expected value
* @param newValue the new value
* @return the <em>witness value</em>, which will be the same as the
* expected value if successful
* @since 9
*/
public final V compareAndExchange(V expectedValue, V newValue) {
return (V)VALUE.compareAndExchange(this, expectedValue, newValue);
}
witness value即expected value,也就是期望的值。
函数式编程增强(JDK8+)
(1)一元操作
updateAndGet(UnaryOperator updateFunction):以原子方式使用给定函数的运算结果更新当前值,并返回更新后的值。
/**
* Atomically updates (with memory effects as specified by {@link
* VarHandle#compareAndSet}) the current value with the results of
* applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final V updateAndGet(UnaryOperator<V> updateFunction) {
V prev = get(), next = null;
for (boolean haveNext = false;;) {
if (!haveNext)
next = updateFunction.apply(prev);
if (weakCompareAndSetVolatile(prev, next))
return next;
haveNext = (prev == (prev = get()));
}
}
所谓一元操作(Unary Operation)是指仅接受一个操作数的运算或函数。java 中常见的一元操作有:
- Lambda表达式:x -> x * 2,如更新乘以2操作后的值:updateAndGet(x -> x * 2)。
- 方法引用:String::toUpperCase
- UnaryOperator接口:该接口是java.util.function包下的函数式接口,专用于对单个操作数进行运算并返回同类型结果
所以上述方法接收的是一个函数式的参数,并将函数的运算结果作为更新值,并返回更新后的值。该函数应是无副作用的,因为在多线程竞争导致更新尝试失败时,函数可能会被重新执行。
getAndUpdate(UnaryOperator updateFunction):以原子方式使用给定函数的运算结果更新当前值,并返回旧的值。
/**
* Atomically updates (with memory effects as specified by {@link
* VarHandle#compareAndSet}) the current value with the results of
* applying the given function, returning the previous value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the previous value
* @since 1.8
*/
public final V getAndUpdate(UnaryOperator<V> updateFunction) {
V prev = get(), next = null;
for (boolean haveNext = false;;) {
if (!haveNext)
next = updateFunction.apply(prev);
if (weakCompareAndSetVolatile(prev, next))
return prev;
haveNext = (prev == (prev = get()));
}
}
(2)二元操作
accumulateAndGet(V x, BinaryOperator accumulatorFunction):以原子方式更新当前值,通过将当前值与给定值作为参数输入指定函数,用函数返回值更新原值。返回更新后的值。
/**
* Atomically updates (with memory effects as specified by {@link
* VarHandle#compareAndSet}) the current value with the results of
* applying the given function to the current and given values,
* returning the updated value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function is
* applied with the current value as its first argument, and the
* given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
* @since 1.8
*/
public final V accumulateAndGet(V x,BinaryOperator<V> accumulatorFunction) {
V prev = get(), next = null;
for (boolean haveNext = false;;) {
if (!haveNext)
next = accumulatorFunction.apply(prev, x);
if (weakCompareAndSetVolatile(prev, next))
return next;
haveNext = (prev == (prev = get()));
}
}
所谓二元操作(Binary Operation)是指需要两个操作数(输入)才能完成运算的操作。所以该函数接收的是两个参数,第一个参数是要更新的值;第二个参数是BinaryOperator类型函数式参数,BinaryOperator是Java 8引入的函数式接口,位于java.util.function包中,专为处理两个相同类型操作数的运算而设计。该函数应满足无副作用特性,因为在多线程竞争导致更新失败时可能被重复执行。
getAndAccumulate(V x, BinaryOperator accumulatorFunction):以原子方式更新当前值,通过将当前值与给定值作为参数输入指定函数,用函数返回值更新原值。返回旧的值。
/**
* Atomically updates (with memory effects as specified by {@link
* VarHandle#compareAndSet}) the current value with the results of
* applying the given function to the current and given values,
* returning the previous value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function is
* applied with the current value as its first argument, and the
* given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the previous value
* @since 1.8
*/
public final V getAndAccumulate(V x,
BinaryOperator<V> accumulatorFunction) {
V prev = get(), next = null;
for (boolean haveNext = false;;) {
if (!haveNext)
next = accumulatorFunction.apply(prev, x);
if (weakCompareAndSetVolatile(prev, next))
return prev;
haveNext = (prev == (prev = get()));
}
}
AtomicReference 典型例子
原子更新引用对象
原子更新自定义对象引用,确保多线程环境下对象替换的线程安全。
@Getter
@Setter
class OrderInfo {
private String address;
private float fee;
public OrderInfo(String address, float age) {
this.fee = age;
this.address = address;
}
}
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<OrderInfo> atomicReference = new AtomicReference<>();
OrderInfo order = new OrderInfo("北京天安门广场", 100);
atomicReference.set(order);
OrderInfo updateOrder = new OrderInfo("上海虹桥机场", 200);
// 原子替换
atomicReference.compareAndSet(order, updateOrder);
// 输出上海虹桥机场
System.out.println(atomicReference.get().getAddress()+","+atomicReference.get().getFee());
}
}
运行结果:上海虹桥机场,200.0
无锁并发栈
需求:定义一个栈,100个线程并发压栈,每个线程入栈1000个元素,确保结果的正确性。
代码实现:
通过AtomicReference实现线程安全的栈结构,利用CAS操作保证push和pop操作的原子性。栈顶节点通过AtomicReference<Node>维护,compareAndSet确保并发修改时仅有一个线程成功更新栈顶。
public class ConcurrentStack<T> {
//栈元素
private static class Node<T> {
final T item;
Node<T> next;
Node(T item) {
this.item = item;
}
}
//栈顶元素
private final AtomicReference<Node<T>> top = new AtomicReference<>();
//入栈
public void push(T item) {
Node<T> newHead = new Node<>(item);
Node<T> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
//栈顶修改为新的节点
} while (!top.compareAndSet(oldHead, newHead));
}
//出栈
public T pop() {
Node<T> oldHead;
Node<T> newHead;
do {
oldHead = top.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
public static void main(String[] args) throws InterruptedException {
final ConcurrentStack<Integer> stack = new ConcurrentStack<>();
Thread[] threads = new Thread[100];
// 100线程并发压栈,每个线程入栈1000个元素,结果应该是100000
for (int i = 0; i < 100; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
stack.push(threadId * 1000 + j);
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread t : threads) t.join();
// 验证结果
int count = 0;
while (stack.pop() != null) count++;
// 应输出100000
System.out.println("Total elements: " + count);
}
}
运行结果:Total elements: 100000。该实现通过10万次并发操作验证了结果的正确性。
ABA问题与带邮戳的原子引用对象
在原子更新引用对象这个例子中,如果一个线程更新了引用对象后,另一个线程又把引用对象更新回原来的值(也就是ABA问题),代码如下:
@Getter
@Setter
class OrderInfo {
private String address;
private float fee;
public OrderInfo(String address, float age) {
this.fee = age;
this.address = address;
}
}
public class AtomicReferenceTest {
public static void main(String[] args) throws InterruptedException {
AtomicReference<OrderInfo> atomicReference = new AtomicReference<>();
OrderInfo order = new OrderInfo("北京天安门广场", 100);
atomicReference.set(order);
OrderInfo updateOrder = new OrderInfo("上海虹桥机场", 200);
OrderInfo updateOrder2 = new OrderInfo("北京天安门广场", 100);
Thread t1 = new Thread(() -> {
// 原子替换
atomicReference.compareAndSet(order, updateOrder);
// 输出上海虹桥机场
System.out.println("t1:" + atomicReference.get().getAddress() + "," + atomicReference.get().getFee());
});
t1.start();
t1.join();
Thread t2 = new Thread(() -> {
// 原子替换
atomicReference.compareAndSet(updateOrder, updateOrder2);
// 输出北京天安门广场
System.out.println("t2:" + atomicReference.get().getAddress() + "," + atomicReference.get().getFee());
});
t2.start();
t2.join();
}
}
输出结果:
t1:上海虹桥机场,200.0
t2:北京天安门广场,100.0
针对这种情况,怎么知道引用对象有没有被修改过呢?为了解决这个问题,又引入了带邮戳的原子引用类型AtomicStampedReference。
构造函数
/**
* Creates a new {@code AtomicStampedReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialStamp the initial stamp
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
如上所示,AtomicStampedReference只有一个构造函数,创建带邮戳的原子引用类型对象的时候,需要指定对象的初始值和版本号。
获取版本号
通过getStamp()方法获取当前引用对象的版本号。
/**
* Returns the current value of the stamp.
*
* @return the current value of the stamp
*/
public int getStamp() {
return pair.stamp;
}
CAS方法
其compareAndSet()源码如下:
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
该方法原子性地同时设置引用和版本戳的值,仅当当前引用等于预期引用且当前版本戳等于预期版本戳时才会更新。
- @param expectedReference 引用的预期值
- @param newReference 引用的新值
- @param expectedStamp 版本戳的预期值
- @param newStamp 版本戳的新值
- @return {@code true} 表示操作成功
根据版本号获取引用对象
获取引用对象的值的时候则需要传入当前对象持有的版本号。
/**
* Returns the current values of both the reference and the stamp.
* Typical usage is {@code int[1] holder; ref = v.get(holder); }.
*
* @param stampHolder an array of size of at least one. On return,
* {@code stampholder[0]} will hold the value of the stamp.
* @return the current value of the reference
*/
public V get(int[] stampHolder) {
Pair<V> pair = this.pair;
stampHolder[0] = pair.stamp;
return pair.reference;
}
原子更新带版本号的引用对象
对于上面的例子,用AtomicStampedReference来实现。
@Getter
@Setter
class OrderInfo {
private String address;
private float fee;
public OrderInfo(String address, float age) {
this.fee = age;
this.address = address;
}
}
public class AtomicReferenceTest {
public static void main(String[] args) throws InterruptedException {
OrderInfo order = new OrderInfo("北京天安门广场", 100);
AtomicStampedReference<OrderInfo> atomicReference = new AtomicStampedReference<>(order, 1);
OrderInfo updateOrder = new OrderInfo("上海虹桥机场", 200);
OrderInfo updateOrder2 = new OrderInfo("北京天安门广场", 100);
//用于存储方法返回的版本戳值
int[] stampHolder = new int[1];
Thread t1 = new Thread(() -> {
// 原子替换,版本号同步加1
atomicReference.compareAndSet(order, updateOrder, atomicReference.getStamp(), atomicReference.getStamp() + 1);
// 输出上海虹桥机场
System.out.println("t1:" + atomicReference.get(stampHolder).getAddress() + "," + atomicReference.get(stampHolder).getFee() + "," + stampHolder[0]);
});
t1.start();
t1.join();
Thread t2 = new Thread(() -> {
// 原子替换,版本号同步加1
atomicReference.compareAndSet(updateOrder, updateOrder2, atomicReference.getStamp(), atomicReference.getStamp() + 1);
// 输出北京天安门广场
System.out.println("t2:" + atomicReference.get(stampHolder).getAddress() + "," + atomicReference.get(stampHolder).getFee() + "," + stampHolder[0]);
});
t2.start();
t2.join();
}
}
运行结果:
t1:上海虹桥机场,200.0,2
t2:北京天安门广场,100.0,3
从运行结果可以看出,线程t2拿到的引用对象虽然值还是原始的值,但是其版本号已经是3了,说明引用对象已经被修改过然后又修改回原始的值了。
555



被折叠的 条评论
为什么被折叠?



