锁策略和CAS

目录

锁策略

乐观锁和悲观锁

乐观锁

悲观锁

轻量级锁和重量级锁

轻量级锁

重量级锁

自旋锁和挂起等待锁

自旋锁

挂起等待锁

读写锁和互斥锁

互斥锁

读写锁

可重入锁和不可重入锁

CAS

基于CAS实现原子类

实现自旋锁


锁策略

锁策略不是Java语言独有的一门技术,任何和锁有关的话题,都会涉及到锁策略。

策略:解决问题的办法。

乐观锁和悲观锁

锁的实现者预测锁被阻塞的概率大不大,来根据这个冲突的概率来决定接下来咋做。

两个线程针对同一个对象加锁,就会造成阻塞。

通常来说,乐观锁做的事情相对少一些。效率也比悲观锁要高一些。

悲观锁做的事情就比较多,所有效率也就没有乐观锁高。

但是都不绝对。

乐观锁

造成锁冲突的概率并不大

假设数据一般情况下并不会发生并发冲突,所以只有在数据提交更新的时候才会检测是否产生冲突,如果发现冲突了,则返回用户错误的信息,让用户决定如何做。

悲观锁

冲突的概率很大

假设每次都是最坏的情况,在拿数据的时候,都会认为别人会修改数据,所有在每次拿数据的时候都会进行加锁操作,这样别人想拿数据就得阻塞等待。直到解锁。

轻量级锁和重量级锁

轻量级锁

加锁解锁的过程更快更高效。

一个乐观锁可能是一个轻量级锁 但是不绝对

重量级锁

加锁解锁的过程更慢更低效。

一个悲观锁可能是一个重量级锁  但是不绝对

自旋锁和挂起等待锁

自旋锁是轻量级锁的一种典型实现

挂起等待锁是重量级锁的一种典型实现

自旋锁

如果获取锁失败,就会立即尝试重新获取锁,一直无限循环,直到获取到锁为止。一旦其他线程释放锁,那么就能第一时间获取到锁。

通过是用户态操作,不需要经过系统内核,时间相对较短

挂起等待锁

不会一直无限循环等待锁的释放,这就导致挂起等待锁就不能第一时间获取到锁。

那么通过上面的几种锁策略,我们的synchronized这把锁是属于那种呢?

我们的synchronized即使乐观锁,也悲观锁,即使轻量级锁,也是重量级锁。

轻量级锁基于自旋锁实现,重量级锁基于挂起等待锁实现。

synchronized会根据我们当前锁的竞争激烈程度,自适用来运行。

如果锁的竞争不激烈,那么就是乐观锁,轻量级锁的状态运行。

如果锁的竞争很激烈,那么就是悲观锁,重量级锁的状态运行。

读写锁和互斥锁

互斥锁

synchronized是互斥锁  只有两个操作  就是单纯的加锁和解锁

进入synchronized修饰的代码块:加锁

退出synchronized修饰的代码块:解锁

读写锁

给读和写两种锁分开:

读写锁有三种操作:

1:给读加锁

2:给写加锁

3:解锁

如果多个线程读取同一个变量,就不会产生线程安全问题

读写锁中约定:

1:读锁和读锁之间不会产生竞争 不会产生阻塞等待

2:写锁和写锁之间会有锁竞争

3:读锁和写锁之间也有锁竞争

相当于把加锁操作区分的更加精细了。

可重入锁和不可重入锁

如果一个锁,在一个线程中,连续对该锁加锁两次,不死锁,就是可重入锁,如果死锁了,就是不可重入锁。

但是synchronized是可重入锁,因为加锁的时候会判定下,看当前尝试申请锁的线程是不是已经是锁的拥有者了,如果是则直接放行。

CAS

compare and swap   比较并交换

和多线程密切相关的一个东西。

可以让咱们不加锁也能实现线程安全的操作。 

寄存器A的值和内存M的值进行比较,如果值相等,就把寄存器A的值和内存M的值进行交换。

交换相当于赋值操作。

CAS操作是CPU的一条指令,这一条指令已经是CPU的最小单位

这一条指令就能完成上述比较并交换的功能。

基于CAS实现原子类

public static void main(String[] args) throws InterruptedException {
    AtomicInteger num = new AtomicInteger(0);  //原子类 AtomicInteger
    //保证++ -- 的时候线程安全
    Thread t1 = new Thread(()->{
        for (int i = 0; i < 5000; i++) {
            //num++
            num.getAndIncrement();
        }
    });
    Thread t2 = new Thread(()->{
        for (int i = 0; i < 5000; i++) {
            //num++
            num.getAndIncrement();
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    //get获取数值
    System.out.println(num.get());
}

上述代码我们可以看见,还是同时两个线程一起执行,但是这个两个线程进行++的操作是通过AtomicInteger来实现的。

这个类就是基于CAS实现的,可以让我们不加锁就能实现多线程环境下的安全问题。

CAS在自增之前,会检查内存和寄存器中的值是否一样,如果一样则自增,如果不一样,则更新内存中的值,在进行自增操作。

CAS本身就是CPU的最小单位,已经无法再次拆分了,所有上述的交换,和比较操作是无法再次拆分的。

实现自旋锁

反复检查锁的状态是否解开了,如果发现锁的状态已经解锁,就能第一时间拿到锁。

owner用来记录当前锁被那个线程持有

unlock操作就是解锁 直接置为null

如果当前owner为空,比较就成功,就把当前线程的引用赋值到owner中,加锁完成,循环结束。

如果比较不成功,意味着owner非空,表示已经有线程把锁持有了,此时CAS就啥都不干,直接返回false,循环继续。

此时这个循环就会转的飞快,会不听的尝试循环这个锁是否被释放,一旦锁释放,就立即能获取到。但是CPU在盲等。

实现自旋锁,目的就是为了盲等,就是为了能够最快速度拿到锁。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值