• CAS操作和sychronized实现原理


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    一、CAS

    什么是CAS

    1.cas叫做比较和交换
    寄存器a和内存m的值进行比较
    如果不相同则无事发生
    相同,寄存器b,和m的值进行交换(不关心交换之后b的值,更关心交换之后m的值,此处的交换相当于是把b赋值给了m)

    CAS应用:

    实现原子类

    前面讲过count++在多线程环境下,线程不安全,要想安全,加锁(加锁性能大大折扣)这个似乎基于cas操作来实现原子的++,保证线程安全,高校
    先来看看伪代码实现:

    在这里插入图片描述
    这是一个原子类,getandincrement()方法相当于count++,oldValue相当于是寄存器a,暂且将oldValue+1理解成寄存器b的值,value是内存中的值

    这段代码执行的逻辑是先看value内存中的值是否和寄存器a的值相同,
    如果相同,把寄存器b的值设置到value中,同时cas返回true,结束循环
    如果不相同,无事发生,cas返回false,进入循环,循环体里重新读取内存value到寄存器a中

    画图演示:

    上述过程抽象成当俩个线程同时调用getAndIncrement时

    执行顺序:在这里插入图片描述
    load表示寄存区读取内存中数据,t1,t2是俩个线程,value是内存,a,b是俩个寄存器
    在这里插入图片描述
    假设t1,t2线程同时加载value到a,t1线程先判断寄存器a的值和value相同,于是b(oldvalue+1)值与内存value交换
    在这里插入图片描述
    t2线程执行cas时候发现,a和value不等,于是重新load,返回false重新进入循环如图:
    在这里插入图片描述
    执行完load,t2重新执行cas最终判决a==value,于是将计数器b(a+1)的值和value交换
    在这里插入图片描述
    这样俩个线程都完成了count++的操作,线程安全,最终内存中value置为2

    实现自旋锁

    竞争不激烈的时候很合适(第一时间拿到锁)
    纯用户态的轻量级锁,当发现锁被其他线程持有的时候,另外的线程不会挂起等待,而是反复询问,看当前的锁是否被释放了(节省了进入内核和系统调度的开销)
    伪代码:

    在这里插入图片描述
    先设置owner为空,表示无人获取的状态,owner表示当前的锁是被谁获取到的
    如果owner和null相同,把当前调用lock的线程的值,设置到owner里(相当于加锁成功)同时结束循环
    如果owner不为null,则cas不进行交换,返回false,会进入循环,又会立即再次发起判断

    CAS的ABA问题

    1.什么是aba问题:
    在这里插入图片描述
    举例说明:银行取款,a扣款的时候,b打钱给a,a的钱到底有没有发生变化(是否扣款成功)
    在这里插入图片描述

    2.解决方案:
    只要有一个记录,能够记录内存中数据的变化
    如何记录:
    另外搞一个内存,保存m的修改次数(版本号)【只增不减】每次进行一次操作都增加版本号,
    此时修改操作要先判断版本号是否相同,如果不相同则修改操作失败

    在这里插入图片描述

    二、Sychronized实现原理

    加锁具体过程

    锁升级

    偏向锁

    类似于懒汉模式,必要加,不必要不加
    类似于count++
    在这里插入图片描述
    偏向锁不是真加锁,而是设置了一个状态,当t2lock时t1的锁才真生效

    轻量级锁

    重量级锁

    通过自适应的自旋锁来转换,
    在这里插入图片描述

    总结:

    无竞争,偏向锁,
    有竞争,轻量级锁
    竞争激烈,重量级锁

    其他优化操作

    锁消除

    jvm自动判定,发现这个代码不需要加锁,写了synchronized就会自动把锁去掉
    比如,你只有一个线程/虽然有多个线程不涉及修改同一个变量,如果代码写了synchronized此时加锁操作就会被synchronied被给jvm干掉
    锁消除只是在编译器/jvm有十足的把握时候进行的

    锁粗话:

    在这里插入图片描述


    三、Callable接口

    1.callable接口类似于runnable
    runnable描述的任务,不带返回值
    callable描述的任务是带返回值的

    如果你当前多线程完成任务,希望带上结果使用callable比较好

    泛型结果是返回值的类型
    2.代码演示

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    //演示使用callable定义一个任务
    public class demo29 {
        //创建线程,通过线程来计算1+2+3+4+5+6+.....+100;
        public static void main(String[] args) throws ExecutionException,InterruptedException {
            Callable<Integer>callable=new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum=0;
                    for (int i = 0; i <=1000 ; i++) {
                        sum+=i;
                    }
                    return sum;
                }
            };
            FutureTask<Integer>futureTask=new FutureTask<>(callable);
    
            //创建线程,来执行上述任务
            //thread的构造方法,不能直接传callable,还需要一个中间的类;
            Thread t=new Thread(futureTask);
            t.start();
    
            //获取线程的计算结果
            //get方法会阻塞,直到call方法计算完毕,get才能返回
            System.out.println(futureTask.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    futureTask
    在这里插入图片描述
    存在的意义就是让我们能够获取到结果(获取到结果的凭证)

    在这里插入图片描述
    2.RentrantLock

     //演示ReentrantLock的加锁方式
        public static void main(String[] args) {
            ReentrantLock locker=new ReentrantLock(true);
            try {
                //加锁
                locker.lock();
            }finally {
                //解锁
                locker.unlock();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    缺点,如果加锁解锁之间有return,或者异常,解锁执行不到,可以采取finally

    说说ReentrantLock和sychroinized区别:

    缺点:如果加锁解锁之间有return,或者异常,解锁执行不到
    优势:
    1.tryLock试试看能不能加上锁,式成功加锁成功,失败则放弃,并且还可以指定加锁的等待超时时间
    实际开发中,使用这种死等的策略往往要慎重,trylock给我们提供了更多的可能
    2.reentranlock可以实现公平锁,默认是公平的,构造的时候,传入一个简单的参数,就实现公平锁了
    ReentrantLock locker=new ReentrantLock(true);
    3.synchronized是搭配wait/notify实现等待通知机制,唤醒操作是随机唤醒一个等待的线程
    reentrantLock是搭配Condition类实现,唤醒操作是可以指定唤醒哪个线程的

    原子类

    使用原子类,最常见的常见场景就是多线程计数
    写了个服务器,服务器一共有多少并发量,可通过原子类来累加

    package Threading;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    //演示原子类
    public class demo31 {
        public static void main (String[] args) throws InterruptedException {
            AtomicInteger count=new AtomicInteger(0);
            //相当于++count
    //        count.incrementAndGet();
            //相当于count--
    //        count.getAndDecrement();
            //相当于--count
    //        count.decrementAndGet();
    
            Thread t1=new Thread(()->{
                for (int i = 0; i <50000 ; i++) {
                    //相当于count++;
                    count.getAndIncrement();
                }
            });
            Thread t2=new Thread(()->{
                for (int i = 0; i <50000 ; i++) {
                    //相当于count++;
                    count.getAndIncrement();
                }
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            //get 获取到内部的值
            System.out.println(count.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    信号量

    p 申请一个资源
    v 释放一个资源
    信号量本身是一个计数器,表示可用资源
    p可用资源-1
    v可用+1

    当计数为0继续p,会阻塞等到v执行
    信号量可用释放一个更广义的锁,锁就是一个特殊的信号量(可用资源只有1的信号量)
    import java.util.concurrent.Semaphore;

    //演示显示量p,v操作
    public class demo32 {
        public static void main(String[] args) throws InterruptedException {
            Semaphore semaphore=new Semaphore(4);
            semaphore.acquire();
            System.out.println("p操作");
            semaphore.acquire();
            System.out.println("p操作");
            semaphore.acquire();
            System.out.println("p操作");
            semaphore.acquire();
            System.out.println("p操作");
            semaphore.acquire();
            System.out.println("p操作");
            semaphore.acquire();
            System.out.println("p操作");
            //这是v操作,释放资源,计数器-1;
            semaphore.release();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    HMS Core音频编辑服务音源分离与空间音频渲染,助力快速进入3D音频的世界
    macOS 上如何写自定义命令行工具?
    ERP系统排行
    RedisTemplate用法
    【C语言数据结构】线性表-顺序存储-动态分配(顺序表)
    【微服务】SpringCloud的OpenFeign与Ribbon配置
    docker对已经启动的容器添加目录映射(挂载目录)
    编写递归SQL的思路
    NVR添加rtsp流模拟GB28181视频通道
    Windows服务器部署SpringBoot及文件服务器MinIO
  • 原文地址:https://blog.csdn.net/panpanaa/article/details/127656429