锁策略和 cas 优化过程

目录

锁策略:  (synchronized实现的策略)

         synchronized的升级策略:  无锁 --> 偏向锁 --> 轻量级锁 -->重量级锁 (这是程序在运行时,jvm做出的优化策略)

无锁: 

偏向锁:

轻量级锁:

重量级锁:

锁消除:

锁粗化:  

 cas优化过程:(无锁编程)

cas实现原子类:

cas实现自旋锁:


锁策略:  (synchronized实现的策略)

   synchronized并非只是一个简单的加锁过程,它实现的策略还是比较复杂的。

   synchronized的升级策略:  无锁 --> 偏向锁 --> 轻量级锁 -->重量级锁 (这是程序在运行时,jvm做出的优化策略)

无锁: 

    无锁就是简单的不涉及加锁问题,没有线程安全问题。   

偏向锁:

     偏向锁是产生锁竞争时,不是一下子就立马加上锁了,相当于有一个中间的过程,在代码写完加锁之后,先对锁对象有一个标记,如果再有一个线程来了,和我竞争同一把锁,此时我就会咔嚓加锁,让那个线程进行阻塞等待,但是如果没有人和我竞争同一把锁,我就只是做一个标记,不会对锁对象真正的进行加锁。(加锁也是有成本的,所以非必要就不加锁)

轻量级锁:

     如果此时产生了锁竞争,就进行加锁,加锁之后,就变成了一个轻量级锁(通常时自旋锁),自旋锁很快,此时锁竞争还不激烈(如两个线程竞争同一把锁),另一个进入阻塞等待的线程就会进入自旋状态(是一个while循环,一直判断竞争的锁对象是否被释放,while会循环的很快,自己一直旋转一样)。

    等到锁被释放之后,能在第一时间拿到锁进行加锁。(但是一直while循环,是要耗费CPU的资源的,所以在锁竞争不激烈时是自旋锁状态)。

   重量级锁:

    当锁竞争非常激烈时(比如10个线程竞争同一把锁),此时就会升级成重量级锁,重量级锁,因为自旋锁是非常消耗CPU的资源的,一个线程自旋还好,如果是10个线程恐怕就对CPU不是很友好了,所以会升级成重量级锁,重量级锁是暂时不参与CPU的调度,进入阻塞等待状态,等锁被释放了,再等内核去调度这剩下的9个线程,所以很难第一时间就拿到锁,系统的调度是随机的,所以极端情况下可能需要等很长时间才会拿到锁。

锁消除:

    遵非必要不加锁的策略。(这个优化还是比较保守的)。是编译时期做出的优化手段,检测当前代码是否是多线程执行的,或者判断当前代码是否有必要加锁,如果不必要,但是又把锁给写了,此时在编译的过程中会自动的把锁去掉。

  (注:synchronized不应该被乱用,就像是StringBuffer就把关键的方法都加上了synchronized关键字,那如果是单线程使用StringBuffer,是不会涉及到线程安全问题的,但是也加上了锁,此时就相当于锁没有被真正的编译)

锁粗化:  

    首先介绍锁粒度:synchronized代码块包含的代码的多少,代码越多,锁粒度就越粗,代码越少锁粒度就越细。    一般在写代码的过程中,多数情况下,是希望锁粒度更小的,因为只要加锁,此时就是不能并发执行的,是串行;串行的代码越少,并发执行的代码就越多。

    但是只是一般情况下,如果某个场景,要频繁的加锁 / 解锁,此时编译器就可能把这个加解锁的操作优化成一个粒度更粗的锁。  如下图:     尤其是每一次加锁都是有开销的,而释放锁之后又要重新进行锁竞争,所以很难保证第一时间拿到锁;此时的小路反而可能会更低。  所以会优化成只是加一次锁,最后再释放锁。

 cas优化过程:(无锁编程)

    就是无锁也能解决线程安全的问题。

    cas的意思是:compare and swap(比较并交换),比较的是寄存器A的值和内存中M的值,如果值是相同的,就把寄存器B 的值和M中的值进行交换。(寄存器B的值大概也是来自另一个内存)

    通常情况下,不考虑寄存器中的值是否改变,更关心的是内存的数值(相当于变量的值)(此种情况就是前文说过的多线程不安全问题中的不是原子操作,叫做内存可见性)。如下图:  两个线程对M中的值进行自增操作,

    (1)如果不加锁,多个线程之间执行是并发的,很可能编译器从原来的内存中读变量优化成从寄存器中读变量,此时会出bug,但是引入cas的优化之后,可以不加锁,然后进行比较。

    (2)如果寄存器A中的值现在加到了2,写回了内存M中,同时t2线程也正在进行操作,就先不进行++操作,先比较一下A和M中的值是否是相等的。

    (3)如果是相等的就说明A已经对M中的值进行了自增,那B就是把自己寄存器中的值更新成M的值,然后在这个基础上再进行自增,此时多线程安全问题就解决了。

    所以说cas是无锁编程。

cas实现原子类:

       在Java标准库中,给我们提供了原子类,如AtomicInteger,就能保证在多线程情况下++和--操作是安全的。就可以在不加锁的情况下实现线程安全。同样的,现在两个线程再对num自增50000次,此时的结果就是正确的。(AtomicInteger中的参数是把num初始化成几的意思,类中提供的方法getAndIncrement是++操作,有前置++后置++还有--的操作都封装成了方法)。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo32 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger num = new AtomicInteger(0);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                //num++
                num.getAndIncrement();
                //++num
                /*num.incrementAndGet();
                num.getAndDecrement();
                num.decrementAndGet();*/
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                num.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(num);
    }
}

我们可以看一下AtomicInteger类的源码,如下图:此时方法中并没有加锁操作,那是如何实现线程安全的呢?

 我们可以来看一段伪代码:  可以具体看一下这个伪代码的意思,就是这样实现线程安全的。

    注:上述代码刚把oldValue赋值给value,这个值能不相等嘛,,此时是可以不相等的,因为是多线程,如果t1线程刚进行到赋值操作,此时就来了t2线程,再赋值,再比较,再自增,此时value的值就改变了,所以,如果不相等,就先进行给寄存器(oldValue)赋值,然后再进行自增操作。

 (此处的CAS就是检测value是否变过,如果没变过---直接自增即可    如果变过了---先赋值再自增)

cas实现自旋锁:

     我们再来看一段伪代码:   自旋锁就是这么实现的。优点:当锁被其他线程释放后,第一时间就可以拿到锁。    缺点:一直循环,进入忙等状态,消耗CPU的资源。

    注:cas优化操作,并非是一段代码,而是一条CPU指令,这一条指令本身就已经是原子性的了,就能完成某些功能(比较再赋值)然后使线程是安全的了,所以cas优化是要靠CPU的支持的。

    cas的a -> b -> a 问题: cas只能是比较是值是否相同,但是不能确定这个值是否中间发生过改变,如果是从a 变成了b,然后又变成了a,此时这个问题该如何解决??(经典面试题,重点掌握) 

     解:关键是aba反复横跳的问题,如果是约定a只能单方向变化呢,就是只能增加或者只能减小,问题就解决了。      但是如果需求是既能增加也能减小咋办?  可以引入一个版本号的变量,约定版本号只能是增加的,(注:每次修改都会增加一个版本号,此时每次增加或者减小的时候就看这个版本号变没变过,再决定当前是否自增),

     只要约定的版本号只能是递增的,就可以保证不会出现aba来回变的情况了。以版本号为准,而不是以那个变量为准了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良月初十♧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值