多线程进阶--面试常见知识点

1 常见的锁策略

1.1 乐观锁vs悲观锁

悲观乐观是对后续锁冲突是否频繁做出的预测。

如果预测接下来发生锁冲突的概率不大,就可以少做一些工作,就称为乐观锁;

如果预测接下来发生锁冲突的概率较大,就需要多做一些工作,就称为悲观锁。

1.2 重量级锁vs轻量级锁

重量级锁,锁的开销较大;轻量级锁,锁的开销较小。

通常,乐观锁就是轻量级锁,悲观锁就是重量级锁。

1.3 自旋锁vs挂起等待锁

自旋锁是轻量级锁的的一种典型实现,往往在用户态进行,例如使用一个while循环,不断判断当前锁是否被释放,如果没释放,就继续循环,如果已经释放,就获取到锁并结束循环。(忙等,消耗cpu但是换来了更快的响应速度);

挂起等待锁要借助系统api来实现,一旦出现锁竞争,就会在系统内核中执行一系列的操作(比如让这个线程进入阻塞状态,不参与cpu的调度,而阻塞的开销通常是很大的)。

1.4 读锁vs写锁

读锁:读的时候别的线程可以读,但是不可写。

写锁:写的时候别的线程不可以读,也不可以写。

1.5 公平锁vs非公平锁

当一堆线程等待锁释放想要拿到锁的时候,该按照什么策略拿呢?

公平锁:先来的线程先拿到锁。(先来后到)

非公平锁:每个线程以相同的概率竞争锁。

以上策略都是锁的一些特点,那前面说过的synchronized属于哪种锁呢?

1、对于"悲观乐观"是自适应的。

2、对于"重量轻量"是自适应的。

3、对于"自旋挂起"是自适应的。

4、不是读写锁。

5、是可冲入锁。

6、是非公平锁。

对于初始情况,synchronized会预测锁冲突的概率不大,此时以乐观锁的模式来运行(也就是轻量并且自旋的);如果后续锁冲突的概率加大,那么此时就会自适应的变为悲观锁(也就是重量挂起的)。

2 CAS

CAS就是compare和swap,也就是比较并且交换,那它比较的是什么呢?是内存和寄存器中的值。

CAS(M,A,B):比较M和A的值,如果相同,就把M和B的值交换,返回true;如果不同,就什么都不做,直接返回false。

这是CAS的伪代码:

CAS本质上是cpu提供的一个指令,是具有原子性的,之后又被操作系统封装,提供api,又被JVM封装,也提供api,最后供程序员使用。

既然CAS是具有原子性的,那么它也可以解决"线程安全"问题,从而在一些场景中代替加锁策略,基于CAS实现"线程安全"的编程称为无锁编程。这样做的好处是什么呢:不仅可以保证线程安全,同时比加锁的效率更高;但是,也存在一些缺点:1、代码会更复杂,不好理解 2、仅使用于一些场景,不如加锁普适性强。

2.1 CAS的关键问题:ABA问题

什么是ABA呢?当一个线程将数据A取出,经过一系列操作后把它变为B,但是最后又通过一系列操作将它变回A,那么对于另一个执行CAS操作的线程来说,可能会认为这个数据是没发生过变化的(虽然有的时候也不会出现bug),这种问题就是ABA问题。

此时,左侧的CAS中value的值不等于oldValue的值,就不执行任何操作,直接返回false。但是如果发生ABA问题,例如此时恰好有个人又给我转了500元,那么我的value又变回1000,等于oldValue,再次扣款,这就出现了很严重的bug,本来我只打算取500,但是现在取了1000!!

那又该怎么解决这个问题呢?

很简单,只需要让判定的数值按照一个方向变化即可(不要出现反复横跳,一会加一会减就可能会出现ABA问题)。在上述这个例子中,我们只需要将CAS的判定条件改为版本号,初识版本号为v1,每执行一次操作,版本号就会加1,那么,只要有其它线程穿插执行,版本号一定不等于old版本号,此时一定不会执行交换操作,而是会直接返回false,bug顺利解决。

3 信号量Semaphore

信号量是操作系统中比较重要的概念,其实信号量就是一个计数器,描述了"可用资源"的个数,每次申请一个可用资源,就要让信号量减1(P操作),每次释放一个操作,就要让信号量加1(V操作),那如果信号量已经为0的情况下继续申请资源(使用P操作)会发生什么呢?此时就会阻塞等待,直到其它线程释放一个资源(使用V操作),才能继续往下执行。

前面提到过的锁其实就是一种特殊的信号量,锁就是可用资源为1的信号量,所以一旦加锁,其它线程就会发生阻塞,因为加锁相当于使信号量减1,而此时信号量一共就是1,所以直到该线程释放锁,也就相当于执行V操作,其它线程才能获得锁

相关推荐
威哥爱编程7 分钟前
Nginx性能调优5招35式不可不知的策略实战
java·nginx·性能调优
五月阳光暖洋洋16 分钟前
SpringBoot2.2.6使用spring-boot-validation读取不到自定义配置文件中的属性
java·开发语言·spring boot
刘钢筋universe19 分钟前
leetcode hot100
java·算法·leetcode
java66666888820 分钟前
深入理解Spring Boot中的容器与依赖注入
java·spring boot·后端
u01040583628 分钟前
Spring Boot中的依赖注入和控制反转
java·spring boot·后端
猫猫爱吃小鱼粮31 分钟前
57、Flink 的项目配置概述
java·flink
龙洋静31 分钟前
RabbitMq - Java客户端基础【简单案例 +Work模型】
java·rabbitmq·java-rabbitmq
想要打 Acm 的小周同学呀31 分钟前
ThreadLocal学习
android·java·学习
Jeady539 分钟前
Gradle使用插件SonatypeUploader-v2.6上传到maven组件到远程中央仓库
java·maven
Naix157343 分钟前
Eclipse运行main函数报 launch error
java·ide·eclipse