在 Java 并发编程里面,最可爱的就是无锁了,非常巧妙,精彩绝伦 额。O__O "…
那么什么是无锁?
- 顾名思义,在并发情况下采用无锁的方式实现对象操作的原子性,保证数据一致性、安全性、正确性
怎么做?
- 采用 CAS (Compare And Swap)的方式操作
- 核心思想是:比对旧数据,如果没人动过或者旧数据和预想的一样,就执行更新等操作
- 实现的原理是 CAS(V, E, N) ,当且紧当 E = V 时,N 才更新到 V 中,这里的 V 是变量名,E 是期望值,N 是要更新到 V 的值
- java.util.concurrent.atomic 包下有好几种具体的实现,对象内存空间地址以及偏移量计算比较多,CRUD 惯了,读起来可能有点吃力哈~
那么问题来了
- 同样需要比对啊,同样有 取值 — 比较 — 更新 的操作啊,多线程情况下,佢完值后别的线程同样可以更新啊,那怎么办?
不用怕
- 这里的 取值 — 比较 — 更新 的操作是更加底层的系统里面的一条 CPU 指令来完成,主要用到 sun.misc.Unsafe 提供的操作系统硬件级别的原子操作,并不是原来 Java 代码层面上写好几行的操作(好几行代码已经有很多条 CPU 指令了),网络上很多高并发组件都有用到这个
下面是在 AtomicReferenceArray 之上封装了一个简易无锁版本的 Stack
package jvm.concurrent.atomic;
import java.util.concurrent.atomic.AtomicReferenceArray;
public class AtomicStack<E> {
// 用 AtomicReferenceArray 来存放数据
protected AtomicReferenceArray<E> elementData;
// 用于 栈的扩容,相当于临时变量
protected AtomicReferenceArray<E> elementDataCopy;
// 栈中数据数量
protected int elementCount = 0;
// 每次扩容的数量
protected int capacityIncrement = 10;
public AtomicStack() {
elementData = new AtomicReferenceArray<E>(capacityIncrement);
}
public E push(E item) {
// 先检查容量,不够见扩容
ensureCapacity();
elementData.set(elementCount, item);
elementCount++;
return elementData.get(elementCount - 1);
}
public E pop() {
if (elementCount == 0) {
throw new StackOverflowError();
}
E item = elementData.getAndSet(--elementCount, null);
// 检查容量,有多余的就释放掉
ensureCapacity();
return item;
}
public E peek() {
if (elementCount == 0) {
throw new StackOverflowError();
}
return elementData.get(elementCount - 1);
}
public boolean empty() {
return elementCount == 0;
}
public int search(Object o) {
for (int i = 0; i < elementData.length(); i++) {
E item = elementData.get(i);
if (item == o || o.equals(item)) {
return i;
}
}
return -1;
}
/**
* 判断栈空间大小,不够了就扩容,有多余的就缩小
*/
private void ensureCapacity() {
if (elementCount == elementData.length()) {
elementDataCopy = new AtomicReferenceArray<E>(elementCount + capacityIncrement);
for (int i = 0; i < elementCount; i++) {
elementDataCopy.set(i, elementData.get(i));
}
elementData = elementDataCopy;
}else if (elementCount == elementData.length() - capacityIncrement && elementCount > 0) {
elementDataCopy = new AtomicReferenceArray<E>(elementData.length() - capacityIncrement);
for (int i = 0; i < elementCount; i++) {
elementDataCopy.set(i, elementData.get(i));
}
elementData = elementDataCopy;
}
}
}