Java自旋锁和JVM对锁的优化详解
在多线程并发编程中,锁的使用尤为重要。Java中的锁主要包括synchronized关键字和ReentrantLock类,这些锁在实现上都涉及到了自旋锁和JVM对锁的优化。
什么是自旋锁
自旋锁是指当一个线程获得锁后,发现其他线程正在使用该锁,则该线程不会立即阻塞,而是一直循环等待直到其他线程释放该锁。
在Java中,synchronized关键字就是一种典型的自旋锁。当一个线程进入synchronized代码块时,如果发现锁已经被其他线程占用,则该线程会不断自旋等待直到获取到锁。
在单核处理器上,自旋锁的效率是比较高的,因为线程的上下文切换是比较耗时的。但在多核处理器上,自旋锁的效率会降低,因为自旋的线程会竞争CPU资源,导致CPU利用率降低。
JVM对锁的优化
JVM在实现锁时,有一些优化策略可以提高线程的并发能力。
偏向锁
偏向锁是指当一个线程第一次进入同步块时,JVM会将锁定对象头记录下来,并将线程ID记录到锁对象中,表示该线程已经获得了锁。
当这个线程再次进入同步块时,JVM会检查锁定对象头中记录的线程ID是否是当前线程。如果是,则表示该线程仍然持有锁,就可以直接进入同步块执行代码。如果不是,则需要撤销偏向锁,升级为轻量级锁。
偏向锁的优势在于减少了锁的竞争,提高了同步代码的执行效率。
轻量级锁
轻量级锁是指当两个线程同时进入同步块时,JVM会先将锁的对象头记录下来,并将对象头中的标志位设置为“轻量级锁”。然后,JVM会将锁定对象的Mark Word复制一份到线程的栈帧中,以便线程在解锁时恢复锁定对象的状态。
当第二个线程进入同步块时,JVM会继续检查锁的对象头中的标志位,并比较线程栈帧中的锁对象是否与Mark Word一致。如果一致,则表示当前线程持有锁,可以直接执行同步块的代码。如果不一致,则需要撤销轻量级锁,升级为重量级锁。
重量级锁
重量级锁是指当多个线程竞争同步块时,JVM会把它们全部阻塞,并将它们对锁的访问转为内核级别的操作。这时,CPU会从用户态切换到内核态,导致用户线程的阻塞和解阻塞都需要切换状态,造成较大的系统开销。
重量级锁的优势在于不会消耗CPU资源,因为线程会被阻塞,不会自旋等待。
自旋锁和JVM优化的示例
示例1:偏向锁
public class BiasedLockExample {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
synchronized (object) {
Thread.sleep(5000); // 睡眠5秒钟,模拟该线程正在占用锁
System.out.println("当前线程:" + Thread.currentThread().getId());
}
}
}
在代码中,我们使用synchronized锁定了一个对象,并在同步块中睡眠了5秒钟,模拟该线程正在占用锁。
然后,我们启动两个线程,其中一个线程首先获取到了锁,然后打印当前线程的ID。另外一个线程在获取锁时,因为该锁已经被偏向了第一个线程,所以直接进入了同步块,输出了第一个线程的ID。
输出结果如下:
当前线程:1
当前线程:1
从结果中可以看出,第二个线程直接获得了锁,并没有阻塞。
示例2:轻量级锁
public class LightLockExample {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (object) {
try {
Thread.sleep(5000); // 睡眠5秒钟,模拟该线程正在占用锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1正在执行");
}
});
Thread t2 = new Thread(() -> {
synchronized (object) {
System.out.println("线程2正在执行");
}
});
t1.start();
Thread.sleep(1000); // 等待线程1先获取到锁
t2.start();
}
}
在代码中,我们启动两个线程,其中第一个线程获取到了锁后,又睡眠了5秒钟,模拟该线程正在占用锁。
然后,我们启动第二个线程,该线程尝试获取锁,由于锁已经被第一个线程获取了,因此该线程会进入自旋等待。
等第一个线程结束后,第二个线程才能获取到锁,并输出“线程2正在执行”信息。
输出结果如下:
线程1正在执行
线程2正在执行
从结果中可以看出,第二个线程在获取锁时,没有直接进入同步块,而是进入了自旋等待状态。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java自旋锁和JVM对锁的优化详解 - Python技术站