文章目录
1. 轻量级锁的核心原理
为什么复制对象头的部分信息到线程堆栈中的锁记录的Displaced Mark Word字段?
因为内置锁对象的Mark Word的结构会有所变化,Mark Word将会出现一个指向锁记录的指针,而不再存着无锁状态下的锁对象哈希码等信息,所以必须将这些信息暂存起来,供后面在锁释放时使用。
2. 代码演示
package innerlock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class InnerLockTest {
int a=1;
double b=1.1;
public static void main(String[] args) throws InterruptedException {
System.out.println(VM.current().details());
Object obj=new Object();
new Thread(()->{
synchronized (obj) {
System.out.println("t1:"+ClassLayout.parseInstance(obj).toPrintable());
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
Thread.sleep(1000);//保证t1已经离开临界区
new Thread(()->{
synchronized (obj) {
System.out.println("t2:"+ClassLayout.parseInstance(obj).toPrintable());
}
},"t2").start();
}
}
上面的代码创建了两个线程,第一个线程t1获取到锁时无线程争夺资源,因此是偏向锁;当第二个线程去争夺t1持有的锁对象时,变成轻量级锁
轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。(比如上面代码,先是线程1进行加锁变成偏向锁,然后是线程2来抢占,此时线程1已经离开临界区)
3. 轻量级锁的分类
1. 普通自旋锁
当有线程来竞争锁时,抢锁线程会在原地循环等待,而不是被阻塞,直到那个占有锁的线程释放锁之后,这个抢锁线程才可以获得锁。
自旋会消耗CPU,因此不可能无休止地进行自旋,默认情况下,自旋的次数为10次,用户可以通过-XX:PreBlockSpin选项来进行更改。
2. 自适应自旋锁
- 如果抢锁线程在同一个锁对象上之前成功获得过锁,JVM就会认为这次自旋很有可能再次成功,因此允许自旋等待持续相对更长的时间
- 如果对于某个锁,抢锁线程很少成功获得过,那么JVM将可能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源
4. 轻量级锁的膨胀
轻量级锁的优点:在多线程竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁产生的性能损耗。重量级锁使用了操作系统底层的互斥锁(MutexLock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗
轻量级锁的缺点:临界区代码执行耗时较长,在其执行期间,其他线程都在原地自旋等待,会空消耗CPU。因此,如果竞争这个同步锁的线程很多,就会有多个线程在原地等待继续空循环消耗CPU(空自旋),这会带来很大的性能损耗
在争用激烈的场景下,轻量级锁会膨胀为基于操作系统内核互斥锁实现的重量级锁
参考:《Java高并发编程卷2》 尼恩