JavaEE初阶----总结锁策略,CAS 和 synchronized 优化过程

⭐️常见锁策略

这个部分主要是一些面试常考的八股文,主要是为了应付面试。不必太纠结其细节。
注意:
锁策略和普通的程序猿基本没啥关系和”实现锁“的人才有关系。

这里所提到的“锁策略”,和 Java 本身没有关系,适用于所有和“锁”相关的情况。

1.悲观锁VS乐观锁

悲观锁:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这
样别人想拿这个数据就会阻塞直到它拿到锁。
乐观锁:

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

简单说一下我理解的:
悲观锁:预期锁冲突的概率很高。例如:悲观的态度认为下一波疫情来了之后,可能买不到菜要抢菜,于是换了一个很大的冰箱并且去超市定期屯一些米面油肉+方便面+生活用品。【要做更多的事情,付出更多的成本和代价,更低效】
乐观锁:预期锁冲突的概率很低。例如乐观的态度认为下一波疫情即使来了,但是不会出现买不到菜和抢菜的情况,便不做过多的预备打算。【做的事情更少,付出的成本和代价更少,更高效】

2.读写锁vs普通的互斥锁

普通的互斥锁,只有两个操作:加锁 和 解锁。
只要两个线程针对同一个对象进行加锁,就会产生 互斥 / 锁竞争。

对于读写锁来说,分成了三个操作:
1、加读锁:如果代码只是进行读操作,就加读锁
2、加写锁:如果代码中只是进行了修改操作,就加写锁。
3、解锁
这样就把读和写操作给天然的分离开来了

3.重量级锁vs轻量级锁(和上面的悲观乐观有一定重叠)

重量级锁就是做了更多的事情开销更大
轻量级锁就是做的事情更少开销更小
例如:

在使用的锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口),此时一般认为这是重量级锁
如果锁是纯用户态实现的,此时一般认为是轻量级锁。(用户态的代码更可控,也更高效)
也可以认为通常情况下,悲观锁一般都是重量级锁,乐观锁一般都是轻量级锁。(这种观点并不绝对)

4.挂起等待锁vs自旋锁

挂起等待锁往往就是通过内核的一些机制来实现的,往往较重【重量级锁的一种典型实现】
自旋锁往往就是通过用户态代码来实现的往往较轻【轻量级锁的一种典型实现】

5.公平锁vs非公平锁

首先评价一件事情是公平还是不公平的标准就是依靠制定的规则,在这儿我们的规则是:遵循先来后到
公平锁:多个线程在等待一把锁的时候,谁先来谁就能够先获取到这个锁
非公平锁:多个线程在等待一把锁的时候,不遵循先来后到的规则

要想实现公平锁反而需要付出更多的代价(要整个队列来把这些参与竞争的线程给排一排先来后到~~)

6.可重入锁vs不可重入锁

一个线程,针对一把锁,咔咔连续加锁两次,如果会死锁,就是不可重入锁,如果不会死锁,就是可重入锁~

对于上面的知识,不用深入了解,万一面试官真的说到上述的一些词,不至于太懵逼~就okk。

⭐️synchronized

1.既是一个乐观锁,同时也是一个悲观锁(根据锁竞争的激烈程度,自适应)
2.不是读写锁,只是一个普通互斥锁
3.既是一个轻量级锁,也是一个重量级锁(根据锁竞争的激烈程度,自适应)
4.轻量级锁的部分基于自旋锁来实现,重量级锁的部分基于挂机等待锁来实现
5.非公平锁
6.可重入锁

⭐️CAS(compare and swap)

原理:要做的事情就是拿着寄存器/某个内存中的值和另外一个内存的值进行比较,如果值相同了,就把另一个寄存器/内存的值和当前的这个内存进行交换。

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B
1.比较A与V是否相等(比较)
2.如果比较相等,将B写入V。(交换)
3.放回操作是否成功。
JavaEE初阶----总结锁策略,CAS 和 synchronized 优化过程_第1张图片
此处所谓的CAS指的是,cpu提供了一个单独的CAS指令,通过这条指令,就能完成上诉伪代码描述的过程。

如果上诉过程都是这“一条指令”就干完了,就相当于这是原子的了(CPU上面执行的指令就是一条一条执行的~指令已经是不可分割的最小单位)此时线程就代表是安全的。

