目标:利用AtomicStampedReference实现栈的压入弹出无锁编程
AtomicStampedReference
意义:对数据进行CAS(compareAndSet)无锁自加或者更换栈的表头之类的问题时会出现ABA问题,AtomicStampedReference通过增加版本号(时间戳)来解决这个问题。
- AtomicStampedReference(V initialRef, int initialStamp):初始化,传入初始数据与初始版本号
- getReference():获取当前引用数据
- getStamp():获取当前版本号
- set(V newReference, int newStamp):设置引用数据与版本号
- attemptStamp(V expectedReference, int newStamp):如果当前引用等于expectedReference,就更新版本号到newStamp。主要用于无障碍编程(ObstructionFree)。
- compareAndSet :比较数据与版本号,符合则更新数据返回true,否则返回false。
(V expectedReference, // 当前引用数据
V newReference, // 修改后的数据
int expectedStamp, // 当前版本号
int newStamp) // 修改后的版本号
代码实现:
package com.miracle.study.queue;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Node;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author Miracle
* @date 2021/4/17 16:53
*/
public class LockFreeStack<T> {
/**
* 头部节点
*/
private AtomicStampedReference<Node<T>> head;
/**
* 初始化头部
*/
public LockFreeStack() {
// 创建空节点
var headNode = new Node<T>(null);
// 将空节点作为头部并传入版本号
head = new AtomicStampedReference<>(headNode, 0);
}
/**
* 节点定义
*
* @param <T>
*/
static class Node<T> {
/**
* 当前节点数据
*/
private T data;
/**
* 指向下一个节点
*/
private Node<T> next;
public Node(T t) {
this.data = t;
}
public T get() {
return data;
}
}
/**
* 采用头加法
* @param t
*/
public void push(T t) {
// 创建新节点
var newNode = new Node<>(t);
while (true) {
// 获取当前头的版本号
var stamp = head.getStamp();
// 获取当前头的节点
var nowHead = head.getReference();
// 将新节点指向头部
newNode.next = nowHead;
// 尝试进行修改头部,版本号+1,成功就返回,失败就重新尝试修改
if (head.compareAndSet(nowHead, newNode, stamp, stamp + 1)) {
return;
}
}
}
/**
* 采用头删法
* @return
*/
public T pop() {
while (true) {
// 获取当前头的版本号
var stamp = head.getStamp();
// 获取当前头的节点
var nowHead = head.getReference();
// 判断当前节点是否为空
if (nowHead == null) {
return null;
}
// 尝试修改当前头节点,修改成功版本号+1,修改失败重新循环尝试
if (head.compareAndSet(nowHead, nowHead.next, stamp, stamp + 1)) {
return nowHead.get();
}
}
}
/**
* 单线程测试
*/
@Test
public void singleTest() {
var stack = new LockFreeStack<Integer>();
for (int i = 0; i < 100; i++) {
stack.push(i);
}
Integer j = null;
while ((j = stack.pop()) != null) {
System.out.println(j);
}
}
/**
* 多线程测试
* @throws InterruptedException
*/
@Test
public void multiTest() throws InterruptedException {
var stack = new LockFreeStack<Integer>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
stack.push(j);
}
}).start();
}
Thread.sleep(5000);
Integer j = 0;
while ((stack.pop()) != null) {
j++;
}
System.out.println(j);
}
}