(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
【19】CAS基本原理
文章目录
1.什么是原子操作?如何实现原子操作?
(1)Compare And Swap:比较并交换
(2)JDK里面实现了很多原子操作类
(3)原子操作类就是不可再分的类。
(4)原子操作类的意思就是这个操作要么全部完成,要么全部都不完成。
(5)例如synchronized所修饰的方法或代码块,就是一个典型的原子操作,即要么操作完,要么就等着什么也不做,一直等到拿到锁。
(6)用synchronized完全可以实现原子操作。
(7)synchronized是一个很重的原子操作。谁抢到这把锁,哪个线程就进入执行,没有抢到锁的,照样安心等待。假如说操作非常简单,比如说只有一个i++,需要引入一个很重的操作代价就很大。
(8)对于比较简单的操作,是否有更轻量级的实现同步机制,不致于到锁里面去等待呢?
为了解决这个问题,在现在的CPU里面提供了一个比较并且交换的指令,即CAS指令.Compare And Swap.操作要么全部完成,要么全部没有完成。
2.CAS的原理
JDK里面如何利用CAS指令来实现Atomic开头的一些类呢?
(1)利用了现代处理器都支持的CAS的指令。
(2)循环这个指令,直到成功为止。
(3)现代CPU里面提供了一个专门的CAS指令,CAS指令的作用是将某一个变量的值,首先进行一个比较,比较什么?比较是否我所希望的旧值,如果是我所希望的旧值,说明没有其他线程改过,就把这个旧值变成一个新值。如果说这个值不是我所希望的旧值,就再把整个CAS操作再来执行一次。
3.悲观锁与乐观锁
3.1悲观锁
使用synchronized关键字上的锁是悲观锁,为什么是悲观?
(1)它在执行操作的时候,先将锁抢到了,安安心心做操作,做完了,就不需要管其他线程的操作了。
(2)它认为总有人要改东西,还不如先下手为强。
3.2乐观锁
(1)它会觉得进入一个操作的时候,没有人会改东西,不管怎么样,先把值算出来再讲。
(2)虽然它很乐观,不代表它是很笨,万一在计算的过程当中,有人改了值,这个时候就用CAS指令进行比较与交换。没人改,就执行自己的操作。
(3)被人改了,就再来一遍。
悲观锁的思想就是肯定有人会改我的,那我就先下手为强,先锁上。乐观锁的思想就是可能有人要修改,我就通过CAS指令比较与交换,改了就重来计算一遍,完成自己的操作。
3.3悲观锁与乐观锁哪个性能更强呢?
(1)原子变量(如AtomicInteger)要比加锁情况(如ReentrantLock)大部分的情况效率要高出很多。
(2)道理是,多个线程去竞争的时候,任一时刻只能有一个线程能够进入锁的范围之内,其它没有竞争成功的就被阻塞了。一旦被阻塞了,就会发生上下文切换,一次上下文切换所牵涉到的资源是非常多的,一次上下文切换所花费的时间周期大概在3~5ms之间,而CPU执行一个CAS指令的时间大概为0.6ns,所以两相比较差异是巨大的,CPU执行一条CAS指令比一次上下文切换所消耗的时间要少很多,而且上下文的切换是非常的频繁的。
(3)当使用synchronized去拿锁的时候,会发生一次上下文切换,执行完成后,再次拿锁又会发生上下文切换,也就是说去拿锁的过程至少会发生两次上下文切换,即上下文切换的时间3-5ms还需要*2,花的时间就更多了。
(4)而执行CAS指令循环机制中,线程不会主动进入被阻塞状态,它会不断的去重试,所以乐观锁的效率大于悲观锁的效率。
(5)所以JDK并发编程整个发展趋势就是采用CAS机制,向无锁化编程机制迁移。包括synchronized关键字底层实现里面也大量使用类似于自旋相关的概念。
4.CAS的问题
为什么加锁机制还有生存的余地?因为CAS也有其弱点。
4.1ABA问题
(1)CAS操作里面,中间环节修改了某个值后又快速的还原成原来的值。
生活举例:一杯水被人喝过一口之后,再到水龙头上再接点水,让人感觉没被人喝过一样。
(2)解决ABA问题需要添加一个版本戳。
即在杯子上贴个记数器,只要有人拿起杯子,我的记数器就变一次,拿一次变一次,在离开之前,记数器是0,记数器没有变,说明杯子没有人动过,只要记数器被改了,就说明杯子有人动过了。
(3)即要求线程在改值之前,要带上一个版本戳,JDK里面也提供了使用版本戳解决ABA问题的实现。例如AtomicMarkableReference,AtomicStampedReference.
- AtomicMarkableReference只关注CAS操作中有没有线程对变量进行修改。
- AtomicStampedReference不但关注有没有线程对变量进行修改,还关心被动过几次。
4.1.1带版本戳的原子操作
/**
*类说明:演示带版本戳的原子操作类
*/
public class UseAtomicStampedReference {
static AtomicStampedReference<String> asr
= new AtomicStampedReference("mark",0);
public static void main(String[] args) throws InterruptedException {
//拿到当前的版本号(旧)
final int oldStamp = asr.getStamp();
final String oldReference = asr.getReference();
System.out.println(oldReference+"============"+oldStamp);
Thread rightStampThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":当前变量值:"
+oldReference + "-当前版本戳:" + oldStamp + "-"
+ asr.compareAndSet(oldReference,
oldReference + "+Java", oldStamp,
oldStamp + 1));
}
});
Thread errorStampThread = new Thread(new Runnable() {
@Override
public void run() {
String reference = asr.getReference();
System.out.println(Thread.currentThread().getName()
+":当前变量值:"
+reference + "-当前版本戳:" + asr.getStamp() + "-"
+ asr.compareAndSet(reference,
reference + "+C", oldStamp,
oldStamp + 1));
}
});
rightStampThread.start();
rightStampThread.join();
errorStampThread.start();
errorStampThread.join();
System.out.println(asr.getReference()+"============"+asr.getStamp());
}
}
4.2开销问题
(1)在CAS操作里面,线程是不会休息的,如果在多个线程产生激烈竞争的情况下面,当前线程就会不断的循环比较与交换,会造成CPU非常忙.如果线程的自旋或者说循环的次数长期不成功,对CPU的开销是非常大的。
4.3只能保证一个共享变量的原子操作
(1)要比较内存中的某个变量的值。
(2)CPU指令在进行CAS操作的时候,只能针对某个地址上的值进行修改,在计算机里面一个地址只能保存一个变量,因此它只能保证一个共享变量的原子操作。如果代码块里面,假设同时要对多个变量进行修改,比如说同时改了A,又同时改了B,C,我希望对这三个变量的修改是一个原子操作,此时的CAS就不太适合了,反而是加锁的机制更适合,synchronized,由加锁来保证A、B、C变量同时修改,让其成为一个原子操作。
5.JDK中相关原子操作类的使用
5.1基本类型的原子操作类
/**
*类说明:演示基本类型的原子操作类
*/
public class UseAtomicInt {
static AtomicInteger ai = new AtomicInteger(10);
public static void main(String[] args) {
ai.getAndIncrement();
ai.incrementAndGet();
//ai.compareAndSet();
ai.addAndGet(24);
ai.getAndAdd(24);
}
}
5.2引用类型的原子操作类
/**
*类说明:演示引用类型的原子操作类
*/
public class UseAtomicReference {
static AtomicReference<UserInfo> atomicUserRef;
public static void main(String[] args) {
UserInfo user = new UserInfo("Mark", 15);//要修改的实体的实例
atomicUserRef = new AtomicReference(user);
UserInfo updateUser = new UserInfo("Bill",17);
atomicUserRef.compareAndSet(user,updateUser);
System.out.println(atomicUserRef.get());
System.out.println(user);
}
//定义一个实体类
static class UserInfo {
private volatile String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
6.打赏鼓励
感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!