【Java之多线程】一文帮你彻底搞懂多线程面试重难点:有关锁的使用策略_java 多线程面试重难点

3. 基于版本号方式实现乐观锁

实现乐观锁策略这一功能的方式有很多,接下来我带大家去学习一种:基于版本号方式
假设我们要使用多线程修改用户的账户余额,我们可以引入一个版本号来实现,具体方法如下:

设当前的余额为100,引入一个版本号version,将其初始值设为1,并且我们规定,提交版本必须大于数据库中记录的当前版本号才能执行更新余额的操作,若不满足此条件,则认为修改失败

图示
以线程1想把主内存中的数据减50,线程2把主内存中的数据减20为例:
在这里插入图片描述
线程1此时准备将主内存中的数据读入自己的工作内存中并修改,而线程2也想将主内存的数据读入自己的工作内存中并修改,此时线程1和线程2以及主内存中的版本号都为1

当线程1把主内存的数据减50后,即修改后,会将自己工作内存中的版本号加1,此时线程1工作内存中的版本号大于主内存中的版本号(2大于1),因此线程1成功修改了主内存中的数据,并将数据50写入主内存中,最后将主内存中的版本号加1(即为2)在这里插入图片描述
此时线程2修改了自己工作内存中的数据,随后将自己的工作内存版本号改为2:
在这里插入图片描述
但正当线程2准备将其改好后的数据80写入主内存时,发现自己的版本号和主内存的版本号都一样,并不满足大于关系,因此此次修改失败,有效避免了多线程并发修改数据时引起的数据安全问题。
总结

  1. 基于版本号这样实现乐观锁的机制就是一种典型的实现方式,这个实现方式和之前所学过的单纯的互斥的加锁方式来说更加轻量一些(只修改版本号,只是在计算机中用户态上进行操作,而互斥加锁方式会涉及到用户态和内核态之间的切换,不仅效率不太高,也容易引起线程阻塞)
  2. 对于这个机制来说,如果修改数据失败,就会涉及到重试操作,如果频繁重试的话那么效率也就不高了,因此,最好在线程并发冲突率比较低的场景下使用乐观锁这一方式比较好

二. 读写锁

1. 理解

在这里插入图片描述
我们都知道,当我们通过多线程方式尝试修改同一数据时,一般都可能引发线程安全问题,但当我们通过多线程方式尝试读取同一数据时,一般不会引发线程安全问题,因此,我们可以根据读和写的不同场景来给读和写操作分别加上不同的锁。
Java当中的synchronized不会对读和写进行区分,默认使用后线程都是互斥的

2. 用法

以Java为例,在标准库中存在这样一个类ReentrantReadWriteLock
源代码如下

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /\*\* Inner class providing readlock \*/
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /\*\* Inner class providing writelock \*/
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /\*\* Performs all synchronization mechanics \*/
    final Sync sync;

    /\*\*
 \* Creates a new {@code ReentrantReadWriteLock} with
 \* default (nonfair) ordering properties.
 \*/
    public ReentrantReadWriteLock() {
        this(false);
    }

该类中提供了两个方法:

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

此方法可以创建出一个读锁实例

public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

此方法可以创建出一个写锁实例
某个线程被读锁修饰后,这两个线程之间不会互斥,而是完全同时并发执行,一般将读锁用于线程读取数据比较多的场景;而当某个线程被写锁修饰后,这两个线程会互斥,一个线程会执行,而另一个线程会阻塞等待,因此必须是一个线程执行完了,另一个线程才会执行,一般用于修改数据比较多的场景

三. 重量级锁和轻量级锁

1. 原理

锁的核心特性 “原子性”,这样的机制追根溯源是 CPU 这样的硬件设备提供的

1.CPU 提供了 “原子操作指令”。
2. 操作系统基于 CPU 的原子指令,实现了 mutex 互斥锁.
3. JVM 基于操作系统提供的互斥锁。实现了 synchronized 和 ReentrantLock 等关键字和类。

在这里插入图片描述

注意:synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的工作

2. 理解

