1. 可重入锁
概念: 可重入锁(Reentrant Lock)意味着锁是基于线程的,而不是基于调用的。这意味着同一个线程可以多次获取自己已经持有的锁,而不会发生死锁。这种设计能够简化代码的复杂性,尤其是在调用链条中涉及多次锁定的情况下。
Java中的实现:
- ReentrantLock: 这是 Java 中显式锁的实现类,它可以替代
synchronized
,提供更多的功能,比如尝试获取锁、可中断的锁获取、定时获取锁等。 - synchronized: 隐式可重入锁的实现。
例子: 如果一个线程已经在一个同步方法中获得了某个对象的锁,那么它可以在同一个对象的另一个同步方法中再次获取该锁,而不需要再次进行锁竞争。
2. synchronized 的实现原理
基于 monitor: synchronized 是通过 monitor 对象实现的,每个 Java 对象都有一个 monitor。当一个线程获得了对象的 monitor 时,该对象就被锁定。其他线程若要访问这个对象,就需要等待该 monitor 被释放。
指令:
monitorenter
: 当线程进入同步代码块时,会尝试获取对象的 monitor。monitorexit
: 当线程离开同步代码块时,会释放对象的 monitor。
3. synchronized 的缺点
性能问题:
- 重量级锁: 在 Java SE 1.6 之前,synchronized 被认为是重量级锁,因为它涉及频繁的上下文切换,这会导致较高的系统资源消耗。
- 上下文切换: 当线程在获取锁时被阻塞,它会被挂起并等待唤醒,而每次上下文切换都会带来一定的开销。
4. 锁升级机制
Java SE 1.6 引入了锁升级机制,旨在优化不同场景下的锁性能。锁的状态可以在无锁、偏向锁、轻量级锁、重量级锁之间动态转换。
- 无锁: 初始状态,没有线程访问同步代码块。
- 偏向锁: 如果只有一个线程访问锁,锁会偏向这个线程,避免不必要的开销。
- 轻量级锁: 如果有少量的线程竞争,锁会升级为轻量级锁,采用 CAS 操作来实现锁的获取与释放,避免线程阻塞。
- 重量级锁: 当竞争激烈时,锁会膨胀为重量级锁,此时线程会被阻塞,等待锁的释放。
5. 锁升级的优缺点
- 偏向锁:
- 优点: 加锁和解锁操作几乎没有开销,适合单线程场景。
- 缺点: 如果线程之间存在竞争,锁撤销会产生额外开销。
- 轻量级锁:
- 优点: 线程不会阻塞,提高了执行效率,适合响应时间要求高的场景。
- 缺点: 自旋会消耗 CPU 资源,适合同步代码块执行时间短的场景。
- 重量级锁:
- 优点: 不消耗 CPU(因为线程被阻塞),适合同步代码块执行时间长的场景。
- 缺点: 线程阻塞和唤醒会导致较大的性能开销。
总结
- 可重入锁和
synchronized
是 Java 多线程编程中的基础概念,理解它们的实现原理和优化机制有助于写出高效的并发代码。 - 锁升级机制使得
synchronized
在多种并发场景中表现出色,但选择合适的锁类型(例如使用 ReentrantLock)和优化代码结构,仍然是提高并发性能的关键。