CAS和Synchronized原理

目录

CAS是什么?

​编辑

CAS的应用场景

1.实现原子类

2.实现自旋锁 

CAS的典型问题:ABA问题

Synchronized原理

1.锁升级/锁膨胀

2.锁消除

3.锁粗化


 

CAS是什么?

 Compare and swap比较并交换,我们假设内存的原数据v,旧的预期值A,需要修改的新值B。1.比较A与V是否相等(比较)2.如果比较相等,将B写入V(交换)3.返回操作是否相等

CAS过程:AB是CPU寄存器中的数据,V是内存中的数据,如果V和A的值相等,那么就把V和B的值交换,一般交换过程中并不关心B后续的情况,更关心V这个变量的情况,这里说的交换,可以近似理解成“赋值”。如果V和A不同,就无事发生。

CAS这个操作,并非是通过一段代码实现的,而是通过一条CPU指令完成的,那就意味着CAS操作是原子的,就可以在一定程度上回避线程安全问题,因此在解决线程安全问题除了加锁之外,又有了一个新的方向。(CAS可以理解为是CPU提供的一个特殊指令,通过这个指令,就可以一定程度的处理线程安全问题)

CAS的应用场景

1.实现原子类

public class ThreadDemo{
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
        //使用原子类 来解决线程安全问题
        Thread t1 = new Thread(() ->{
           for(int i = 0;i <5000;i++){
               count.getAndIncrement();//count++
               //count.incrementAndGet();//++count
               //count.getAndIncrement();//count--
               //count.decrementAndGet();//--count
           }
        });
        Thread t2 = new Thread(() ->{
           for(int i = 0;i<5000;i++){
               count.getAndIncrement();
           }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

原子类的伪代码

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value; //①
        while ( CAS(value, oldValue, oldValue+1) != true) { //②
            oldValue = value;
       }
        return oldValue;
   }
}

把旧的value用寄存器保存起来,这里的oldvalue可以理解为寄存器中的值 ,相当于是先把内存中的值读到寄存器中去了,这里CAS()正常情况下,oldValue应该和value是一样的,然后这里就会产生CAS,把oldValue+1写到value中,但是也有可能会有执行完读取value到寄存器之后,线程发生切换了,另外一个线程也修改了value的值,此时这个线程回来之后,再进行CAS判定,就认为不相等了,在①和②的代码之间,可能会发生线程调度。原子类的实现,每次修改之前,再确认一下这个值是否符合要求。

2.实现自旋锁 

自旋锁的伪代码

public class SpinLock {
    private Thread owner = null;//需要记录一下当前的锁是谁加的
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

解锁就是把owner置为空,加锁中用CAS()检测当前的owner是否为null,就进行交换,也就是把当前线程的引用赋值给owner,如果赋值成功,此时就循环结束,加锁完成了。如果当前锁,已经被别的线程占用了,CAS就会发现,this.owner不是null,CAS就不会产生赋值,也同时返回false,循环继续执行,并进行下次判定。

CAS的典型问题:ABA问题

CAS在运行中的核心,检查value和oldvalue是否一致,如果一致,就视为value中途没有被修改过,所以进行下一步交换操作是没有问题的,这里的一致,有两种情况,一种是可能没改过,一种是改过,但是还原回来了。

Synchronized原理

两个线程,针对同一个对象加锁,就会产生阻塞等待,synchronized内部其实还有一些优化机制,存在目的的就是为了让这个锁更高效,更好用

1.锁升级/锁膨胀

①无锁->②偏向锁->③轻量级锁->④重量级锁(偏向锁不是真加锁)

当代码执行到这个代码块中之后,加锁过程,就可能会经历这几个阶段,进行加锁的时候,首先会进入到偏向锁状态,偏向锁,并不是真正的加锁,而是占个位置,有需要了再真加锁,没需要就算了。上述过程,就是“偏向锁”这个过程,相当于“懒汉模式”提到的懒加载一样“非必要,不加锁”,synchronized的时候,并不是真的加锁,先偏向锁状态,做个标记(这个过程是非常轻量的),如果整个使用锁的过程中都没有出现锁竞争,在synchronized执行完之后,取消偏向锁即可,但是,如果使用过程中,另一个线程也尝试加锁,在它加锁之前,迅速的把偏向锁升级成真正的加锁状态,另一个线程就只能阻塞等待了,当synchronized发生锁竞争的时候,就会从偏向锁,升级成轻量级锁,此时,synchronized发生锁竞争的时候,就会从偏向锁升级为轻量级锁,此时,synchronized相当于是通过自旋的方式,来进行加锁。如果要是很快别的线程释放了锁,自旋锁是划算的,但是如果迟迟拿不到锁,一直自旋,并不划算,那么自旋到一定程度之后,就会升级为重量级锁(挂起等待锁)。

挂起等待锁是操作系统内核提供的加锁功能,这个锁会影响到线程的调度,此时,如果线程进行了重量级锁的加锁,并且发生锁竞争,此时线程就会被放到阻塞队列里,暂时不参与CPU调度了,然后直到说,锁被释放了,这个线程才有机会被调度到,并且有机会获取到锁。一旦当前线程被切换出CPU,这就是个比较低效的事情了。

锁只能升级,不能降级

2.锁消除

在编译阶段,编译器去智能的判定,看当前的代码是否真的要加锁,如果这个场景不需要加锁,程序员加了,就自动把锁干掉。例如StringBuffer关键方法中带有synchronized,但是如果是单线程中使用StringBuffer,synchronized加了也白加,此时编译器就会直接把这些加锁操作消除了

3.锁粗化

锁的粒度:synchronized包含的代码越多,粒度就越粗,包含代码越少,粒度就越细

通常情况下,认为锁的粒度越细一点,比较好,加锁部分的代码,是不能并发执行的,锁的粒度越细,能并发的代码就越多;反之越少,也有的情况下,锁定粒度粗一些反而更好,两次加锁解锁之间,间隙非常少,此时,直接加一次大锁就更减少开销

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值