1.重量级锁依赖了OS提供的mutex,的开销一般很大,往往是通过内核来完成的
2.轻量级加锁一般不使用mutex,开销一般比较小,一般通过用户态就能直接完成

2. 区分用户态和内核态

我们可以类比一个生活中的例子,当去银行办理业务时,如果是通过用户在银行工作人员的指导下自己在窗口外完成,那么效率会比较高,就像计算机中的用户态一样。而当我们把自己的业务交给银行相关人员去完成时,由于银行工作人员的闲忙时间是不可控的,因此无法保证效率,就好比计算机中的内核态。

四. 自旋锁

1. 理解

当两个线程为了完成任务同时竞争一把锁时, 拿到锁的那个线程会立马执行任务,而没拿到就会阻塞等待,当一个线程把锁释放后,另一个线程不会被立即唤醒,而是等操作系统将其进行一系列的调度到CPU中的操作才能被唤醒然后执行任务,这种锁叫做挂起等待锁,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度。但实际上,大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放,所以没必要就放弃 CPU。这个时候就可以使用自旋锁来处理这样的问题。

2. 实现方式

自旋锁的伪代码为:while (抢锁(lock) == 失败) {}

如果获取锁失败,就会立即再尝试获取锁,无限循环,直到获取到锁为止。第一次获取锁失败, 第二次的尝试会在非常短的时间内到来,一旦锁被其他线程释放, 就能第一时间获取到锁

3. 优缺点

自旋锁是一种典型的轻量级锁的实现方式,它没有放弃 CPU, 不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁,这样会大大提高代码的执行效率,但如果锁被其他线程持有的时间比较久, 那么就会持续地消耗 CPU 资源。(而挂起等待的时候是不消耗 CPU 的)
因此,我们应该注意自旋锁的适用场合:

  1. 如果多个线程执行任务时锁的冲突比较低,或者线程持有锁的时间比较短,此时使用自旋锁比较合适
  2. 如果某个线程任务对CPU比较敏感,且不希望吃太多CPU资源,那么此时就不太适合使用自旋锁。

注意:synchronized自身已经设置好了自旋锁和挂起等待锁,会根据不同的情况自动选择最优的使用方案

五. 公平锁和非公平锁

1. 理解

若有三个线程 A,B,C。
A先尝试获取锁,获取成功了,因为只有一把锁,所以B和C线程都会阻塞等待,那么如果A用完了锁后,B和C线程哪一个会最先获取到锁呢?

  1. 公平锁:遵守先来后到原则,因为B线程比C线程来的早一点,所以B线程先获取到锁
  2. 非公平锁:没有先来后到原则,B和C线程获取到锁的概率是随机的

2. 注意事项

操作系统内部的线程调度就可以视为是随机的,如果不做任何额外的限制,锁就是非公平锁。如果要想实现公平锁,就需要依赖额外的数据结构(比如队列) 来记录线程们的先后顺序。公平锁和非公平锁没有好坏之分, 关键还是看适用场景(大部分情况下非公平锁就够用了,但当我们希望线程的调度时间成本是可控的,那么此时就需要用到公平锁了)

注意:synchronized为非公平锁

六. 可重入锁和不可重入锁

1. 为什么要引入这两把锁

(1)实例一

在介绍可重入锁和不可重入锁之前,大家先来思考一个问题,为什么Java中的main函数要用static来修饰?

public class Test {
    public static void main(String[] args) {
        
    }
}


试想以下,如果main函数不是static来修饰的话:

public class Test {
    public void main(String[] args) {


### 一、网安学习成长路线图


网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/aa7be04dc8684d7ea43acc0151aebbf1.png)


### 二、网安视频合集


观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f0aeee2eec7a48f4ad7d083932cb095d.png)


### 三、精品网安学习书籍


当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/078ea1d4cda342f496f9276a4cda5fcf.png)


### 四、网络安全源码合集+工具包


光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e54c0bac8f3049928b488dc1e5080fc5.png)


### 五、网络安全面试题


最后就是大家最关心的网络安全面试题板块  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/15c1192cad414044b4dd41f3df44433d.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/b07abbfab1fd4edc800d7db3eabb956e.png)  



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值