JDK1.6对对synchronized的优化主要体现在引入了“偏向锁”和“轻量级锁”的概念,同时synchronized的锁只可升级,不可降级
一、偏向锁(Biased Locking)
- 优化场景:适用于只有一个线程反复进入同步代码块的情况。
- 工作机制:当一个线程第一次获得锁时,锁会偏向于该线程,记录下线程ID。之后该线程再访问这个锁时,不需要进行同步操作。
- 撤销条件:如果有其他线程尝试访问这个锁,偏向锁会被撤销,锁会升级为轻量级锁或重量级锁。
二、轻量级锁(Lightweight Locking)
- 优化场景:适用于多个线程进入同步代码块,但未发生竞争的情况。
- 工作机制:
- 当一个线程尝试获取锁时,如果锁是无竞争状态(即没有其他线程持有锁),则通过CAS操作将锁标记为已拥有状态。
- 如果锁已经被其他线程持有,则当前线程会进行自旋操作,通过不断地尝试CAS来获取锁,而不是立即阻塞。
- 自旋操作:自旋是一种忙等待的方式,线程在极短时间内反复尝试获取锁,有固定的自旋次数。如果自旋失败,则锁会升级为重量级锁。
- 优势:减少了线程切换和阻塞的开销,提高了性能。
三、重量级锁(Heavyweight Locking)
- 工作机制:当轻量级锁的自旋操作失败后,锁会升级为重量级锁,此时线程会被阻塞,直到锁被释放。
- 劣势:线程阻塞和唤醒的开销较大,性能较低。
总结
- 偏向锁:适用于单线程反复进入同步代码块的情况,避免了不必要的同步操作。
- 轻量级锁:适用于多线程进入同步代码块但未发生竞争的情况,通过CAS和自旋操作减少了锁的开销。
- 重量级锁:适用于多线程竞争激烈的情况,当自旋失败后,线程会被阻塞。
这些锁优化技术共同作用,旨在提高Java中synchronized
关键字的性能,使其在不同的并发场景下都能表现出较好的效率。
自旋操作是一种用于优化多线程锁竞争的技术,旨在减少线程阻塞和唤醒的开销。以下是对自旋操作及其在轻量级锁中的应用的详细解释:
四、自旋操作(Spin Locking)
(1)基本概念
- 忙等待:自旋操作是一种忙等待(busy-waiting)方式,即线程在等待获取锁时不会立即进入阻塞状态,而是不断地循环尝试获取锁。
- CAS操作:CAS(Compare-And-Swap)是一种原子操作,用于比较内存中的值是否与预期值相等,如果相等则更新为新值。自旋操作通常使用CAS来尝试获取锁。
(2)工作机制
- 尝试获取锁:当一个线程发现锁已经被其他线程持有时,它会进行自旋操作。具体来说,线程会反复执行CAS操作,尝试将锁的状态从“未持有”变为“已持有”。
- 固定自旋次数:自旋操作不会无限制地进行下去。为了避免浪费CPU资源,自旋操作通常会设置一个固定的自旋次数。如果在这段时间内仍然无法获取锁,自旋操作就会停止。
- 自旋失败后的处理:如果自旋操作在固定次数内没有成功获取锁,线程将放弃自旋,锁会升级为重量级锁,此时线程会被阻塞,直到锁被释放。
(3)优势和劣势
-
优势:
- 减少线程切换:自旋操作避免了线程在短时间内频繁地进入和退出阻塞状态,从而减少了线程切换的开销。
- 提高性能:在锁竞争不激烈且持锁时间较短的情况下,自旋操作可以显著提高性能,因为线程无需进入阻塞状态即可快速获取锁。
-
劣势:
- CPU消耗:自旋操作会占用CPU资源,因为线程在自旋期间一直在执行循环操作。如果锁竞争激烈或持锁时间较长,自旋操作可能会浪费大量的CPU资源。
- 适用场景有限:自旋操作适用于锁竞争不激烈且持锁时间较短的场景。在高竞争或长时间持锁的情况下,自旋操作的效果可能不如直接阻塞线程。
示例
假设有两个线程A和B,线程A已经持有某个锁,线程B尝试获取该锁:
- 线程B发现锁已被线程A持有,于是开始自旋操作。
- 线程B通过CAS操作不断尝试获取锁,如果线程A在短时间内释放了锁,线程B可以立即获取锁并继续执行。
- 如果线程A在固定的自旋次数内没有释放锁,线程B的自旋操作失败,锁升级为重量级锁,线程B进入阻塞状态,等待锁的释放。
总结
自旋操作通过忙等待和CAS操作,在一定条件下避免了线程阻塞,提高了锁的性能。然而,它也有局限性,需要根据具体的应用场景来决定是否采用这种优化