CAS的三个问题
- 循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗
- 针对单个变量的操作,不能用于多个变量来实现原子操作
- ABA问题
ABA问题
由于CAS操作进行比较替换时只比较了旧值是否相等,比较不够充分导致操作依旧是不够安全的
- thread1、thread2同时读取到i=0
- thread1、thread2都要执行CAS(0, 1)操作
- 假设thread2在thread1之后执行,则thread1执行成功,此时预期thread2会失败
- 但是thread1紧接着有执行了CAS(1, 0)操作,将i的值改回了0,由于此时i的值为0,所有thread2的CAS(0, 1)会执行成功,与预期不符,这就是ABA问题
示例:
package com.hzw.subject1.aba;
// 存储在栈里面元素 -- 对象
public class Node {
public final String value;
public Node next;
public Node(String value) {
this.value = value;
}
@Override
public String toString() {
return "value=" + value;
}
}
package com.hzw.subject1.aba;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
// 实现一个 栈(后进先出)
public class Stack {
// top cas无锁修改
AtomicReference<Node> top = new AtomicReference<Node>();
public void push(Node node) { // 入栈
Node oldTop;
do {
oldTop = top.get();
node.next = oldTop;
}
while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
}
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time) {
Node newTop;
Node oldTop;
do {
oldTop = top.get();
if (oldTop == null) { //如果没有值,就返回null
return null;
}
newTop = oldTop.next;
if (time != 0) { //模拟延时
LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
}
}
while (!top.compareAndSet(oldTop, newTop)); //将下一个节点设置为top
return oldTop; //将旧的Top作为值返回
}
}
package com.hzw.subject1.aba;
import java.util.concurrent.locks.LockSupport;
public class Test {
public static void main(String[] args) throws InterruptedException {
Stack stack = new Stack();
// ConcurrentStack stack = new ConcurrentStack();
stack.push(new Node("B")); //B入栈
stack.push(new Node("A")); //A入栈
Thread thread1 = new Thread(() -> {
Node node = stack.pop(800);
System.out.println(Thread.currentThread().getName() +" "+ node.toString());
System.out.println("done...");
});
thread1.start();
Thread thread2 = new Thread(() -> {
LockSupport.parkNanos(1000 * 1000 * 300L);
Node nodeA = stack.pop(0); //取出A
System.out.println(Thread.currentThread().getName() +" "+ nodeA.toString());
Node nodeB = stack.pop(0); //取出B,之后B处于游离状态
System.out.println(Thread.currentThread().getName() +" "+ nodeB.toString());
stack.push(new Node("D")); //D入栈
stack.push(new Node("C")); //C入栈
stack.push(nodeA); //A入栈
System.out.println("done...");
});
thread2.start();
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
System.out.println("开始遍历Stack:");
Node node = null;
while ((node = stack.pop(0))!=null){
System.out.println(node.value);
}
}
}
如何解决ABA问题
在进行CAS旧值比较时进行旧值版本的比较,只有旧值相同且版本一致时才进行CAS操作,否则CAS操作失败
例如使用AtomicStampedReference进行应用类的原子性操作
package com.hzw.subject1.aba;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;
public class ConcurrentStack {
// top cas无锁修改
//AtomicReference<Node> top = new AtomicReference<Node>();
AtomicStampedReference<Node> top = new AtomicStampedReference<>(null, 0);
public void push(Node node) { // 入栈
Node oldTop;
int v;
do {
v = top.getStamp();
oldTop = top.getReference();
node.next = oldTop;
}
while (!top.compareAndSet(oldTop, node, v, v+1)); // CAS 替换栈顶
}
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop(int time) {
Node newTop;
Node oldTop;
int v;
do {
v = top.getStamp();
oldTop = top.getReference();
if (oldTop == null) { //如果没有值,就返回null
return null;
}
newTop = oldTop.next;
if (time != 0) { //模拟延时
LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
}
}
while (!top.compareAndSet(oldTop, newTop, v, v+1)); //将下一个节点设置为top
return oldTop; //将旧的Top作为值返回
}
}