#详细介绍!!!常见的锁策略+经典面试题

本篇主要介绍常见的锁策

包括以下几种:

1. 乐观锁 vs 悲观锁

2. 轻量级锁 vs 重量级锁

3. 自旋锁 vs 挂起等待锁

4. 读写锁

5. 公平锁 vs 非公平锁

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

7. 经典面试题

目录

一:常见锁策略详细解读

1. 乐观锁 vs 悲观锁

2. 轻量级锁 vs 重量级锁

3. 自旋锁 vs 挂起等待锁

4. 读写锁

5. 公平锁 vs 非公平锁

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

二.:经典面试题

1.你是怎样理解乐观锁和悲观锁的,具体怎么实现的

2.介绍下读写锁

3.什么是自选锁,我为什么使用自旋锁策略,有什么缺点



#详细介绍!!!常见的锁策略+经典面试题_第1张图片


 

一:常见锁策略详细解读

1. 乐观锁 vs 悲观锁

乐观锁:主要思想是:假设锁的并发程度低,在此场景下去尽可能的提高执行效率

乐观锁指的是一个线程针对加锁,并不会开始就直接加锁,而是直接执行代码(不加锁执行),直到要进行修改变量等危害线程安全的代码时,再判断是否有其他线程并发执行了锁代码,如果有则此返回错误信息并且此处执行作废

乐观锁有个重要的特点:虽然没加锁,但是能识别锁代码是否存在并发冲突

如果不存在并发执行,那么此时不加锁也能保证线程安全,

如果处于并发更新数据,那么此时后修改则的代码进行重新执行,直到执行成功

乐观锁一般用于锁竞争程度较低的场景

实现乐观锁:可引入一个版本号,每次执行版本都累加,通过判断版本号是否正确来判定当前执行是否有效(是否存在冲突)

如果:执行完写入的版本号的预期版本号不符合则数据无效

悲观锁:主要思想是:假设当前锁的并发程度高,且认为每个线程的并发都有安全隐患。

悲观锁指的是一个线程针对加锁,在开始就直接加锁,那么其他并发线程就会等待解锁在去进行锁竞争加锁,此时就一定不会存在线程安全,比较已经加锁了,其他线程进不去锁。

悲观锁适用于锁竞争程度高的场景

实现悲观锁:每次执行所资源都真正加锁,竞争锁失败的线程就堵塞等待

例子:

假设A想去奶茶店买奶茶。

悲观锁:A认为现在奶茶店很忙,所以A会在确定奶茶店空闲(锁空闲)的时候再去买(加锁)

乐观锁:A认为现在奶茶店很闲,所以A会直接去奶茶店

        如果发现真的很闲,那么此时就直接买奶茶(修改变量等操作)

        如果发现奶茶店很忙,那么此时就回家,下次再来(白跑)

乐观锁和悲观锁适用场景:

乐观锁一般用于锁竞争程度较低的场景

原因:如果该锁并发程度高,此时则会造成大量线程在并发的时候反复执行锁代码且并没有取到实质性的作用(白跑),造成资源浪费   

悲观锁适用于锁竞争程度高的场景

原因:避免了大量线程在并发的时候反复执行锁代码且并没有取到实质性的作用(白跑)。每个线程在并发的时候都进行锁等待,此时就避免了资源的浪费

2. 轻量级锁 vs 重量级锁

轻量级锁:在线程竞争锁失败时不会堵塞等待,而是反复去进行加锁操作直到加锁成功。

重量级锁:重量级锁在竞争锁失败时会堵塞等待,让出cpu资源,直到锁资源解锁再去加锁。

优缺点:

轻量级锁:

优点:在竞争锁失败后不会释放CPU资源,而是重复去进行加锁操作,线程能在第一时间获取到锁。此时内核态操作就少了,因为线程一直在进行(用户态)加锁操作,并没有大量的操作系统去进行调度。    

缺点:不释放持续占用CPU资源,当锁竞争程度大的时候,会造成大量的资源无效浪费

重量级锁: 

优点:在大量线程并发执行锁资源时,竞争锁失败的线程会放弃CPU资源进行堵塞等待,使得资源得到有效利用

缺点:由于重量级锁严重依赖OS提供的mutex,导致进行锁等待的线程进行系统调度过于频繁(用户态+内核态),导致线程不一定能第一时间拿到锁,使得效率变低

