AtomicBoolean ab = new AtomicBoolean(true);
ab.compareAndSet(true,false); //比较然后赋值,如果旧值为 true,就设置为 false,设置成功才返回 true,否则返回 false,单个 CAS 就结束
ab.weakCompareAndSet(true,false); //目前和 compareAndSet 方法一模一样
ab.get(); //获取最新的值(因为有 volatile 修饰)
ab.getAndSet(true); //循环 CAS 获取最新的值,并赋为 true
ab.lazySet(false); //内部调用unsafe.putOrderedInt() 方法,这个是普通的设置int,值未必立刻回写到主存
ab.set(true); //立即set,因为内部的 value 属性被 volatile 修饰
AtomicInteger ai = new AtomicInteger(0);
ai.getAndIncrement(); //获取旧值,并加1,内部 CAS 循环加1,直到成功
ai.incrementAndGet(); //加1,并获取新值,内部 CAS 循环加1,直到成功
ai.getAndDecrement(); //获取旧值,并减1,内部 CAS 循环减1,直到成功
ai.decrementAndGet(); //减1,并获取新值,内部 CAS 循环减1,直到成功
ai.getAndAdd(2); //获取旧值,并加2,内部 CAS 循环加2,直到成功
ai.addAndGet(2); //加2,并获取新值,内部 CAS 循环加2,直到成功
ai.getAndAccumulate(6,Math::max); //循环 CAS 设置 二元运算后的值。二值运算的第一位是获取到的最新值,第二位是设置的值(6)
ai.accumulateAndGet(4,(x,y)->{ //和上面的区别只是,最后返回的值是 二元运算后的值
return x+y;
});
ai.getAndUpdate(Math::abs); //循环 CAS 设置 一元运算后的值,获取的是旧值
ai.updateAndGet((x)->{ //循环 CAS 设置 一元运算后的值,获取的是新值
return x*x;
});
ai.get(); //获取最新的值(因为有 volatile 修饰)
ai.getAndSet(2); //循环 CAS 获取最新的值,并设置属性为2
ai.set(3); //设置属性为2
ai.compareAndSet(1,2); //如果属性为1,则设置为2。成功返回 true。否则返回 false
ai.lazySet(3); //ab.lazySet(false)
ai.weakCompareAndSet(3,3); //目前和 compareAndSet() 方法一样
实现数组中元素的原子性更新操作
AtomicArray 类内部维护一个 final 修饰的 array 数组
其余的方法和 Atomic 类是一样的,只是所有方法都多了一个 index 属性,表明操作数组的哪一位
AtomicReferenceArray:原子更新的是引用类型的引用(即只能引用重新指向),而不是引用类型内部的值
实现目标类中的字段的原子性更新操作
AtomicFieldUpdater 类内部维护一个 final 修饰的 offset 变量(通过反射得到类中 filed 属性对应的偏移量)
通过偏移量,使用 CAS 来实现原子操作,操作和 Atomic 类一样
目标类中的对应字段必须被 volatile 修饰,且不能被 private 修饰
//这个 Target 是 age 字段必须被 volatile int 修饰,且不能被 private 修饰
AtomicIntegerFieldUpdater AIFU = AtomicIntegerFieldUpdater.newUpdater(Target.class,"age");
Target target = new Target("wk",24);
aifu.accumulateAndGet(target,56,(x,y)->{
return x+y;
});
System.out.println(aiuf.getAge());
//这个 Target 是 time 字段必须被 volatile long 修饰,且不能被 private 修饰
AtomicLongFieldUpdater ALFU = AtomicLongFieldUpdater.newUpdater(Target.class,"time");
//这个 Target 是 man 的字段必须是 man.class 类,并且 Target 中的该字段必须被 volatile 修饰,并且不能被 private 修饰
AtomicReferenceFieldUpdater ARFU = AtomicReferenceFieldUpdater.newUpdater(Target.class,man.class,"man");
AtomicReference ar = new AtomicReference();
System.out.println(ar.get());//null
ar.compareAndSet(null,"string");
System.out.println(ar.get());//string,从 null 变成了 string
//aiuf 是静态内部类,里面有几个基本属性
AtomicFieldUpdater.aiuf a = new AtomicFieldUpdater.aiuf("wk",22);
AtomicFieldUpdater.aiuf b = new AtomicFieldUpdater.aiuf("wkk",222);
AtomicReference ar1 = new AtomicReference(a);
System.out.println(ar1.get());//aiuf{name='wk', age=22, man=null}
ar1.compareAndSet(a,b);
//注意:ar1.get() 变掉了
System.out.println(ar1.get());//aiuf{name='wkk', age=222, man=null}
AtomicFieldUpdater.aiuf a = new AtomicFieldUpdater.aiuf("wk",22);
AtomicFieldUpdater.aiuf b = new AtomicFieldUpdater.aiuf("wkk",222);
AtomicStampedReference asr = new AtomicStampedReference(a,0);
boolean flag = asr.attemptStamp(a,1); //如果引用是 a,且stamp没变,将 stamp 设置为1
System.out.println(flag);
asr.compareAndSet(a,b,1,2);//单个 CAS,如果是 a,1,那么设置为 b,2
int[] stampHolder = new int[1];//只是用来存储 stamp;
AtomicFieldUpdater.aiuf aa = (AtomicFieldUpdater.aiuf)asr.get(stampHolder);//这个 stampHolder 是get() 用来回调的。这样就相当于返回了 两个参数,引用和 stamp
System.out.println(aa);
asr.getReference();//获取引用,因为Pair类使用 volatile 修饰,且使用的是引用的赋值,所以 Pair 中的属性也是可见的
asr.getStamp();//同理
asr.set(a,2); //pair 是可见的,且是引用赋值,所以 引用的赋值时原子的
asr.weakCompareAndSet(a,b,2,3);//内部调用compareAndSet
DoubleAccumulator da = new DoubleAccumulator((x,y)->{
return x*y;
},1.2);
//通过设定的值 1.2 和 3 做 二元操作符运算,得到新的设定值
da.accumulate(3);
//返回当前值。返回的值不是原子快照;在没有并发更新的情况下调用会返回准确的结果,但在计算值时发生的并发更新可能不会合并!-----------返回结果不准确!---------
da.get();
//重置 da 至原始状态,相对于重新 new 一个 da,但是只有没有并发更新的时候才有效,不然不准确,,,,,
da.reset();
//顾名思义
da.getThenReset();
AtomicInteger ai = new AtomicInteger(0);
ai.getAndIncrement();//获取并是 ai 值加 1
public final int getAndIncrement() {
//this 的作用是传入 ai 对象的地址、valueOffset 的作用是传入 ai 对象中 value 变量的地址偏移量、1 为要增加的值
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
//这里使用了 循环 CAS 的方式,一直到 CAS 成功才返回!
do {
//通过 ai 对象的地址和地址偏移量(value 变量相对于对象地址的偏移量),得到当前 value 变量的实际地址的值
var5 = this.getIntVolatile(var1, var2);
//比较 (var1,var2)、var5 -- 如果 var1 地址对应的 var2 偏移量地址的值 和 var5 相同,就将 value 变量所在地址的值替换为 var4+ var5,即原始的值+1
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
如果要用 循环 CAS 来代替加锁操作,可能要循环很久才会成功
只能对一个变量进行操作,如果要对多个变量操作,必须要加锁
ABA 问题
- 有一个变量 p = 4
- 线程1 通过步骤1读取了 p 的预期值 4
- 线程2 通过步骤1读取了 p 的预期值4,并通过步骤2成功将其设置成9
- 线程3 通过步骤1读取了 p 的预期值9,并通过步骤2成功将其设置成4
- 线程1 通过步骤2成功将其设置成了21
虽然这里线程1成功 CAS 了,但是我们真实意图其实线程1应该 CAS 失败的
因为步骤2成功的标准是 p 的值没有变,没有变真正含义,应该是没有变动
而 p 的值虽然最终没有变,但它变动了
- 有一个栈,目前的元素为:A->B->C->null, A 为 top,即 top 指针指向 A
- 现成我们通过 CAS 做出栈入栈操作
- 线程1 做 pop 操作,首先取到 top 指向的值 为A,并且取到 A 的下一个节点 B
- 然后 线程1 准备(注意:是准备,但是没有做)做 CAS 操作,即 CAS(&top,A,B):如果top指针指向的仍然是 A 的话,则把 top 指针指向 B
- 这时,线程2 做了一次完整的 pop 操作,此时栈的元素为:B->C->null
- 线程2 又做了一次完整的 pop 操作,此时栈的元素为:C->null
- 接着,线程3 做了一次 完整的 push(A) 操作,此时栈的元素为:A->C->null,现在 top 又指向 A 了
- 这时,线程1 终于正式 CAS(&top,A,B)了,成功执行!!!把 top 执行了 B,这样栈的元素为:B->null
- 问题所在:原本的栈:A->C->null,突然变为 B->null。这时一个很严重的问题
- 最关键的是,线程1 最后的 CAS 操作不应该成功,线程1 应该重新开始 pop,最终的栈也应该是:C->null!!!
public class CasStack {
AtomicReference<Node> top = new AtomicReference<>();// top指针 cas无锁修改
public void push(Node node) {//入栈
Node oldTop;
do {
oldTop = top.get();//获取预期的 top 指针指向的值
node.next = oldTop;//并将新的节点的下一个节点指向 预期值,即头插法
} while (!top.compareAndSet(oldTop, node));// 如果现在 top 指针指向的值还是预期值的话,就将 top 指针指向 新节点 node
}
public Node pop() {// 出栈
Node newTop;
Node oldTop;
do {
oldTop = top.get();//获取预期值
if (oldTop == null) {//如果没有值,就返回null
return null;
}
newTop = oldTop.next;
LockSupport.parkNanos(1000 * 1000 * 5); // 休眠指定的时间
} while (!top.compareAndSet(oldTop, newTop));//如果 top 指向的值还是预期值 oldTop ,则将 top 指向 之前取到的下一个值
oldTop.next = null;//帮助GC
return oldTop;//将旧的Top作为值返回
}
}
class Node {
public String value;
public Node next;
public Node(String value) {
this.value = value;
}
}
public class ConcurrentStack {
AtomicStampedReference<Node> top = new AtomicStampedReference<>(null, 0);//带版本号的 CAS
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)); // 关键:将内部 oldTop,v包装成了一个新对象,最后调用的还是最经典的compareAndSwapXXX() 方法,只是内部的参数是包装后的对象
}
// 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
public Node pop() {
Node newTop;
Node oldTop;
int v;
do {
v = top.getStamp();
oldTop = top.getReference();
if (oldTop == null) { //如果没有值,就返回null
return null;
}
newTop = oldTop.next;
LockSupport.parkNanos(1000 * 1000 * 5); // 休眠指定的时间
}
while (!top.compareAndSet(oldTop, newTop, v, v+1));//同理
oldTop.next = null;
return oldTop;
}
}
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);
}
}
}
JDK 1.8u171