CAS——基本概念
判断与执行的原子性: 在线程安全问题中,有一类情形就是判断跟业务执行不是原子性的,导致线程切换后其实已经不满足执行条件了,引发了比如临界资源的线程安全问题,像库存超卖就是这种情况
CAS算法的原理:
CAS(Compare-and-Swap),即比较并替换;
CAS(比较并交换)是CPU指令级的操作,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B;
CAS算法的举例:
比如说线程A拿着预期值库存AK时去进行比较和更新,但是此时线程B将库存改为BK,那么线程A在针对某些业务进行更新时,会首先判断原值是否还是预期值AK,显然这个是BK导致更新失败;
CAS——基本操作(UpSafe 工具)
unSafe基本使用
/**
* 通过反射获取unfase实例对象
*/
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe=(Unsafe) theUnsafe.get(null);
/**
* 获取volatie的偏移地址,编译通过对象基址+类的字段偏移地址,找到对象的字段数据
*/
long studentCountAresss =unsafe.objectFieldOffset(Teacher.class.getDeclaredField("studentCount"));
long nameAresss =unsafe.objectFieldOffset(Teacher.class.getDeclaredField("teacherName"));
int expectCount=0;
int toCount=0;
/**
* 比较交换目标对象的目标值
*/
unsafe.compareAndSwapObject(targetObject,idAresss,expectCount,toCount);
unfase实战演练
首先写一个CasUtils工具类
public class CasUtils {
/**
* @利用反射创建unsafe实例
* 由于此类蕴含了大量操作系统底层的方法,为了避免安全问题所以被设计成私有需要通过反射创建
*/
private static Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe=(Unsafe) theUnsafe.get(null);
}catch (Exception e){
e.printStackTrace();
System.out.println("unsafe方法初始化时出现了一个错");
}
}
/**
* @对目标对象进行cas
* 其中要利用unfase方法读取某个需要更改的偏移地址;
* 基址由对象提供,偏移地址是类中数据结构分布的决定,所以从类中取;
* 读取到该字段我们才能让cpu实时的取到该字段的最新值(原值),用于和expectCount比较
*/
public static boolean casStoreCount(Store targetObejct,Integer expectCount,Integer toCount) throws Exception{
long goodsCountAresss =unsafe.objectFieldOffset(Store.class.getDeclaredField("goodsCount"));
return unsafe.compareAndSwapObject(targetObejct, goodsCountAresss, expectCount, toCount);
}
}
建立一个商店类
public class Store {
private String storeName;
private volatile Integer goodsCount=0;
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public Integer getGoodsCount() {
return goodsCount;
}
public void setGoodsCount(Integer goodsCount) {
this.goodsCount = goodsCount;
}
}
然后模拟线程并发
public class Test {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(100);
Store store=new Store();
/**
* 100个线程并发去添加数量
*/
startAdderThread(store,countDownLatch);
/**
* 利用countDownLatch的await阻塞方法,让100线程执行完后统计添加结果
*/
startEndThread(store,countDownLatch);
}
public static void startAdderThread(Store store,CountDownLatch countDownLatch){
for(int i=1;i<=100;i++) {
Thread thread = new Thread() {
public void run() {
addGoodsCount();
}
public void addGoodsCount() {
try {
/**
* 利用循环cas无锁安全点的将值+1,失败则自旋循环执行
* 超过10次则停止自旋直接中断业务,避免浪费cpu
*/
for (int i = 1; i <= 10; i++) {
boolean casStatus = CasUtils.casStoreCount(store, store.getGoodsCount(), store.getGoodsCount() + 1);
if (casStatus) {
countDownLatch.countDown();
System.out.println("成功添加一个");
break;
}
if(i == 10){
countDownLatch.countDown();
System.out.println("有一个线程失败并取消了");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("出现了一个cas错误");
}
}
};
thread.start();
}
}
public static void startEndThread(Store store,CountDownLatch countDownLatch) throws Exception{
countDownLatch.await();
System.out.println("商品库存添加完成:");
System.out.println("数量一个共是:"+store.getGoodsCount());
}
}
unSafe全部api
/**
* 常用三剑客
* 更新对象内的对象引用
* 更新对象内的Long数值
* 更新对象内的int数值
*/
public native boolean compareAndSwapObject(Object obj, long offset,
Object expect, Object update);
public native boolean compareAndSwapLong(Object obj, long offset,
long expect, long update);
public native boolean compareAndSwapInt(Object obj, long offset,
int expect, int update);
/**
* 利用反射读取字段,获取内存分布的偏移量,便于通过对象地址加这个找到字段的存储地址
*/
public native long objectFieldOffset(Field field);
/**
* 对象的数值类型无序操作(不可见修改与获取,性能最高但安全性差)
*/
public native void putLong(Object obj, long offset, long value);
public native long getLong(Object obj, long offset);
/**
* 对象的数值类型有序操作(不可见修改与获取,性能高但安全性差)
*/
public native void putOrderedInt(Object obj, long offset, int value);
public native void putOrderedLong(Object obj, long offset, long value);
/**
* 对象的数值类型操作(可见修改与获取)
*/
public native void putIntVolatile(Object obj, long offset, int value);
public native void putLongVolatile(Object obj, long offset, long value);
public native long getLongVolatile(Object obj, long offset);
public native int getIntVolatile(Object obj, long offset);
/**
* 数组类型操作
*/
public native int arrayBaseOffset(Class arrayClass);//数组第一个元素 的偏移地址
public native int arrayIndexScale(Class arrayClass);//获取用户给定数组寻址的换算因子
/**
* 对象的对象类型操作(可见修改)
*/
public native void putObjectVolatile(Object obj, long offset, Object value);
public native Object getObjectVolatile(Object obj, long offset);
/**
* 对象的对象类型操作(不可见修改)
*/
public native void putObject(Object obj, long offset, Object value);
public native void putOrderedObject(Object obj, long offset, Object value);
/**
* 线程操作
* @param thread
*/
public native void unpark(Thread thread);//唤醒一个线程
CAS——应用工具(Atmoic原子类)
基于unsafe工具,jdk自身封装了很多业务场景的应用类api;这一系列的操作就是原子工具类;
AtomicInteger
对内存操作进行自增时,通常会遇到在对对象基数自增,此时基数已经被另外一个线程改了导致并发安全问题,或者进行判断时候基数变了但已不满足条件也不应该自增,AtomicInteger正是利用了unsafe的cas以及volatie实现了这一系列的安全操作
public class Test01_AtomicInteger {
public static void main(String[] args) {
AtomicInteger ammount=new AtomicInteger(0);
/**
* @单位自增
*/
int i1 = ammount.incrementAndGet();//先自增后获取,原本是5,返回6
int i2 = ammount.getAndIncrement();//先获取后自增;原本是5,返回5
/**
* @指定自增
*/
int i3 =ammount.getAndAdd(5);//先自增后获取
int i4 =ammount.addAndGet(5);//先获取后自增
/**
* @获取当前
*/
int i5 =ammount.get();
/**
* @条件自增
*/
int i6=ammount.updateAndGet(value->value*30);//满足结果才自增然后获取
int i7=ammount.getAndUpdate(value->value*30);//满足结果才获取然后自增
源码:
while (true){
/**
* 自增的前提是避免并发安全,比较更新的时候我们要保证基数是不变的,不然做加法的时候就会出问题
*/
int prev=ammount.get();
int next=prev+1;
if(ammount.compareAndSet(prev,next)){
break;
}
}
}
}
AtomicReference
利用值比较安全的更新对象内部的引用指针,一般是嵌套对象有这种需求
public class Store {
private GoodHourse goodHourse;
/**
* 很显然在对象内部,如果你的判断语句直接引用goodHourse,并产生此时指针已经发生了变化的线程安全问题
* 所以必须安全的更新指针
* @param newStore
*/
public void replaceGoodHource(Store newStore){
AtomicReference<Store> atomicReference=new AtomicReference(goodHourse);
Store oldStore = atomicReference.get();
for(int i=0;i<50;i++){
/**
* oldStore.getGoodCount()>500判断完时候更新引用的时候可能此时引用已经呗另外一个线程换了所以必须&上一个cas
* 这就叫条件与线程安全的组合拳
*/
if(oldStore.getGoodCount()>500&&atomicReference.compareAndSet(oldStore, newStore)){
System.out.println("原子引用更新成功");
break;
}
}
}
private Integer goodCount;
public GoodHourse getGoodHourse() {
return goodHourse;
}
public void setGoodHourse(GoodHourse goodHourse) {
this.goodHourse = goodHourse;
}
public Integer getGoodCount() {
return goodCount;
}
public void setGoodCount(Integer goodCount) {
this.goodCount = goodCount;
}
}
AtomicStampedReference(解决ABA问题)
很显然AtomicReference只能知道值是否相等,如果改2之后又改回1是是否感知的,因此必须在cas的时候带上版本号或者时间戳就知道有没有被操作过,不管新值和旧值是否一样;
public class Store {
private GoodHourse goodHourse;
private AtomicStampedReference<Store> atomicStampedReference=new AtomicStampedReference(goodHourse,0);
public void replaceGoodHource(Store newStore){
Store oldStore = atomicStampedReference.getReference();
/**
* 关键在于利用原子工具类获取当前版本号
* 然后如果更新成功,指定新的版本号是多少
*/
int oldVersion=atomicStampedReference.getStamp();
int newVersion=oldVersion+1;
for(int i=0;i<50;i++){
if(oldStore.getGoodCount()>500&&atomicStampedReference.compareAndSet(oldStore, newStore,oldVersion,newVersion)){
System.out.println("原子引用更新成功");
break;
}
}
}
private Integer goodCount;
public GoodHourse getGoodHourse() {
return goodHourse;
}
public void setGoodHourse(GoodHourse goodHourse) {
this.goodHourse = goodHourse;
}
public Integer getGoodCount() {
return goodCount;
}
public void setGoodCount(Integer goodCount) {
this.goodCount = goodCount;
}
}
AtomicMarkableReference(解决有没修改过的问题)
public class Store {
private GoodHourse goodHourse;
AtomicMarkableReference<Store> atomicMarkableReference=new AtomicMarkableReference(goodHourse,true);
public void replaceGoodHource(Store newStore){
Store oldStore = atomicMarkableReference.getReference();
for(int i=0;i<50;i++){
/**
* 默认是true
* 被改过了就变成false其他线程就一直无法更新成功
* 至于什么时候更新为true手动决定
*/
if(oldStore.getGoodCount()>500&&atomicMarkableReference.compareAndSet(oldStore, newStore,true,false)){
System.out.println("原子引用更新成功");
break;
}
}
}
private Integer goodCount;
public GoodHourse getGoodHourse() {
return goodHourse;
}
public void setGoodHourse(GoodHourse goodHourse) {
this.goodHourse = goodHourse;
}
public Integer getGoodCount() {
return goodCount;
}
public void setGoodCount(Integer goodCount) {
this.goodCount = goodCount;
}
}
AtomicReferenceFieldUpdater更新器
这个适用于在对象外部代码,判断和值对比的组合拳
public class Test01_AtomicRef {
public static void main(String[] args) {
Store store=new Store();
AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Store.class,GoodHourse.class,"goodHourse");
if(store.getGoodCount()>500&& updater.compareAndSet(store, store.getGoodHourse(), new GoodHourse())){
System.out.println("原子业务执行成功");
}
}
}
AtomicArray
很显然数组也是需要原子操作的时候,比如想让某个下标的元素依据之前的值做一个自增,会产生线程安全问题,所以用volatie可以拿到原先的值,但是相加的时候之前已经值被改了的问题无法规避;
public class Test01 {
public static void main(String[] args) {
/**
* 让某个数组下标自增+1
*/
AtomicIntegerArray arr=new AtomicIntegerArray(new int[]{1,2,3,4,5});
arr.getAndIncrement(0);
/**
*让某个数组下标自增+3
*/
arr.getAndAdd(0,3);
}
}
LongAdder累加器
累加经常会遇到线程安全问题,而且高并发情况下,累加的重试概率很高;所以LongAdder将大数字拆分成cell数组进行最后单线程合并,这样的累加的效率就会提升;类似的还有DoubleAdder
public class Store {
LongAdder longAdder=new LongAdder();
private Integer goodCount;
public void addGoodCount(Long goodCount){
longAdder.add(goodCount);
}
}
JVM并发编程专题章节:
多线程安全——cas
多线程协作
多线程管理
多线程框架
多线程测试
GodSchool
致力于简洁的知识工程,输出高质量的知识产出,我们一起努力
博主私人微信:supperlzf