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());
    }
}

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

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

 //演示ReentrantLock的加锁方式
    public static void main(String[] args) {
        ReentrantLock locker=new ReentrantLock(true);
        try {
            //加锁
            locker.lock();
        }finally {
            //解锁
            locker.unlock();
        }
    }
}

缺点,如果加锁解锁之间有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());
    }
}

信号量

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();
    }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值