3.9多线程

一.常见锁策略

1.悲观锁 vs乐观锁

体现在处理锁冲突的态度

①悲观锁:预期锁冲突的概率高

所以做的工作更多,付出的成本更多,更低效

②乐观锁:预期锁冲突的概率低

所以做的工作少,付出的成本更低,更搞笑

2.读写锁 vs 普通的互斥锁

①普通的互斥锁,只有两个操作 加锁和解锁

只有两个线程针对同一个对象加锁,就会产生互斥

②读写锁:有三个操作

针对读锁和读锁之间,是不存在互斥的关系--->因为多线程同时读一个变量,不会有线程安全的问题

读锁和写锁之间,写锁和写锁才需要互斥

而且很多场景中,都是读多写少

3.重量级锁 vs 轻量级锁

是为了处理锁冲突的结果

重量级锁 做了更多的事情,开销大,轻量级锁反之亦然

大多数情况下,悲观锁一般都是重量级锁,乐观锁一般都是轻量级锁

比如: 在使用的锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口).一般认为这是重量级锁

如果锁是纯纯用户态实现的,一般认为这是轻量级锁,因为用户态的代码更可靠,也更高效

4.挂起等待锁 vs 自旋锁

①挂起等待锁:通过内核的一些机制来实现的.往往比较中 [重量级锁的一种典型实现]

②自旋锁 : 往往是通过用户态代码来实现的,往往比较轻,[轻量级锁的一种典型实现]\

5.公平锁 vs 非公平锁

公平锁:多个线程在等待一把锁的时候谁是先来的,谁就能先获取到这个锁(遵守先来后到

非公平锁:多个线程在等待一把锁的时候不遵守先来后到,每个等待的线程获取到锁的概率是均等的

对于操作系统来说->线程之间的调度是随机的(机会是均等的) .操作系统提供的mutex这个锁.就是属于非公平锁

6.可重入锁 vs 不可重入锁

可以嵌套加锁,就是可重入锁,会死锁的就是不可重入锁

7.synchronized(面试

1)即是一个乐观锁.也是一个悲观锁(根据锁竞争的激烈程度,自适应)

2)不是读写锁,只是一个普通互斥锁

3)既是一个轻量级锁,也是一个重量级锁,(根据锁竞争的激烈程度,自适应)

4)非公平锁 5)可重入锁

二.CAS

compare(比较) and swap(交换)

1.定义

CAS锁:拿着寄存器或者/某个内存的值和另外一个内存的值进行比较,如果值相同了,就交换

3.9多线程_第1张图片

伪代码

3.9多线程_第2张图片

CAS:提供一个单纯的CAS指令,通过这一条指令,就完成上述伪代码描述的过程

CAS指令:如果上述伪代码的过程只用一条做完,就是原子性,这个时候就是线程安全了

2.作用

1)基于CAS能够实现原子类

(java标准库里提供了一组原子性,针对常用的int.long,,int[]进行了封装,基于cas方式修改,并且线程安全

3.9多线程_第3张图片

这段代码是不存线程安全的问题

基于CAS实现的++操作

因为这里既能保证线程安全,又能比synchronized高效

synchronized会涉及到锁的竞争,又要涉及线程的阻塞等待

3.9多线程_第4张图片

这里的oldvalue变量,实际实际上可能使用一个寄存器来存的

这个赋值操作就相当于把数据从内存读到寄存器中(load

判定一下当前内存的值是不是和刚刚寄存器取到的值一直

如果判定成功,那就把value设为oldvalue+1.并返回true 循环结束

如果判定失败,继续下次循环,返回true;下次循环还是要先读一下value

这两行代码之间,很有可能有其他线程修改了value的值

3.9多线程_第5张图片

为什么上述++操作线程安全

load

cas

3.9多线程_第6张图片
3.9多线程_第7张图片

2).基于CAS实现自旋锁

自旋锁是一个轻量级锁,也可以视为一个乐观锁

这把锁虽然没能够立即拿到,预期很快就能拿到(假设锁冲突不激烈)

3.ABA问题

1)定义

CAS中的关键就是先比较再交换

比较其实在比较当前值和旧值是否相同

把这两个值相同,就视为中间没有发生改变

但是这句话有漏洞,可能当前值和旧值相同的情况是因为变了然后又变回来了, 这就是ABA

2.案例

举例:ABA产生的BUG

假设小红账户余额 100 他想要取50

当按下取钱操作的时候,机器卡了一下,小红一紧张多按了一下取款

这就相当于 一次取钱操作 执行了两遍 (两个线程,并发的去执行这个取钱操作)

预期是只能取成功一次

如果基于CAS来实现的话

3.9多线程_第8张图片

本来应该没问题

第二个线程再比较的时候就会发现,读到的100和原本的50不一样,就会不操作了

但是在取款的一瞬间,小红额度朋友给他转账了50.这个时候就会触发的ABA问题

3.9多线程_第9张图片

这个时候第二个线程就会发现还是100.于是又扣了50

3.9多线程_第10张图片

3)解决方法

引入版本号

这个版本号只能变大,修改变量的时候,比较的不是变量而是比较版本号

3.9多线程_第11张图片
3.9多线程_第12张图片

当引入版本号以后,t2再次尝试进行这里的比较版本操作

就会发现版本的旧值和当前的值并不匹配,就直接放弃修改

或者加入时间戳

看当前读到的内存改变的时间戳有没有变化

你可能感兴趣的:(java,线程)