1.为什么需要悲观锁和乐观锁
2.乐观锁比悲观锁更节省资源,那为什么要用悲观锁?
3.悲观锁为什么要锁升级?
4.悲观锁什么时候升级,如何升级?
======
回头更新!
看看gpt的回答:
-
为什么需要悲观锁和乐观锁
悲观锁:假设并发冲突是常态,每次访问共享资源时都会加锁,以防止数据被其他线程修改。这种方式适用于高冲突场景,保证了数据的一致性。
乐观锁:假设并发冲突是偶然的,线程在操作共享资源时不会加锁,而是在提交数据时检查是否发生了冲突。如果发生冲突,则可以重试。这种方式适用于低冲突场景,通常性能较好。 -
乐观锁比悲观锁更节省资源,那为什么要用悲观锁?
在高竞争场景下,多个线程频繁访问同一共享资源,乐观锁可能会频繁出现冲突,导致重试次数增加,从而降低性能。在这种情况下,悲观锁可以确保数据的一致性,避免不断的重试。
在某些情况下,悲观锁可以有效避免脏读(即读取到未提交的数据)。乐观锁在检查冲突时只能在提交时进行,因此可能存在读到不一致数据的风险。 -
悲观锁为什么要锁升级?
锁升级是为了在特定情况下提高系统的性能和资源利用率。例如,当一个线程在持有共享资源的情况下,发现后续操作不会有其他线程访问该资源,就可以将轻量级锁升级为重量级锁,以减少上下文切换和提升并发性能。 -
悲观锁什么时候升级,如何升级?
何时升级:一般在某个线程持有锁的时间较长,或者频繁出现锁竞争(如多个线程争抢同一资源)时,可以考虑锁升级。
如何升级:实现方式通常是通过将当前锁状态标记为“重量级”,并将其他线程的请求排队,等待锁释放。具体实现依赖于使用的锁机制(如Java的ReentrantLock等)。
我的回答:
对于悲观锁为什么要锁升级:
在 synchronized 最初的实现方式(JDK6以前) “阻塞或唤醒一个Java线程需要操作系统切换系统状态来完成,这种状态切换需要耗费处理器时间;并且每次获取和释放锁的操作都涉及系统调用,开销较大。如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”。在JDK 1.6后,为了提高锁的获取与释放效率对(synchronized )进行了优化,,以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程。再具体来说,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为重量级锁。
锁升级的时机?
无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程ID也在对象头里),如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(关于自旋的介绍见文末)的形式尝试获取锁,线程不会阻塞,从而提高性能。
轻量级锁的获取主要由两种情况:
① 当关闭偏向锁功能时;
② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。
重量级锁显然,此忙等是有限度的(有个计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是CAS修改锁标志位,但不修改持有锁的线程ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。