偏斜锁
功能
1.消除无竞争情况下的同步原语.
2.提高执行性能.
实现方式
在对象头中记录线程 ID,首次进入锁代码时,直接将对象头中的线程 ID 设置为当前线程,之后只需要进行 CAS 操作即可完成加锁和解锁操作,避免了无竞争情况下的互斥同步操作.
具体实现
当线程进入同步块时,如果该同步对象未被锁住,则认为该线程已经获得了锁,将对象头 Mark Word 拷贝到线程栈帧的锁记录中,并将对象头中的 Mark Word 修改为可偏向状态。之后线程执行完毕后,依然会检查是否存在竞争,如果没有竞争,就将锁恢复回偏向状态。当对象处于偏向状态时,其他线程需要获取该对象的锁时,只需要判断对象头的 Mark Word 是否是可偏向状态,以及其记录的线程 ID 是否和当前线程 ID 是否相等即可.
示例代码
下面代码中,首先创建了一个 ReentrantLock
对象。由于 ReentrantLock
默认开启偏向锁,因此第一个线程加锁时会启用偏向锁。之后第二个线程也加锁,同时会启用偏向锁撤销机制,即偏向锁的状态会变为可偏向状态。当第一个线程解锁时,系统会启动偏向锁撤销机制,并将锁的状态变为轻量级锁。最后两个线程都解锁,此时偏向锁最终被撤销。
public class BiasedLockExample {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 偏向锁默认是开启的,可以通过以下参数来关闭偏向锁
// -XX:-UseBiasedLocking
// 设置延迟时间,让 JVM 充分运行,获取到尽量多的锁
// -XX:BiasedLockingStartupDelay=0
lock.lock(); // 第一个线程加锁(偏向锁)
System.out.println("Locked by thread1");
printLockInfo(lock);
lock.lock(); // 第二个线程加锁,同时启动了偏向锁撤销
System.out.println("Locked by thread2");
printLockInfo(lock);
lock.unlock(); // 第一个线程解锁,启动偏向锁撤销
System.out.println("Unlocked by thread1");
printLockInfo(lock);
lock.unlock(); // 第二个线程解锁,偏向锁最终被撤销
System.out.println("Unlocked by thread2");
printLockInfo(lock);
}
private static void printLockInfo(Lock lock) {
ReentrantLock reentrantLock = (ReentrantLock) lock;
// ReentrantLock 会在对象头记录锁状态信息(包括偏向锁、轻量级锁、重量级锁等)
// 可以通过 ReentrantLock 的 getState() 方法获取当前锁状态
System.out.println("Lock state: " + reentrantLock.getState());
// 可以通过 ReentrantLock 的 isFair() 方法判断当前锁是否是公平锁
System.out.println("Is fair: " + reentrantLock.isFair());
}
}
轻量级锁
功能
避免多个线程同时访问同一内存地址.
实现方式
让线程通过 CAS 操作将对象头中的 Mark Word 修改成锁记录指针,如果此时有竞争出现(即多个线程访问同一个内存地址),则当前线程通过自旋等待其他线程释放锁.
具体实现
当第一个线程进入同步块时,虚拟机会使用 CAS 操作将对象头中的 Mark Word 替换为其所在线程栈中的锁记录指针。如果替换成功证明当前线程获取到了锁,并且可以继续执行同步块中的代码。如果替换失败,则表示存在竞争,虚拟机会在同步状态处于轻量级锁状态下自旋等待其他线程释放锁,如果自旋等待结束后仍未获取到锁,则锁会升级至重量级锁。
示例代码
下面代码中,首先创建了一个 ReentrantLock
对象。之后创建了两个线程 A 和 B,分别尝试获取锁。由于没有其他竞争线程,因此两个线程都可以成功获取锁,并且启用轻量级锁机制。当两个线程都成功获取到锁之后,分别解锁并输出锁状态信息。
public class LightweightLockExample {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(() -> {
lock.lock(); // 线程A加锁(轻量级锁)
System.out.println("Locked by thread A");
printLockInfo(lock);
lock.unlock(); // 线程A解锁
System.out.println("Unlocked by thread A");
printLockInfo(lock);
}).start();
new Thread(() -> {
lock.lock(); // 线程B加锁(轻量级锁)
System.out.println("Locked by thread B");
printLockInfo(lock);
lock.unlock(); // 线程B解锁
System.out.println("Unlocked by thread B");
printLockInfo(lock);
}).start();
}
private static void printLockInfo(Lock lock) {
ReentrantLock reentrantLock = (ReentrantLock) lock;
System.out.println("Lock state: " + reentrantLock.getState());
System.out.println("Is fair: " + reentrantLock.isFair());
}
}
重量级锁
功能
确保并发执行时同步操作的正确性
实现方式
以对象 Monitor 为基础实现锁;在同步块开始时,虚拟机会创建 Monitor 锁对象,然后将锁对象与被同步对象关联起来;当线程进入同步块时,它尝试获取 Monitor 锁对象的所有权;当线程执行完毕后,会释放 Monitor 锁对象的所有权。由于重量级锁涉及到用户态和内核态的切换,因此效率比偏斜锁和轻量级锁要低,一般只在无法优化的情况下使用
具体实现
当线程进入同步块时,如果没有竞争,则虚拟机会创建表示 Monitor 的 C++ 对象,并将其与对应的 Java 对象进行关联,同时将锁记录中的 owner 指向该 Monitor 对象。当有多个线程竞争该锁时,会使该锁升级为重量级锁。此时虚拟机会将该 Monitor 对象的 owner 指向 null,并记录当前竞争锁的所有线程;之后等待其他线程释放锁,并通过内核 Mutex 机制来阻塞当前线程,直到该线程被唤醒并成功获取到锁。
示例代码
下面代码中,首先创建了一个 ReentrantLock
对象。之后创建了两个线程 A 和 B,分别尝试获取锁。由于线程 A 的执行时间较长,因此当线程 B 尝试获取锁时,会因为发现存在竞争而升级为重量级锁。当线程 A 执行完毕后,会释放锁并降级为轻量级锁。最终,两个线程都可以成功获取锁并解锁,并输出锁状态信息.
public class HeavyweightLockExample {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(() -> {
lock.lock(); // 线程A加锁(轻量级锁)
System.out.println("Locked by thread A");
printLockInfo(lock);
try {
Thread.sleep(5000L); // 线程A等待5秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock(); // 线程A解锁
System.out.println("Unlocked by thread A");
printLockInfo(lock);
}).start();
new Thread(() -> {
lock.lock(); // 线程B加锁(重量级锁)
System.out.println("Locked by thread B");
printLockInfo(lock);
lock.unlock(); // 线程B解锁
System.out.println("Unlocked by thread B");
printLockInfo(lock);
}).start();
}
private static void printLockInfo(Lock lock) {
ReentrantLock reentrantLock = (ReentrantLock) lock;
System.out.println("Lock state: " + reentrantLock.getState());
System.out.println("Is fair: " + reentrantLock.isFair());
}
}