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

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

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

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

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

* 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 等关键字和类。
> 
> 
> 


![在这里插入图片描述](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充当了哪些锁




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


网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。  
 ![在这里插入图片描述](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/forums/4f45ff00ff254613a03fab5e56a57acb)**

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值