18.JAVA的CAS操作
程序员文章站
2022-05-04 15:13:46
...
18.CAS
18.1CAS简介
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
1、 循环会耗时(因为底层是自旋锁)
2、一次性只能保证一个共享变量的原子性
3、会存在ABA问题
package com.CAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by yj on 2020/9/5 14:49
*/
public class CASDemo {
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
// 正常在业务操作,这里面比较的都是一个个对象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//期望、更新
//public final boolean compareAndSet(int expect, int update)
//如果期望值达到了,就更新,如果没达到就不更新.CAS是CPU的并发原语
atomicInteger.compareAndSet(2020,2021);//这儿由于上面给这个atomicInteger传入的是2020,所以是期望值,所以更新为2021
atomicInteger.compareAndSet(2020,2021);//这儿上面更新为了2021,此时不为期望值,更新失败
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();//i++
}
}
18.2unsafe类
上面这个其实就是自旋锁。
18.3ABA问题
就是右边线程比较快,达到A后进行cas操作,cas(1,3)和cas(3,1)后A又变回了1,但是左边线程不知道,直接拿到了这个A=1,其实他是经过右边线程进行了两次cas操作后的结果,但是左边线程并不知道,认为A的值就没变过。
这个例子就是描述的前面两步操作将值最后变为原样,最后一步又取这个值,但是它不知道这一步是经过变换的.
package com.CAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by yj on 2020/9/5 14:49
*/
public class CASDemo {
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
// 正常在业务操作,这里面比较的都是一个个对象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//=========这儿两次操作把值又调回了2020===========
System.out.println(atomicInteger.compareAndSet(2020,2021));//这儿由于上面给这个atomicInteger传入的是2020,所以是期望值,所以更新为2021
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021,2020));//这儿上面更新为了2021,此时不为期望值,更新失败
System.out.println(atomicInteger.get());
//==========这儿并不知道拿到的值是经过变换的=========
System.out.println(atomicInteger.compareAndSet(2020,2021));//这儿上面更新为了2021,此时不为期望值,更新失败
System.out.println(atomicInteger.get());
}
}
18.4解决ABA问题(原子引用)
带时间戳的原子引用,为什么它可以解决这个问题,其实就类似于给了一个版本号,每次操作给予时间戳一定的变换.
package com.CAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by yj on 2020/9/5 14:49
*/
public class CASDemo {
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
/**
*Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实
*例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;*/
// 正常在业务操作,这里面比较的都是一个个对象,所以不会出现问题,这儿因为测试使用了包装类,其范围有限定,所以出问题了
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Lock lock = new ReentrantLock(true);
//参数(期望的值,新的值,时间戳,时间戳+1)
atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println("a2=>"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=>"+atomicStampedReference.getStamp());
},"a").start();
// 乐观锁的原理相同!
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6,
stamp, stamp + 1));
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b").start();
}
}
下一篇: eclipse使用maven教程