CAS最大的意义就是让我们写这种多线程安全的代码,提供了一个新的思路和方向(就和锁不一样了)

CAS都能干啥?如何帮助我们解决一些线程安全的问题

举一个例子把:
1.基于CAS能够实现“原子类”

Java标准库里提供了一组原子类,针对锁常用的一些long,int,intarray。。。。进行了封装,可以基于CAS的方式修改,并且线程安全)

import java.util.concurrent.atomic.AtomicInteger;

class test{
    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.getAndIncrement();
            }
        });
        t1.start();
        Thread t2=new Thread(()->{
            for(int i =0;i<50000;i++)
            {
                num.getAndIncrement();
            }
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println(num.get());
    }
}

原子类背后具体是怎么实现的?

class AtomicInteger{
    private int value;
    public int getAndIncrement(){
        int oldValue=value;
        while (CAS(value,oldValue,oldValue+1)!=true)
        //CAS就是来判定一下,当前内存的值,是不是和刚才取出的值是一样的,
            // 如果两个值是一样的,就把value值,设为oldValue+1,同时放回true,循环结束
            //如果两个值不一样,就啥也不做,返回false,进行下次循环继续判断
        {
            oldValue=value;
        }
        return oldValue;
    }
}

2.基于CAS能够实现“自旋锁”

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

        }
    }
}
public void unlock()
{
    this.owner=null;
}

和刚才的原子类类似,也是通过一个循环来实现的,循环里面调用CASCAS会比较当前的owner值是否为null,如果是null就改成当前线程,意思是当前线程拿到了锁,如果不是null就返回false,进入下一次循环,下次循环仍然是进行CAS操作,如果当前这个锁一直被别人持有,当前尝试加锁的线程就会在这个while的地方快速反复的进行计算—自旋~~忙等
自旋锁是一个轻量级锁,也可以视为是一个乐观锁~
当前这把锁虽然没能立即拿到,但是预期很快就能拿到(假设锁冲突不激烈)
短暂的自旋几次,浪费点CPU问题都不大,好处就是只要这边锁一释放,就能立即的拿到锁~

如何理解 CAS中的 ABA 问题?(面试常问)

CAS中的关键是先进行比较,再进行交换,比较当前值和旧值是不是相同,如果这两个值相同就视为是中间没有发生过改变。
但是这里的结论存在漏洞,当前值和旧值可能是中间确实没改变过,也有可能变了,但是又变回来了,这样的漏洞,在大多数情况下,其实没啥影响~~~,但是,极端情况下也会引起bug~
ABA问题就好比,我今天买了一个手机,我拿到这个手机,以我的技术我无法区分出这个是一个全新的手机还是翻新机,虽然说翻新机大多数情况下,也是能用的,说不定还挺不错,但是少数情况下还是可能翻车~~
举一个典型的例子~因为ABA问题产生bug(有点复杂不过很容易看懂)
JavaEE初阶----总结锁策略,CAS 和 synchronized 优化过程_第2张图片

那么我们要如何处理ABA问题呢?

那就非常容易想到了,类似于给他贴一个标签,每次给他++或者--

引入一个“版本号”,这个版本号,只能变大,不能变小。
在修改变量的时候,比较就不是比较变量本身了,而是版本号了。

⭐️synchronized 中的 锁优化的机制

其实锁优化机制可以理解为就是编译器对锁的优化
首先我们得要回顾一下synchronized是属于哪些锁:
1.既是一个乐观锁,同时也是一个悲观锁(根据锁竞争的激烈程度,自适应)
2.不是读写锁,只是一个普通互斥锁
3.既是一个轻量级锁,也是一个重量级锁(根据锁竞争的激烈程度,自适应)
4.轻量级锁的部分基于自旋锁来实现,重量级锁的部分基于挂机等待锁来实现
5.非公平锁
6.可重入锁
synchronized 几个典型的优化手段:
1、锁膨胀/锁升级
synchronized会根据自身的情况来对锁进行升级(体现了能够“自适应”这样的能力)
JavaEE初阶----总结锁策略,CAS 和 synchronized 优化过程_第3张图片

2、锁粗化
JavaEE初阶----总结锁策略,CAS 和 synchronized 优化过程_第4张图片
3.锁消除
JavaEE初阶----总结锁策略,CAS 和 synchronized 优化过程_第5张图片

你可能感兴趣的:(java,intellij-idea,java,面试,经验分享)