Java 并发编程中的悲观锁和 synchronized
关键字,以及 Java 内存模型中的锁优化机制(如偏向锁、轻量级锁)都是非常重要的概念。下面将详细介绍这些内容。
悲观锁(Pessimistic Locking)
悲观锁假设数据会发生冲突,因此在读取数据时就加锁,以防止其他线程修改数据。这种方式虽然能保证数据的一致性,但可能会降低系统的并发性能,因为它增加了锁的持有时间。
悲观锁的实现方式:
- 数据库中的悲观锁:在数据库中,可以使用行级锁或者表级锁来实现悲观锁。
- Java 中的悲观锁:在 Java 中,最常用的悲观锁实现方式是使用
synchronized
关键字或者ReentrantLock
类。
synchronized 关键字
synchronized
是 Java 中内置的关键字之一,它可以用来实现悲观锁。当一个线程进入一个 synchronized
代码块或者方法时,它会获取一个锁,其他试图获取相同锁的线程会被阻塞,直到第一个线程释放锁。
示例
public class SynchronizedExample {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public synchronized int getCount() {
return counter;
}
}
在这个例子中,increment
和 getCount
方法都使用了 synchronized
关键字来保证线程安全。
Java 内存模型中的锁优化
Java 内存模型针对 synchronized
关键字进行了多种优化,以提高程序的并发性能。这些优化包括偏向锁、轻量级锁等。
偏向锁(Biased Locking)
偏向锁是 Java 5 引入的一种锁优化策略,它的目标是在无多线程竞争的前提下尽量减少不必要的轻量级锁执行路径。当一个线程访问同步块并获取锁时,会在对象头中存储当前线程的 ID,以后该线程再次进入时可以直接进入代码块,而无需再次获取锁。
轻量级锁(Lightweight Locking)
轻量级锁是 Java 6 引入的一种锁优化策略,它使用 CAS(Compare-and-Swap)操作来实现。当一个线程尝试获取锁时,首先尝试使用 CAS 将对象头中的锁标志位设置为 01(表示轻量级锁),如果 CAS 成功,则表示当前没有其他线程竞争锁,直接获取锁;如果 CAS 失败,则需要膨胀为重量级锁。
锁膨胀
当轻量级锁尝试获取锁失败时,会升级为重量级锁。重量级锁使用操作系统提供的互斥锁(mutex)来实现,这会导致线程挂起和唤醒,从而增加上下文切换的开销。
锁的优化过程
以下是 synchronized
锁的优化过程:
- 无锁状态:初始状态下,对象头中没有锁标志位,表示无锁。
- 偏向锁:当第一个线程获取锁时,如果对象头中没有锁标志位,就会尝试使用 CAS 设置锁标志位为 01(偏向锁),并把线程 ID 存储在对象头中。如果 CAS 成功,则进入偏向锁状态。
- 轻量级锁:如果对象已经是偏向锁状态,且当前线程与对象头中的线程 ID 相同,则直接进入同步块;如果不同,则尝试获取轻量级锁。
- 重量级锁:如果轻量级锁获取失败,则锁膨胀为重量级锁,此时线程会阻塞,等待锁的释放。
总结
Java 中的锁优化机制通过使用偏向锁和轻量级锁等技术来减少锁的竞争和开销,从而提高并发性能。synchronized
关键字提供了一种简单而强大的方式来实现悲观锁,而 Java 内存模型则负责自动选择最合适的锁优化策略。理解这些机制有助于开发者编写高效、可靠的并发程序。