注意:CPU给操作系统(os)提供了原子操作指令

           os给JVM提供了mutex(互斥锁)

           JVM给JAVA代码提供了synchronized等加锁机制的关键字

3. 自旋锁 vs 挂起等待锁

自旋锁:

自旋锁是一个典型的轻量级锁的实现方式

自旋锁就是线程进行加锁操作,如果锁已经被占用,那么线程不会放弃CPU资源,就到锁外一直尝试加锁操作(自旋加锁),直到加锁成功(参考轻量级锁)

优缺点和上面轻量级锁一致:

优点:不放弃CPU资源,以用户态的操作去一直旋转加锁,能在第一时间加锁成功

缺点:并发程度高时占用CPU资源,无实质性作为,造成大量资源浪费

下列伪代码:没加锁成功就一直循环(自旋)加锁,直到成功

while((加锁) != true){};

挂起等待锁:

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

挂起等待锁就是线程进行并发加锁时,如果锁竞争失败,则放弃CPU资源进行堵塞等待,待唤醒后再次尝试加锁。(参考重量级锁)

优缺点和上面的重量级锁一致:

优点:并发程度高时,竞争锁资源失败的线程放弃CPU资源进行阻塞等待,使资源得到有效的利用

缺点:不一定能在第一时间拿到锁,比较涉及到内核态的操作对于计算机来说就很慢。

4. 读写锁

读写锁:

是把读操作和写操作分开来对待了

JAVA提供了ReentrantReadWriteLock类来进行读写锁的添加

其中:ReentrantReadWriteLock.ReadLock是提供一个读加锁

           ReentrantReadWriteLock.WriteLock是提供一个写加锁

           他们的加锁解锁是使用lock和unlock方法

我们都知道多线程并发执行,不涉及到写(修改等)的操作是不存在线程安全问题

例如:多线程同时读取内存中的同一个数据,此时不管怎么读,读到的都是那个数据,也不会发生改变,不存在线程安全问题

那么在频繁读的操作上就不需要加锁去限制并发,进而大大提高效率

读写锁就是针对这种情况作出的改变


读写锁:在多线程并发执行下不涉及到写的操作时,那么这些加锁操作并不会互斥,它们仍然是并发执行的,这让他们的读效率给拉满 了   

3种情况:

读加锁与读加锁之间可并发执行,不互斥  

读加锁与写加锁之间互斥

写加锁与写加锁之间互斥

5. 公平锁 vs 非公平锁

公平锁:线程并发执行,加锁顺序遵循先来后到的原则

例如:此时线程A占用了锁,线程B来尝试加锁看有锁已经被占用了在一旁等待,线程C也来尝试加锁

此时线程A解锁之后,并不会形成锁竞争,而是B直接加锁,因为B比C先进行尝试加锁操作

非公平锁:线程并发执行,加锁顺序是无序的,谁抢到锁就谁加锁

例如:此时线程A占用了锁,线程B来尝试加锁看有锁已经被占用了在一旁等待,线程C也来尝试加锁

此时线程A解锁之后,形成锁竞争,B,C线程先抢占一番锁,锁速度快拿到锁就谁加锁

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

可重入锁:指的是同一个线程可以针对同一把锁进行多次加锁(递归加锁)

在JAVA中以Reentrant开头的类都是实现了可重入锁功能

例如:A线程正处于加锁状态,此时A线程因为其他原因在没解锁的状态下,在此对这把锁进行加锁操作

按照一般情况下,锁已经被占用了,后续加锁的线程进行堵塞等待(不可重入锁)

但是可重入锁可以再次加锁成功

下列伪代码:

        如果是可重入锁则正常加锁,不会阻塞等待

        如果是不可重入锁,则会堵塞等待

                原因:锁已经被占用了,线程拿不到锁

// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用
lock();

二.:经典面试题

答案上面都有介绍

1.你是怎样理解乐观锁和悲观锁的,具体怎么实现的

2.介绍下读写锁

3.什么是自选锁,我为什么使用自旋锁策略,有什么缺点


#详细介绍!!!常见的锁策略+经典面试题_第2张图片#详细介绍!!!常见的锁策略+经典面试题_第3张图片

 

 #详细介绍!!!常见的锁策略+经典面试题_第4张图片

 

你可能感兴趣的:(java,开发语言,java-ee,面试,jvm)