多线程-进阶

目录

1.常见的锁策略

1.1 乐观锁VS悲观锁

1.2 重量级锁VS轻量级锁

1.3 自旋锁VS挂起等待锁

1.4 读写锁

1.5 可重入锁VS不可重入锁

1.6 公平锁VS非公平锁

1.7 sychronized

2.CAS

2.1 概念

2.2 特点

2.3 应用

2.4 CAS的ABA问题

2.5 面试题

3.synchronized原理

3.1 锁升级

3.2 锁消除

3.3 锁粗化

4.JUC的常见类

4.1 Callable接口

4.2 ReentrantLock

4.2.1 ReentrantLock的⽤法:

4.2.2 特点

4.2.3 ReentrantLock与synchronized的对比

4.2.4 选择

5.信号量Semaphore


1.常见的锁策略

1.1 乐观锁VS悲观锁

乐观和悲观是“锁的一种特性”,它是一类锁,而不是一把具体的锁。

悲观乐观,是对后续锁冲突是否激烈(频繁)给出的预测:

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

如果预测接下来所冲突的概率很大,就可以多做一些工作,就称为 悲观锁

1.2 重量级锁VS轻量级锁

轻量级锁,锁的开销比较小 - 乐观锁

重量级锁,锁的开销比较大 - 悲观锁

1.3 自旋锁VS挂起等待锁

自旋锁,就属于是一种轻量级锁的典型实现(往往是在纯用户态实现)

忙等,消耗cpu但是换来更快的响应速度。如果获取锁失败,⽴即再尝试获取锁,⽆限循环,直到获取到锁为⽌.第⼀次获取锁失败,第⼆次的尝试会在极短的时间内到来.

⾃旋锁是⼀种典型的轻量级锁的实现⽅式.

• 优点:没有放弃CPU,不涉及线程阻塞和调度,⼀旦锁被释放,就能第⼀时间获取到锁.

• 缺点:如果锁被其他线程持有的时间⽐较久,那么就会持续的消耗CPU资源.(⽽挂起等待的时候是不消耗CPU的).

挂起等待锁,就属于一种重量级锁的典型实现(要借助系统api来实现)

一旦出现锁竞争了,就会在内核中触发一系列的动作(比如让这个线程进入阻塞的状态,暂时不参与cpu调度)

1.4 读写锁

把加锁分为了两种 - 读锁和写锁

读加锁,读的时候,可以读,但是不能写;写加锁,写的时候,不能读,也不能写

两个线程加锁过程中:

1.读锁和读锁之间,不会有竞争       多线程读取同一个数据,没有线程安全问题的

2.读锁和写锁之间,有竞争

3.写锁和写锁之间,也有竞争

1.5 可重入锁VS不可重入锁

一个线程对同一把锁,连续加锁两次,不会死锁,就是可重入锁,会死锁,就是不可重入锁。

1.6 公平锁VS非公平锁

当很多线程去加一把锁的时候,一个线程能够拿到锁,其他线程阻塞等待。一旦第一个线程释放锁之后,接下来是哪个线程可以拿到锁呢?

公平锁:”先来后到“顺序

非公平锁:”均等“的概率,重新竞争锁

操作系统提供的加锁api是默认”非公平锁“

1.7 sychronized

对于”悲观乐观“ 、”轻量重量“、”自旋-挂起等待“都是自适应的

不是读写锁、是可重入锁、是非公平锁

2.CAS

2.1 概念

CAS: 全称Compare and swap,字⾯意思:”⽐较并交换“,

比如有一个内存,M  现在还有两个寄存器A,B    即CAS(M,A,B)

如果M和A的值相同的话,就把M和B里的值进行交换,同时整个操作返回true (交换的本质是为了把B赋值给M)如果M和A的值不同的话,无事发生,整个操作返回false

(一个cpu指令,就能完成上述交换的逻辑)

单个的cpu指令,是原子的!!就可以使用CAS完成一些操作,进一步的替换”加锁“

给线程安全的代码,引入了新的思路  - 基于CAS实现线程安全的方式,也称为”无锁编程“

2.2 特点

优点:保证线程安全,同时避免阻塞(效率)

缺点:a.代码会更复杂,不好理解

           b.只能适合一些特定场景,不如加锁方式更普适

2.3 应用

a.实现原子类

例如,int进行++就不是原子的,分为三步走(load,add,save)

AtomicInteger,基于CAS的方式对int进行封装了,此时进行++,就是原子的了。

b.实现自旋锁

2.4 CAS的ABA问题

CAS也是多线程编程的一种重要技巧,虽然开发中直接使用CAS的概率不大,但是经常会用到一些内部封装 了CAS的操作

CAS这块还有一个关键问题:ABA问题

CAS进行操作的关键,是通过 值 ”没有发生变化“来作为”没有其他线程穿插执行“判定依据

但是这个判定方式,不够严谨

更极端的情况下,可能有另一个线程穿插进来,把值A->B->A

