多线程进阶

目录

一.常见的锁策略

1.乐观锁和悲观锁

3.重量级锁和轻量级锁

4.自旋锁和等待挂起锁

5.公平锁和非公平锁

6.可重入锁和不可重入锁

二.CAS

1.CAS的应用

1.2.CAS中的ABA问题


一.常见的锁策略

1.乐观锁和悲观锁

 乐观锁:假设发生锁冲突概率比较小,基本上没有冲突的情况下,因此就直接尝试访问数据,直到出现了锁冲突后再简单地去处理问题.                                                                                                        悲观锁:假设发生锁冲突的概率比较高,悲观锁会付出更高的成本去处理问题,然后再去尝试访问数据.

2.读写锁

读和写比较容易存在线程安全问题.例如:                                                                                             1.多线程尝试修改同一个变量是线程不安全 的.因此两个写线程之间,就需要 互斥的锁.                     2.多个线程尝试读取同一个变量是线程安全的.因此两个写线程之间,就不需要互斥的锁.                   3.一个读线程一个写线程之间,其实也存在线程安全问题,也需要互斥.

ReentrantReadWriteLock.ReadLock类表示一个读锁,这个对象提供了lock/unlock方法进行加锁解锁.                                                                                                                                                       ReentranReadWriteLock.WriteLock类表示一个写锁,这个对象也提供了lock/unlock方法进行加锁解锁.

对应读比较多,写比较少的情景,使用读写锁,能够大大提高效率.(降低锁冲突的概率).锁一旦冲突,就会阻塞等待,等待时间是不确定的并且会影响程序的效率.这种情况还是比较常见的

3.重量级锁和轻量级锁

重量级锁:加锁解锁的开销很大,往往是通过内核来完成的.                                                                  轻量级锁:开锁解锁的开销更小,往往是通过用户态来完成的.                                                               这两个锁的使用跟应用场景没有什么关系,主要看加锁和解锁的开销大不大.

对比乐观锁和悲观锁.它们两个锁都是按照应用场景来使用,锁冲突的概率高不高来决定.一定主要区分.

4.自旋锁和等待挂起锁

自旋锁:线程在抢救失败后进入阻塞状态,放弃CPU,需要过很久才能再次被调度.但实际上,大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放.没必要就放弃CPU,这个时候就可以使用自旋锁来处理这样的问题.因此节省了操作系统调度线程的开销,要比挂起等待锁更能及时地获取到开锁.只不过相比之下更浪费cpu.                                                                                                                 如果获取锁失败,立即再尝试获取锁,无限循环,直到获取到锁为止.第一次失获取锁失败,第二次的尝试会在极短的时间内到来.一旦锁被其他线程释放就能第一时间获取锁.

挂起等待锁:如果线程获取不到锁,就会阻塞 等待.啥时候结束阻塞是不确定的,这个要取决于操作系统的调度,当线程挂起的时候,是不占用cpu的.

使用自旋锁和挂起锁的场景:                                                                                                                1.如果锁冲突的概率比较低,使用自旋锁比起挂起锁等待锁,更合适.                                                     2.如果线程持有锁的时间比较短,使用自旋锁也比挂起锁更合适.                                                        3.如果对CPU比较敏感,不希望吃太多的CPU资源,那么就使用挂起等待锁

5.公平锁和非公平锁

对于公平锁来说,遵循的是先来后到的原则.对于非公平锁来说,遵循的是抢到锁的概率是相同,完全是取决于系统的调度.

6.可重入锁和不可重入锁

针对同一把锁加锁两次,如果是可重入锁,则不会造成死锁,如果是不可重入锁,就会造成死锁.

二.CAS

CAS:全程Compare and swap,字母意思:比较并交换.compare是拿两个内存进行比较,或者拿寄存器的值与内存的值比较,而swap是交换两个内存的值.                                                                             我们假设内存中的原数据V,旧的预期值A,需要修改的新值B:                                                              1.比较A与V是否相等.(比较)                                                                                                                 2.如果比较相等,将B写入V.(交换)                                                                                                       3.返回操作是否成功.                                                                                                                         

CAS伪代码                                                                                                                                          下面写的代码不是原子的,真实的CAS是一个原子的硬件指令完成的,这个伪代码只是辅助理解CAS的工作流程

当多个线程同时对某一个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号.CAS可以视为是一种乐观锁(或者可以理解CAS是 乐观锁的一种实现方式)

1.CAS的应用

1.1实现原子类                                                                                                                                     例如之前的i++案例,多线程进行i++如果不加锁是线程不安全的,但是加锁操作是比较低效的.因此使用CAS就能够高效地完成自增,并且是线程安全的.

java的 CAS利用的是unsafe这个类提供的CAS操作.

例如:假设两个线程同时调用                                                                                                                 getAndcrement 

1.两个线程都读取value的值到oldValue中.(oldValue是一个局部变量,在 栈上,每个线程都有自己的栈)

 

2.线程1先执行CAS操作,由于oldValue和value的值相同,直接进行对value赋值

 

3.线程2再执行CAS操作,第一次CAS的时候发现oldValue和value不相等,不能进行赋值.因此需要进入循环,在循环里重写读取value的值赋给oldvalue 

 

4.线程2接下来第二次执行CAS,此时oldValue和value相同,于是直接执行赋值操作

通过形如上述代码就可以实现一个原子类.不需要使用重量级锁,就可以高效的完成多线程的自增操作

CAS其实就是在感知 这两个操作之间是否夹杂去其他线程操作:是否有其他线程在 这个过程中篡改了数据.如果没有线程修改,此时就把数据给改了;如果数据被其他线程已经修改过了,那就重新读取旧值,交给下次循环来判定

1.2.CAS中的ABA问题

ABA问题指的是:

假设存在两个线程t1和t2有一个共享变量num,初始值为A.线程t1想使用CAS把num值改成Z,那么需要先读取num的值,记录到oldNum变量中.2使用CAS判定当前num的值是否为A,如果为A就修改成Z

但是,在t1执行这两个操作之间,t2线程可能把num的值从A改成B,又从B改成了A.线程t1的CAS是期望num不变就修改,但是num的值已经被t2给改了.只不过又改成A了.这个时候t1究竟是否更新num的值呢?

举个例子                                                                                                                                             假设A去银行取钱,存款为100.例如A要取50,,线程1获取到当前存款为100,期望更新为50:线程2获取到当前存款值为100,期望更新为50.线程1执行扣款成功,存款被改为50.线程2阻塞等待中.在执行线程2之前,A的朋友正好转给A50,账户余额变为100.轮到线程2执行了,发现当前存款为100和之前读的100相同,再次执行扣款操作.  

注意:存钱不会涉及到CAS操作,取钱才会涉及CAS操作.

其实出现ABA的概率是比较低的,但是导致的结果很严重,并且如果人的基数很大,那么即使是小概率,会发现ABA问题的人也很多.

解决方法:引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期.

1.CAS操作在读取旧值的同时,也要读取版本号                                                                                   2.修改的时候,如果当前版本号和读到的版本号相同,则修改数据,并把版本号+1.如果当前版本号高于读到的版本号,就操作失败(认为数据已经被修改过了)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值