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

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

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

需要这份系统化资料的朋友,可以点击这里获取

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


该类中提供了两个方法:



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

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



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


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


## 三. 重量级锁和轻量级锁


### 1. 原理


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



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


![在这里插入图片描述](https://img-blog.csdnimg.cn/1af857c280d84dc0bd6d75ae49bc3fb9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5pil6aOO772e5Y2B5LiA6L29,size_20,color_FFFFFF,t_70,g_se,x_16)


注意: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) {
Test a=new Test();
a.main();
}
}


**那么此时这段代码能否被执行呢?答案是不能,因为在java中,没有static的变量或函数,如果想被调用的话,是要先新建一个对象才可以。而main函数作为程序的入口,需要在其它函数实例化之前就启动,这也就是为什么要加一个static。main函数好比一个门,要探索其它函数要先从门进入程序。static提供了这样一个特性,无需建立对象,就可以启动。也可以利用反证法说明,如果不是static修饰的,若不是静态的,main函数调用的时候需要new对象,new完对象才能调用main函数。那么你既想进入到main函数new对象,又想new完对象来调用main函数,那么就不行了,相当于自己把自己锁在里面出不来了**


#### (2)实例二


另外一个Java当中的例子:



synchronized void func1(){
func2();
}
synchronized void func2(){

}

我们对func1这个方法进行加锁时,是可以成功的,但当我们对func2这个方法再次加锁后,就比较危险了。因为要执行完func1这个方法,就必须执行完func2,而此时锁已经被func1这个方法占用了,func2获取不到锁,所以func2就会一直阻塞等待,去等func1释放锁,但func1一直执行不完成,所以锁永远不会释放,func2永远也获取不到锁,这样就形成了一个闭环,相当于自己把自己锁在里面出不来了,此时这个线程就会崩溃,是比较危险的


### 2. 实现方案


了解了上面两个实例的严重性后,我们引入了可重入锁这个机制,当我们形成死锁后,如果是可重入锁的话,它不会让线程阻塞等待最终死锁从而奔溃,而是运用计数器的方法,去记录当前某个线程针对某把锁尝试加了几次,每加一次锁计数都会加1,每次解锁计数都会减1,这样当计数器里面的计数完全为0的时候才会真正释放锁,正是因为有了这样的机制,才有效避免了死锁问题。**而在Java中,`synchronized`就是一把可重入锁,它给我们提供了很大的方便,保证在我们即使造成死锁问题时,程序也不至于崩溃。**


## 七. 面试题


### 1. 如何理解乐观锁和悲观锁,具体实现方式如何


1. 如何理解?见**乐观锁和悲观锁字面理解**部分(尝试用自己的语言组织)
2. 实现方式:  
 (1)乐观锁:见**基于版本号方式实现乐观锁**部分  
 (2)悲观锁:多个线程访问同一个共享数据时产生并发冲突时,会在取数据时会进行加锁操作,这样的话其他线程想要拿到这个数据时就会阻塞等到直到其他线程获取到锁


### 2. 简单介绍一下读写锁


读写锁实际是一种特殊的自旋锁,它能把同一块共享数据的访问者分为读者和写者,读写锁会把读操作和写操作分别进行加锁,且读锁和读锁之间的线程不会发生互斥,写锁和写锁之间以及读锁和写锁之间的线程会发生互斥。读锁适用于线程读取数据比较多的场景,而写锁适用于线程修改数据比较多的场景。


### 3. 简单介绍一下自旋锁


1. 理解:当两个线程为了完成任务同时竞争一把锁时, 拿到锁的那个线程会立马执行任务,而没拿到锁的线程就会立即再尝试获取锁,无限循环,直到获取到锁为止,这样的锁就叫自旋锁
2. 优点和缺点:见**自旋锁优缺点**部分


### 4. 简单介绍一下Java中synchronized充当了哪些锁


1. 主要以悲观锁为主,初始使用乐观锁策略,但当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略
2. 并不区分读写锁
3. synchronized自身已经设置好了自旋锁和挂起等待锁,会根据不同的情况自动选择最优的使用方案
4. synchronized是一把非公平锁
5. synchronized就是一把可重入锁


## 学习路线:

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/7a04c5d629f1415a9e35662316578e07.png#pic_center)





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

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

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

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值