悲观锁和乐观锁

什么是悲观锁和乐观锁?都有哪些具体的实现?

悲观锁:

他默认每次对共享资源访问都可能引发冲突(例如数据被意外修改).

Java 中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。【互斥】

在高并发环境下,如果多个线程频繁争抢锁资源,会导致大量线程被阻塞。这些被阻塞的线程会引发频繁的线程上下文切换,从而增加系统的额外性能消耗。此外,悲观锁还可能引发死锁问题,使程序无法正常执行,进一步影响系统稳定性。

乐观锁:

它是一种乐观的并发策略,它默认多个线程在访问共享资源的时候不会发生冲突,不会造成堵塞线程执行,也无需等待.

核心思想是:线程在修改数据的时候,先检查该数据是否被其他线程修改过,如果没有则提交更新,否则重试或者放弃操作.

CAS:

​CAS操作包含三个操作数(CAS算法实现):​

1.变量的真实内存值(V)​
2.预期值(A)​
3.新值(B)​

​工作原理:​

1.比较:首先比较内存值V是否与预期值A相等​
2.交换:如果相等,处理器会自动将该位置值更新为新值B​

CAS的伪代码表示:

CAS是一种无锁的并发并发控制技术,通过硬件级原子操作来保证多线程环境下数据更新安全,

在更新某个变量的值之前,先判断该变量当前的值是否与我之前读取的值一致(无其他线程修改),如果一致则更新为新值;如果不一致则放弃更新,返回失败

什么是 ABA 问题?如何解决?

ABA问题就是在变量V初始读取A,后续CAS操作时仍为A,但这不能在保证期间违背修改过,因为可能存在这样的时序:V被改为B后又改回A,导致CAS误判变量未被改动。这种中间状态被隐藏的情况就是典型的ABA问题.

解决:解决ABA问题的常见方案是为变量添加版本号。Java在1.5版本后提供的AtomicStampedReference类专门处理这类问题,其核心机制是:在执行compareAndSet操作时,不仅会比较变量的当前值,还会验证附加的版本标记,只有两者都符合预期才会执行更新操作。这种设计通过引入状态标记来识别中间变化,有效避免了传统CAS操作可能出现的误判情况。

JMM-Java内存模型:

JMM 即 Java Memory Model,它定义了主存、本地内存的抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。

存在的问题:

CPU的处理速度极快,而内存访问相对较慢(大约相差100倍左右)。

导致了一个现象:

当CPU需要读取主存中的变量时,它不得不停下来等待​
性能瓶颈:CPU宝贵的计算能力被浪费在等待内存数据上

JDK 1.2 之后,线程可以把主存中的变量保存到自己的本地内存(每个线程私有的内存),而不是直接在主存中进行读写,就不需要将时间浪费在等待内存数据上,每次只需要从本地内存中读取到对应的变量值即可。​
这种本地内存和主内存模型的设计,就是 JMM​.
 

什么是主内存?什么是本地内存?

1.主存:所有线程创建的实例对象都存放在主内存中​
2.本地内存:每个线程都有一个私有的本地内存,本地内存存储了该线程可以操作的共享变量的副本。每个线程只能操作自己本地内存中的变量,无法直接访问其他线程的本地内存。本地内存是
JMM 抽象出来的一个概念,并不真实存在,它涵盖了CPU高速缓存、寄存器以及其他的硬件和编译器优化。​

JDK1.2 之前和之后的内存模型有什么区别?分别有什么缺点?

在JDK1.2之前,java的内存模型都是从主存中读取变量CPU需要读取主存中的变量时,它不得不停下来等待,性能瓶颈:CPU宝贵的计算能力被浪费在等待内存数据上.

在JDK1.2之后,线程可以把主存中的变量保存到自己本地内存(每个线程私有的内存),而不是直接的在主存中续写,就不需要将时间浪费在等待内存数据上,每次只需要从本地内存中读取到对应的变量即可,这种本地内存和主内存模型的设计就是JMM.

什么是可见性问题,如何解决可见性问题:

可见性问题.如果一个线程对一个共性变量进行了修改,而其他线程不能及时读到修改后的值.

解决方法:给对应的变量加上volatile(可以用来修饰成员变量和静态成员变量),线程只要检测到volatile修饰变量的值被修改,就会重新从主内存进行加载。

什么是指令重排?多线程情况下指令重排有什么问题?

JVM 会在不影响结果正确性的前提下,可以调整语句的执行顺序,

static int i; ​
static int j; ​
// 在某个线程内执行如下赋值操作 ​
i = 1;  ​
j = 2;  ​
​
可以看到,至于是先执行 i 还是 先执行 j ,对最终的结果不会产生影响。​
所以,上面代码真正执行时,既可以是 ​
​
i = 1;  ​
j = 2; ​
​
也可以是​
​
j = 2; ​
i = 1; ​
​

这种特性称之为指令重排,多线程下的指令重排会产生线程安全问题

I_Result 是一个对象,有一个属性 r1 用来保存结果,问可能的结果有几种? ​
正常情况​

情况1:线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1 ​

情况2:线程2 先执行 num = 2,但没来得及执行 ready = true,线程1 执行,还是进入 else 分支,结果为 1 ​

情况3:线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num = 2 已经执行过了) ​
意外情况(actor2方法中,ready = true被指令重排到 num = 2前面啦 ):​

情况4:线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2,结果为 0

什么是有序性问题?怎么能解决有序性问题?

有序性问题:由于指令重排导致真实结果和我们的预期结果不一致

解决方法:使用 volatile 修饰的变量,可以禁用指令重排

Volatile如今保证可见性和有序性问题?能保证原子性吗

  • 保证可见性volatile 变量的写操作会立即刷新到主内存,读操作会从主内存中读取最新值,这样就保证了不同线程对该变量的可见性。
  • 保证有序性volatile 关键字通过内存屏障禁止了指令重排,从而保证了变量操作的有序性。
  • 原子性volatile 不能保证原子性。例如,对 volatile 修饰的变量进行自增(i++)操作,这是一个复合操作(读取 - 修改 - 写入),在多线程环境下,可能会出现线程安全问题,无法保证操作的原子性。
  • volatile不能解决指令交错问题,所以不能保证原子性

 volatile 和 synchronized 有什么区别?


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值