CAS了解一下
Posted tianjx01
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAS了解一下相关的知识,希望对你有一定的参考价值。
今天咱们还是给自个充充电,就不大战秃头老了!等冲个差不多,电死它!
今天我们就来聊一下CAS,这个玩意,太重要了,是并发包(JUC)的基础,没有它可以说是并发包简直就是废废的,说它之前咱们先讨论一下线程不安全的场景,并尝试解决一下!
目的:怎么让一个变量的快速到达200?
/**
* @author: tianjx
* @date: 2022/1/2 16:22
* @description: 线程不安全问题!
*/
public class CASDemo04
// 类的成员变量
static int data = 0;
// main方法内代码
public static void main(String[] args)
IntStream.range(0,5).forEach((i)->
new Thread(()->
IntStream.range(0,40).forEach((j)->
data++;
);
).start();
);
try
TimeUnit.SECONDS.sleep(2);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(data);
哦?发现用多线程,竟然会造成结果不等于200!(假设A,B线程同时拿到data(100),A快一点,A现在工作内存中改成101,然后写到主内存,然后B开始,先在工作内存改成101,然后在写到主内存101,明明加了两次,但是结果确实101!)
啊!这可怎么办?那肯定就是加锁吧!
方法一:那…synchronized
登场吧,我们常用的!
/**
* @author: tianjx
* @date: 2022/1/2 16:39
* @description: synchronized 解决线程不安全
*/
public class CASDemo05
// 类的成员变量
static int data = 0;
// main方法内代码
public static void main(String[] args)
IntStream.range(0,5).forEach((i)->
new Thread(()->
synchronized (CASDemo05.class)
IntStream.range(0,40).forEach((j)->
data++;
);
).start();
);
try
TimeUnit.SECONDS.sleep(2);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(data);
这个确实可以,但是我们都清楚synchronized
是悲观锁,它会造成以下缺陷:
1、如果根据时间片来获取锁,加锁,释放锁,再加锁,在释放锁,那么就会造成频繁的上下文切换,线程一多,反而多线程的性能可能还不如单线程!
2、一个线程持有锁之后,所有的线程都会被阻塞或者挂起!这不是假多线程嘛!
(其实synchronized
也做过很多的优化,刚刚开始可不是重量级锁哦!如果大家也想了解了解,请双击屏幕点个赞,并且关注不迷路哦!跪谢!)
方法二:lock
锁(朋友莫慌,后面会介绍)
public class CASDemo06
// 类的成员变量
static int data = 0;
static Lock lock = new ReentrantLock();
// main方法内代码
public static void main(String[] args)
IntStream.range(0,5).forEach((i)->
new Thread(()->
IntStream.range(0,40).forEach((j)->
lock.lock();
data++;
lock.unlock();
);
).start();
);
try
TimeUnit.SECONDS.sleep(2);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(data);
方法三:AtomicInteger
(朋友莫慌,后面会介绍)
/**
* @author: tianjx
* @date: 2022/1/2 16:54
* @description: Atomic
*/
public class CASDemo07
// 类的成员变量
static AtomicInteger atomicInteger = new AtomicInteger(0);
// main方法内代码
public static void main(String[] args)
IntStream.range(0,5).forEach((i)->
new Thread(()->
IntStream.range(0,40).forEach((j)->
atomicInteger.incrementAndGet();
);
).start();
);
try
TimeUnit.SECONDS.sleep(2);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(atomicInteger.get());
方法四:LongAdder(朋友莫慌,后面会介绍)
/**
* @author: tianjx
* @date: 2022/1/2 16:58
* @description:
*/
public class CASDemo08
// 类的成员变量
static LongAdder longAdder = new LongAdder();
// main方法内代码
public static void main(String[] args)
IntStream.range(0,5).forEach((i)->
new Thread(()->
IntStream.range(0,40).forEach((j)->
longAdder.increment();
);
).start();
);
try
TimeUnit.SECONDS.sleep(2);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(longAdder);
Lock
底层是用AQS+CAS
来实现的,在高并发的场景下,比synchronized
性能高的可不是一点半点AtomicInteger
是JUC
并发包下的,底层实现也是CAS
,相对于lock
不断加锁,释放锁更加优雅!LongAdder
是JDK1.8之后新增的类,也是JUC
并发包下的,不用说实现也是用了CAS
,不过哦,他很有特点,它比上面更适合在高并发场景下,写的次数大于读的次数!
多线程要保证线程安全可是非常重要的,而且上面的方法多次提到了CAS
,我们必须的聊聊了!(可能前奏有点多,但是上面知识可能会在工作中常用,所以就放到前面了!)
先简单介绍下它,CAS
其实就是compare and swap的缩写,比较并交换,他是一种乐观锁,也是一种无锁,认为大概率是不需要加锁的,如果需要加锁就进行比较并交换!
接下来我们说下原理,他又三个参数v
,o
,n
,v
表示内存中实际存放的值,o
表示预期的值,n
表示要修改的值,如果v==o
,那么我们则把值改成n,如果不相等则更改失败,返回o
!(就是这么简单,别惊讶!JUC包下的类,大部分都调用的是unsafe的cas方法,可能参数或多或少,基本上都是这个的变种,八九不离十!)
我们已经了解了它并且还知道了它的原理,但是大家有没有一种疑惑!假如又两个线程AB,A线程发现v==o
,准备更改为n
,但是B线程来了,也发现v==o
准备改成另外一个值,这不并没有实现线程安全嘛!这不吹牛那?
是这样的,自从JNI的出现,我们java也可以越过JVM,调用操作系统原语了,而CAS就是一种系统原语,而系统原语就是属于操作系统原语了,操作系统原语可能是若干条指令,但是一个原语是不能被其他终端打断的,所以并不会出现上面的问题!
最后的最后我们再聊聊CAS
有什么优缺点把!
优点:采用无锁的方式,在性能上可能会有不小的提升
缺点:1、会造成ABA问题,2、只能保证一个共享变量(不过可以进行变量合并成对象来玩)
最后的最后的最后在聊聊ABA问题!
臭名昭著的ABA问题,即第一个线程把A把值改成B,然后在改成A,第二个线程发现没有符合期望值,那执行呗!
其实这不完全算问题,有些工作场景并不关心这个,只要最后的值相同就行!比如买了一张火车票,然后秒退,这时候火车票总数不变,不影响!但是有些场景很注重次数,可能这个就很严重了,假如高考试卷,有个人偷偷打开了保险箱,把试题拍了下来,第二个人来的时候是看不出来的,但是这样就造成了泄题!
这时候我们其实加个版本号就行了,每次操作算一个版本,比较值的同时,在比较版本号就行了,具体实现的类有AtomicStampedReference
等等!
参考博客:一文彻底搞懂CAS实现原理_东升的思考-CSDN博客
参考博客:CAS是什么?彻底搞懂CAS_阿杰-CSDN博客_cas
以上只是我的简单理解,如果不足之处,请大家指出!
以上是关于CAS了解一下的主要内容,如果未能解决你的问题,请参考以下文章