针对第一个线程来说,看起来好像是这个值没变,但是实际上已经被穿插执行了

ABA问题通常是不会有bug的,但是极端情况下不好说

总结:在实际开发中,一般不会直接使用CAS,都是封装好的。

面试中比较容易考到CAS,一旦考察到CAS,一定涉及到ABA问题

解决方案:

给要修改的值,引⼊版本号.在CAS⽐较数据当前值和旧值的同时,也要⽐较版本号是否符合预期.

• CAS操作在读取旧值的同时,也要读取版本号.

• 真正修改的时候,

◦ 如果当前版本号和读到的版本号相同,则修改数据,并把版本号+1.

◦ 如果当前版本号⾼于读到的版本号.就操作失败(认为数据已经被修改过了).

2.5 面试题

a.讲解下你⾃⼰理解的CAS机制

全称Compare and swap,即"⽐较并交换".相当于通过⼀个原⼦的操作,同时完成"读取内存,⽐较是否相等,修改内存"这三个步骤.本质上需要CPU指令的⽀撑.

b. ABA问题怎么解决?

给要修改的数据引⼊版本号.在CAS⽐较数据当前值和旧值的同时,也要⽐较版本号是否符合预期.如果发现当前版本号和之前读到的版本号⼀致,就真正执⾏修改操作,并让版本号⾃增;如果发现当前版本号⽐之前读到的版本号⼤,就认为操作失败.

3.synchronized原理

synchronized几个重要的机制:a.锁升级 b.锁消除 c.锁粗化

3.1 锁升级

偏向锁,不是真正意义的锁,只是做了一个标记,能不加就不加,实际上是一个懒汉模式。

什么是偏向锁?

偏向锁不是真的加锁,⽽只是在锁的对象头中记录⼀个标记(记录该锁所属的线程).如果没有其他线程参与竞争锁,那么就不会真正执⾏加锁操作,从⽽降低程序开销.⼀旦真的涉及到其他的线程竞争,再取消偏向锁状态,进⼊轻量级锁状态.

3.2 锁消除

编译器的一种优化手段

编译器会自动针对你当前写的代码,做出判定,如果编译器觉得这个场景不需要加锁,此时就会把你写的synchronized给优化掉。

3.3 锁粗化

⼀段逻辑中如果出现多次加锁解锁,编译器+JVM会⾃动进⾏锁的粗化。

synchronized里头,代码越多,就认为锁的粒度越粗,代码越少,就认为锁的粒度越细。

4.JUC的常见类

4.1 Callable接口

是创建线程的一种方式,适合于,想让某个线程执行一个逻辑,并返回结果的时候

4.2 ReentrantLock

可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果,保证线程安全

4.2.1 ReentrantLock的⽤法:

lock():加锁,如果获取不到锁就死等.

trylock(超时时间):加锁,如果获取不到锁,等待⼀定的时间之后就放弃加锁.

unlock():解锁

4.2.2 特点

优势:

1.ReentrantLock,在加锁的时候,有两种方式:lock, trylock,给了咱们更多的操作空间

2.ReentrantLock,提供了公平锁的实现(默认情况下是非公平锁)

3.ReentrantLock,提供了更强大的等待通知机制,搭配了Condition类,实现等待通知的。

虽然ReentrantLock有上述优势,我们在加锁的时候,还是首选synchronized,因为ReentrantLock使用更复杂,尤其容易忘记解锁,另外synchronized还有一系列优化机制。

4.2.3 ReentrantLock与synchronized的对比

• synchronized是⼀个关键字,是JVM内部实现的(⼤概率是基于C++实现).ReentrantLock是标准库的⼀个类,在JVM外实现的(基于Java实现).

• synchronized使⽤时不需要⼿动释放锁.ReentrantLock使⽤时需要⼿动释放.使⽤起来更灵活,但是也容易遗漏unlock.

• synchronized在申请锁失败时,会死等.ReentrantLock可以通过trylock的⽅式等待⼀段时间就放弃.

• synchronized是⾮公平锁,ReentrantLock默认是⾮公平锁.可以通过构造⽅法传⼊⼀个true开启公平锁模式.

4.2.4 选择

如何选择使⽤哪个锁?

• 锁竞争不激烈的时候,使⽤synchronized,效率更⾼,⾃动释放更⽅便.

• 锁竞争激烈的时候,使⽤ReentrantLock,搭配trylock更灵活控制加锁的⾏为,⽽不是死等.

• 如果需要使⽤公平锁,使⽤ReentrantLock.

5.信号量Semaphore

用来表述”可用资源的个数“,本质上是一个计数器。

每次申请一个可用资源,就需要让计数器-1  P操作  acquire

每次释放一个可用资源,就需要让计数器+1  V操作  release

锁 本质上就属于一种特殊的信号量

锁就是可用资源为1的信号量。

加锁操作,P操作,1->0;

解锁操作,V操作,0->1;

开发中如果遇到了需要申请资源的场景,就可以使用信号量来实现了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值